mirror of
https://github.com/nextcloud/server.git
synced 2026-06-11 01:30:50 -04:00
Merge pull request #39685 from nextcloud/feat/integrate-files-reminders
This commit is contained in:
commit
7c7a3be566
30 changed files with 780 additions and 31 deletions
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,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"}
|
||||
{"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,2BAKA,0EACC,wCAKF,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"}
|
||||
|
|
@ -593,7 +593,7 @@ table td.selection {
|
|||
background-color: var(--color-main-background);
|
||||
mask: var(--icon-star-rounded-white) no-repeat;
|
||||
mask-size: 22px 22px;
|
||||
|
||||
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
display: flex;
|
||||
|
|
@ -655,8 +655,11 @@ a.action > img {
|
|||
vertical-align: text-bottom;
|
||||
}
|
||||
|
||||
a.action.action-editlocally img.icon {
|
||||
filter: var(--background-invert-if-dark);
|
||||
a.action.action-editlocally,
|
||||
a.action.action-setreminder {
|
||||
img.icon {
|
||||
filter: var(--background-invert-if-dark);
|
||||
}
|
||||
}
|
||||
|
||||
/* Actions for selected files */
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -710,6 +710,22 @@
|
|||
}
|
||||
});
|
||||
|
||||
if (Boolean(OC.appswebroots.files_reminders) && Boolean(OC.appswebroots.notifications)) {
|
||||
this.registerAction({
|
||||
name: 'SetReminder',
|
||||
displayName: function(_context) {
|
||||
return t('files', 'Set reminder');
|
||||
},
|
||||
mime: 'all',
|
||||
order: -24,
|
||||
icon: function(_filename, _context) {
|
||||
return OC.imagePath('files_reminders', 'alarm.svg')
|
||||
},
|
||||
permissions: OC.PERMISSION_READ,
|
||||
actionHandler: function(_filename, _context) {},
|
||||
});
|
||||
}
|
||||
|
||||
if (!/Android|iPhone|iPad|iPod/i.test(navigator.userAgent)) {
|
||||
this.registerAction({
|
||||
name: 'EditLocally',
|
||||
|
|
|
|||
|
|
@ -133,6 +133,11 @@
|
|||
this.render();
|
||||
this.$el.removeClass('hidden');
|
||||
|
||||
window._nc_event_bus.emit('files:action-menu:opened', {
|
||||
el: this.$el[0],
|
||||
context,
|
||||
})
|
||||
|
||||
OC.showMenu(null, this.$el);
|
||||
}
|
||||
});
|
||||
|
|
@ -140,4 +145,3 @@
|
|||
OCA.Files.FileActionsMenu = FileActionsMenu;
|
||||
|
||||
})();
|
||||
|
||||
|
|
|
|||
|
|
@ -707,8 +707,10 @@
|
|||
tr.addClass('highlighted');
|
||||
this._currentFileModel = model;
|
||||
|
||||
const secondaryActionsOpen = Boolean(tr.find('.actions-secondary-vue').length)
|
||||
|
||||
// open sidebar and set file
|
||||
if (typeof show === 'undefined' || !!show || (OCA.Files.Sidebar.file !== '')) {
|
||||
if (!secondaryActionsOpen && (typeof show === 'undefined' || !!show || (OCA.Files.Sidebar.file !== ''))) {
|
||||
OCA.Files.Sidebar.open(path.replace('//', '/'))
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
Set file reminders.
|
||||
]]></description>
|
||||
<version>0.1.0-dev</version>
|
||||
<version>1.0.0</version>
|
||||
<licence>agpl</licence>
|
||||
<author>Christopher Ng</author>
|
||||
<namespace>FilesReminders</namespace>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ return array(
|
|||
'OCA\\FilesReminders\\Db\\ReminderMapper' => $baseDir . '/../lib/Db/ReminderMapper.php',
|
||||
'OCA\\FilesReminders\\Exception\\NodeNotFoundException' => $baseDir . '/../lib/Exception/NodeNotFoundException.php',
|
||||
'OCA\\FilesReminders\\Exception\\UserNotFoundException' => $baseDir . '/../lib/Exception/UserNotFoundException.php',
|
||||
'OCA\\FilesReminders\\Listener\\LoadAdditionalScriptsListener' => $baseDir . '/../lib/Listener/LoadAdditionalScriptsListener.php',
|
||||
'OCA\\FilesReminders\\Listener\\NodeDeletedListener' => $baseDir . '/../lib/Listener/NodeDeletedListener.php',
|
||||
'OCA\\FilesReminders\\Listener\\UserDeletedListener' => $baseDir . '/../lib/Listener/UserDeletedListener.php',
|
||||
'OCA\\FilesReminders\\Migration\\Version10000Date20230725162149' => $baseDir . '/../lib/Migration/Version10000Date20230725162149.php',
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ class ComposerStaticInitFilesReminders
|
|||
'OCA\\FilesReminders\\Db\\ReminderMapper' => __DIR__ . '/..' . '/../lib/Db/ReminderMapper.php',
|
||||
'OCA\\FilesReminders\\Exception\\NodeNotFoundException' => __DIR__ . '/..' . '/../lib/Exception/NodeNotFoundException.php',
|
||||
'OCA\\FilesReminders\\Exception\\UserNotFoundException' => __DIR__ . '/..' . '/../lib/Exception/UserNotFoundException.php',
|
||||
'OCA\\FilesReminders\\Listener\\LoadAdditionalScriptsListener' => __DIR__ . '/..' . '/../lib/Listener/LoadAdditionalScriptsListener.php',
|
||||
'OCA\\FilesReminders\\Listener\\NodeDeletedListener' => __DIR__ . '/..' . '/../lib/Listener/NodeDeletedListener.php',
|
||||
'OCA\\FilesReminders\\Listener\\UserDeletedListener' => __DIR__ . '/..' . '/../lib/Listener/UserDeletedListener.php',
|
||||
'OCA\\FilesReminders\\Migration\\Version10000Date20230725162149' => __DIR__ . '/..' . '/../lib/Migration/Version10000Date20230725162149.php',
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
'name' => '__root__',
|
||||
'pretty_version' => 'dev-master',
|
||||
'version' => 'dev-master',
|
||||
'reference' => '5a11535c51ae0277f6bb0af048215e329b6068d0',
|
||||
'reference' => '9685b49f0d8f9e7d34f299e51628748a04d0e175',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../',
|
||||
'aliases' => array(),
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
'__root__' => array(
|
||||
'pretty_version' => 'dev-master',
|
||||
'version' => 'dev-master',
|
||||
'reference' => '5a11535c51ae0277f6bb0af048215e329b6068d0',
|
||||
'reference' => '9685b49f0d8f9e7d34f299e51628748a04d0e175',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../',
|
||||
'aliases' => array(),
|
||||
|
|
|
|||
5
apps/files_reminders/img/alarm.svg
Normal file
5
apps/files_reminders/img/alarm.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000">
|
||||
<path d="M0 0h24v24H0V0z" fill="none" />
|
||||
<path
|
||||
d="M12.5 8H11v6l4.75 2.85.75-1.23-4-2.37zm4.837-6.19l4.607 3.845-1.28 1.535-4.61-3.843zm-10.674 0l1.282 1.536L3.337 7.19l-1.28-1.536zM12 4c-4.97 0-9 4.03-9 9s4.03 9 9 9 9-4.03 9-9-4.03-9-9-9zm0 16c-3.86 0-7-3.14-7-7s3.14-7 7-7 7 3.14 7 7-3.14 7-7 7z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 415 B |
|
|
@ -26,6 +26,8 @@ declare(strict_types=1);
|
|||
|
||||
namespace OCA\FilesReminders\AppInfo;
|
||||
|
||||
use OCA\Files\Event\LoadAdditionalScriptsEvent;
|
||||
use OCA\FilesReminders\Listener\LoadAdditionalScriptsListener;
|
||||
use OCA\FilesReminders\Listener\NodeDeletedListener;
|
||||
use OCA\FilesReminders\Listener\UserDeletedListener;
|
||||
use OCA\FilesReminders\Notification\Notifier;
|
||||
|
|
@ -51,5 +53,7 @@ class Application extends App implements IBootstrap {
|
|||
|
||||
$context->registerEventListener(NodeDeletedEvent::class, NodeDeletedListener::class);
|
||||
$context->registerEventListener(UserDeletedEvent::class, UserDeletedListener::class);
|
||||
|
||||
$context->registerEventListener(LoadAdditionalScriptsEvent::class, LoadAdditionalScriptsListener::class);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace OCA\FilesReminders\Db;
|
||||
|
||||
use DateTime;
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\AppFramework\Db\QBMapper;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
|
|
@ -138,12 +139,13 @@ class ReminderMapper extends QBMapper {
|
|||
/**
|
||||
* @return Reminder[]
|
||||
*/
|
||||
public function findNotified(?int $limit = null) {
|
||||
public function findNotified(DateTime $buffer, ?int $limit = null) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
|
||||
$qb->select('id', 'user_id', 'file_id', 'due_date', 'updated_at', 'created_at', 'notified')
|
||||
->from($this->getTableName())
|
||||
->where($qb->expr()->eq('notified', $qb->createNamedParameter(true, IQueryBuilder::PARAM_BOOL)))
|
||||
->andWhere($qb->expr()->lt('due_date', $qb->createNamedParameter($buffer, IQueryBuilder::PARAM_DATE)))
|
||||
->orderBy('due_date', 'ASC')
|
||||
->setMaxResults($limit);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2023 Christopher Ng <chrng8@gmail.com>
|
||||
*
|
||||
* @author Christopher Ng <chrng8@gmail.com>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\FilesReminders\Listener;
|
||||
|
||||
use OCA\Files\Event\LoadAdditionalScriptsEvent;
|
||||
use OCA\FilesReminders\AppInfo\Application;
|
||||
use OCP\App\IAppManager;
|
||||
use OCP\EventDispatcher\Event;
|
||||
use OCP\EventDispatcher\IEventListener;
|
||||
use OCP\Util;
|
||||
|
||||
class LoadAdditionalScriptsListener implements IEventListener {
|
||||
public function __construct(
|
||||
private IAppManager $appManager,
|
||||
) {}
|
||||
|
||||
public function handle(Event $event): void {
|
||||
if (!($event instanceof LoadAdditionalScriptsEvent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->appManager->isEnabledForUser('notifications')) {
|
||||
return;
|
||||
}
|
||||
|
||||
Util::addScript(Application::APP_ID, 'main');
|
||||
}
|
||||
}
|
||||
|
|
@ -28,12 +28,11 @@ namespace OCA\FilesReminders\Notification;
|
|||
|
||||
use InvalidArgumentException;
|
||||
use OCA\FilesReminders\AppInfo\Application;
|
||||
use OCA\FilesReminders\Exception\NodeNotFoundException;
|
||||
use OCA\FilesReminders\Service\ReminderService;
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\Files\FileInfo;
|
||||
use OCP\Files\IRootFolder;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\L10N\IFactory;
|
||||
use OCP\Notification\AlreadyProcessedException;
|
||||
use OCP\Notification\IAction;
|
||||
use OCP\Notification\INotification;
|
||||
use OCP\Notification\INotifier;
|
||||
|
|
@ -42,7 +41,7 @@ class Notifier implements INotifier {
|
|||
public function __construct(
|
||||
protected IFactory $l10nFactory,
|
||||
protected IURLGenerator $urlGenerator,
|
||||
protected ReminderService $reminderService,
|
||||
protected IRootFolder $root,
|
||||
) {}
|
||||
|
||||
public function getID(): string {
|
||||
|
|
@ -55,6 +54,7 @@ class Notifier implements INotifier {
|
|||
|
||||
/**
|
||||
* @throws InvalidArgumentException
|
||||
* @throws AlreadyProcessedException
|
||||
*/
|
||||
public function prepare(INotification $notification, string $languageCode): INotification {
|
||||
$l = $this->l10nFactory->get(Application::APP_ID, $languageCode);
|
||||
|
|
@ -65,18 +65,15 @@ class Notifier implements INotifier {
|
|||
|
||||
switch ($notification->getSubject()) {
|
||||
case 'reminder-due':
|
||||
$reminderId = (int)$notification->getObjectId();
|
||||
try {
|
||||
$reminder = $this->reminderService->get($reminderId);
|
||||
} catch (DoesNotExistException $e) {
|
||||
throw new InvalidArgumentException();
|
||||
}
|
||||
$params = $notification->getSubjectParameters();
|
||||
$fileId = $params['fileId'];
|
||||
|
||||
try {
|
||||
$node = $reminder->getNode();
|
||||
} catch (NodeNotFoundException $e) {
|
||||
$nodes = $this->root->getUserFolder($notification->getUser())->getById($fileId);
|
||||
if (empty($nodes)) {
|
||||
throw new InvalidArgumentException();
|
||||
}
|
||||
$node = reset($nodes);
|
||||
|
||||
$path = rtrim($node->getPath(), '/');
|
||||
if (strpos($path, '/' . $notification->getUser() . '/files/') === 0) {
|
||||
// Remove /user/files/...
|
||||
|
|
|
|||
|
|
@ -155,7 +155,9 @@ class ReminderService {
|
|||
->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('files', 'folder.svg')))
|
||||
->setUser($user->getUID())
|
||||
->setObject('reminder', (string)$reminder->getId())
|
||||
->setSubject('reminder-due')
|
||||
->setSubject('reminder-due', [
|
||||
'fileId' => $reminder->getFileId(),
|
||||
])
|
||||
->setDateTime($reminder->getDueDate());
|
||||
|
||||
try {
|
||||
|
|
@ -167,7 +169,10 @@ class ReminderService {
|
|||
}
|
||||
|
||||
public function cleanUp(?int $limit = null): void {
|
||||
$reminders = $this->reminderMapper->findNotified($limit);
|
||||
$buffer = (new DateTime())
|
||||
->setTimezone(new DateTimeZone('UTC'))
|
||||
->modify('-1 day');
|
||||
$reminders = $this->reminderMapper->findNotified($buffer, $limit);
|
||||
foreach ($reminders as $reminder) {
|
||||
$this->reminderMapper->delete($reminder);
|
||||
}
|
||||
|
|
|
|||
272
apps/files_reminders/src/components/SetReminderActions.vue
Normal file
272
apps/files_reminders/src/components/SetReminderActions.vue
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
<!--
|
||||
- @copyright 2023 Christopher Ng <chrng8@gmail.com>
|
||||
-
|
||||
- @author Christopher Ng <chrng8@gmail.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/>.
|
||||
-
|
||||
-->
|
||||
|
||||
<template>
|
||||
<NcActions class="actions-secondary-vue"
|
||||
:open.sync="open">
|
||||
<NcActionButton @click="$emit('back')">
|
||||
<template #icon>
|
||||
<ArrowLeft :size="20" />
|
||||
</template>
|
||||
{{ t('files_reminders', 'Back') }}
|
||||
</NcActionButton>
|
||||
|
||||
<NcActionButton v-if="Boolean(dueDate)"
|
||||
:aria-label="clearAriaLabel"
|
||||
@click="clear">
|
||||
<template #icon>
|
||||
<CloseCircleOutline :size="20" />
|
||||
</template>
|
||||
{{ t('files_reminders', 'Clear reminder') }} – {{ getDateString(dueDate) }}
|
||||
</NcActionButton>
|
||||
|
||||
<NcActionSeparator />
|
||||
|
||||
<NcActionButton v-for="({ label, ariaLabel, dateString, action }) in options"
|
||||
:key="label"
|
||||
:aria-label="ariaLabel"
|
||||
@click="action">
|
||||
{{ label }} – {{ dateString }}
|
||||
</NcActionButton>
|
||||
|
||||
<NcActionSeparator />
|
||||
|
||||
<NcActionInput type="datetime-local"
|
||||
is-native-picker
|
||||
:min="now"
|
||||
v-model="customDueDate">
|
||||
<template #icon>
|
||||
<CalendarClock :size="20" />
|
||||
</template>
|
||||
</NcActionInput>
|
||||
|
||||
<NcActionButton :aria-label="customAriaLabel"
|
||||
@click="setCustom">
|
||||
<template #icon>
|
||||
<Check :size="20" />
|
||||
</template>
|
||||
{{ t('files_reminders', 'Set custom reminder') }}
|
||||
</NcActionButton>
|
||||
</NcActions>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue, { type PropType } from 'vue'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
import { showError, showSuccess } from '@nextcloud/dialogs'
|
||||
|
||||
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
|
||||
import NcActionInput from '@nextcloud/vue/dist/Components/NcActionInput.js'
|
||||
import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
|
||||
import NcActionSeparator from '@nextcloud/vue/dist/Components/NcActionSeparator.js'
|
||||
|
||||
import ArrowLeft from 'vue-material-design-icons/ArrowLeft.vue'
|
||||
import CalendarClock from 'vue-material-design-icons/CalendarClock.vue'
|
||||
import Check from 'vue-material-design-icons/Check.vue'
|
||||
import CloseCircleOutline from 'vue-material-design-icons/CloseCircleOutline.vue'
|
||||
|
||||
import { clearReminder, setReminder } from '../services/reminderService.ts'
|
||||
import {
|
||||
DateTimePreset,
|
||||
getDateString,
|
||||
getDateTime,
|
||||
getInitialCustomDueDate,
|
||||
getVerboseDateString,
|
||||
} from '../shared/utils.ts'
|
||||
import { logger } from '../shared/logger.ts'
|
||||
|
||||
import type { FileAttributes } from '../shared/types.ts'
|
||||
|
||||
interface ReminderOption {
|
||||
dateTimePreset: DateTimePreset
|
||||
label: string
|
||||
ariaLabel: string
|
||||
dateString?: string
|
||||
action?: () => Promise<void>
|
||||
}
|
||||
|
||||
const laterToday: ReminderOption = {
|
||||
dateTimePreset: DateTimePreset.LaterToday,
|
||||
label: t('files_reminders', 'Later today'),
|
||||
ariaLabel: t('files_reminders', 'Set reminder for later today'),
|
||||
}
|
||||
|
||||
const tomorrow: ReminderOption = {
|
||||
dateTimePreset: DateTimePreset.Tomorrow,
|
||||
label: t('files_reminders', 'Tomorrow'),
|
||||
ariaLabel: t('files_reminders', 'Set reminder for tomorrow'),
|
||||
}
|
||||
|
||||
const thisWeekend: ReminderOption = {
|
||||
dateTimePreset: DateTimePreset.ThisWeekend,
|
||||
label: t('files_reminders', 'This weekend'),
|
||||
ariaLabel: t('files_reminders', 'Set reminder for this weekend'),
|
||||
}
|
||||
|
||||
const nextWeek: ReminderOption = {
|
||||
dateTimePreset: DateTimePreset.NextWeek,
|
||||
label: t('files_reminders', 'Next week'),
|
||||
ariaLabel: t('files_reminders', 'Set reminder for next week'),
|
||||
}
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'SetReminderActions',
|
||||
|
||||
components: {
|
||||
ArrowLeft,
|
||||
CalendarClock,
|
||||
Check,
|
||||
CloseCircleOutline,
|
||||
NcActionButton,
|
||||
NcActionInput,
|
||||
NcActions,
|
||||
NcActionSeparator,
|
||||
},
|
||||
|
||||
props: {
|
||||
file: {
|
||||
type: Object as PropType<FileAttributes>,
|
||||
required: true,
|
||||
},
|
||||
|
||||
dueDate: {
|
||||
type: Date as PropType<null | Date>,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
open: true,
|
||||
now: new Date(),
|
||||
customDueDate: getInitialCustomDueDate() as '' | Date,
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
open(isOpen) {
|
||||
if (!isOpen) {
|
||||
this.$emit('close')
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
fileId(): number {
|
||||
return this.file.id
|
||||
},
|
||||
|
||||
fileName(): string {
|
||||
return this.file.name
|
||||
},
|
||||
|
||||
clearAriaLabel(): string {
|
||||
return `${t('files_reminders', 'Clear reminder')} – ${getVerboseDateString(this.dueDate as Date)}`
|
||||
},
|
||||
|
||||
customAriaLabel(): null | string {
|
||||
if (this.customDueDate === '') {
|
||||
return null
|
||||
}
|
||||
return `${t('files_reminders', 'Set reminder at custom date & time')} – ${getVerboseDateString(this.customDueDate)}`
|
||||
},
|
||||
|
||||
options(): ReminderOption[] {
|
||||
const computeOption = (option: ReminderOption): null | ReminderOption => {
|
||||
const dateTime = getDateTime(option.dateTimePreset)
|
||||
if (!dateTime) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
...option,
|
||||
ariaLabel: `${option.ariaLabel} – ${getVerboseDateString(dateTime)}`,
|
||||
dateString: getDateString(dateTime),
|
||||
action: () => this.set(dateTime),
|
||||
}
|
||||
}
|
||||
|
||||
const options = [
|
||||
laterToday,
|
||||
tomorrow,
|
||||
thisWeekend,
|
||||
nextWeek,
|
||||
]
|
||||
return options
|
||||
.map(computeOption)
|
||||
.filter(Boolean) as ReminderOption[]
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
t,
|
||||
getDateString,
|
||||
|
||||
async set(dueDate: Date): Promise<void> {
|
||||
try {
|
||||
await setReminder(this.fileId, dueDate)
|
||||
showSuccess(t('files_reminders', 'Reminder set for "{fileName}"', { fileName: this.fileName }))
|
||||
this.open = false
|
||||
} catch (error) {
|
||||
logger.error('Failed to set reminder', { error })
|
||||
showError(t('files_reminders', 'Failed to set reminder'))
|
||||
}
|
||||
},
|
||||
|
||||
async setCustom(): Promise<void> {
|
||||
// Handle input cleared
|
||||
if (this.customDueDate === '') {
|
||||
showError(t('files_reminders', 'Please choose a valid date & time'))
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await setReminder(this.fileId, this.customDueDate)
|
||||
showSuccess(t('files_reminders', 'Reminder set for "{fileName}"', { fileName: this.fileName }))
|
||||
this.open = false
|
||||
} catch (error) {
|
||||
logger.error('Failed to set reminder', { error })
|
||||
showError(t('files_reminders', 'Failed to set reminder'))
|
||||
}
|
||||
},
|
||||
|
||||
async clear(): Promise<void> {
|
||||
try {
|
||||
await clearReminder(this.fileId)
|
||||
showSuccess(t('files_reminders', 'Reminder cleared'))
|
||||
this.open = false
|
||||
} catch (error) {
|
||||
logger.error('Failed to clear reminder', { error })
|
||||
showError(t('files_reminders', 'Failed to clear reminder'))
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.actions-secondary-vue {
|
||||
display: block !important;
|
||||
float: right !important;
|
||||
padding: 5px 0 0 4px !important;
|
||||
pointer-events: none !important; // prevent activation of file row
|
||||
}
|
||||
</style>
|
||||
102
apps/files_reminders/src/main.ts
Normal file
102
apps/files_reminders/src/main.ts
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
/**
|
||||
* @copyright 2023 Christopher Ng <chrng8@gmail.com>
|
||||
*
|
||||
* @author Christopher Ng <chrng8@gmail.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 Vue, { type ComponentInstance } from 'vue'
|
||||
import { subscribe } from '@nextcloud/event-bus'
|
||||
import { showError } from '@nextcloud/dialogs'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
|
||||
import SetReminderActionsComponent from './components/SetReminderActions.vue'
|
||||
|
||||
import { getReminder } from './services/reminderService.js'
|
||||
import { logger } from './shared/logger.js'
|
||||
|
||||
import type { FileAttributes } from './shared/types.js'
|
||||
|
||||
interface FileContext {
|
||||
[key: string]: any
|
||||
$file: JQuery<HTMLTableRowElement>
|
||||
fileInfoModel: {
|
||||
[key: string]: any
|
||||
attributes: FileAttributes
|
||||
}
|
||||
}
|
||||
|
||||
interface EventPayload {
|
||||
el: HTMLDivElement
|
||||
context: FileContext
|
||||
}
|
||||
|
||||
const handleOpen = async (payload: EventPayload) => {
|
||||
const fileId = payload.context.fileInfoModel.attributes.id
|
||||
const menuEl = payload.context.$file[0].querySelector('.fileactions .action-menu') as HTMLLinkElement
|
||||
const linkEl = payload.el.querySelector('.action-setreminder-container .action-setreminder') as HTMLLinkElement
|
||||
|
||||
let dueDate: null | Date = null
|
||||
let error: null | any = null
|
||||
try {
|
||||
dueDate = (await getReminder(fileId)).dueDate
|
||||
} catch (e) {
|
||||
error = e
|
||||
logger.error(`Failed to load reminder for file with id: ${fileId}`, { error })
|
||||
}
|
||||
|
||||
linkEl.addEventListener('click', (_event) => {
|
||||
if (error) {
|
||||
showError(t('files_reminders', 'Failed to load reminder'))
|
||||
throw Error()
|
||||
}
|
||||
|
||||
const mountPoint = document.createElement('div')
|
||||
const SetReminderActions = Vue.extend(SetReminderActionsComponent)
|
||||
|
||||
const origDisplay = menuEl.style.display
|
||||
menuEl.style.display = 'none'
|
||||
menuEl.insertAdjacentElement('afterend', mountPoint)
|
||||
|
||||
const propsData = {
|
||||
file: payload.context.fileInfoModel.attributes,
|
||||
dueDate,
|
||||
}
|
||||
const actions = (new SetReminderActions({ propsData }) as ComponentInstance)
|
||||
.$mount(mountPoint)
|
||||
|
||||
const cleanUp = () => {
|
||||
actions.$destroy() // destroy popper
|
||||
actions.$el.remove() // remove action menu button
|
||||
menuEl.style.display = origDisplay
|
||||
}
|
||||
|
||||
actions.$once('back', () => {
|
||||
cleanUp()
|
||||
menuEl.click() // reopen original actions menu
|
||||
})
|
||||
|
||||
actions.$once('close', () => {
|
||||
cleanUp()
|
||||
})
|
||||
}, {
|
||||
once: true,
|
||||
})
|
||||
}
|
||||
|
||||
subscribe('files:action-menu:opened', handleOpen)
|
||||
55
apps/files_reminders/src/services/reminderService.ts
Normal file
55
apps/files_reminders/src/services/reminderService.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
* @copyright 2023 Christopher Ng <chrng8@gmail.com>
|
||||
*
|
||||
* @author Christopher Ng <chrng8@gmail.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 axios from '@nextcloud/axios'
|
||||
import { generateOcsUrl } from '@nextcloud/router'
|
||||
|
||||
interface Reminder {
|
||||
dueDate: null | Date
|
||||
}
|
||||
|
||||
export const getReminder = async (fileId: number): Promise<Reminder> => {
|
||||
const url = generateOcsUrl('/apps/files_reminders/api/v1/get/{fileId}', { fileId })
|
||||
const response = await axios.get(url)
|
||||
const dueDate = response.data.ocs.data.dueDate ? new Date(response.data.ocs.data.dueDate) : null
|
||||
|
||||
return {
|
||||
dueDate,
|
||||
}
|
||||
}
|
||||
|
||||
export const setReminder = async (fileId: number, dueDate: Date): Promise<[]> => {
|
||||
const url = generateOcsUrl('/apps/files_reminders/api/v1/set/{fileId}', { fileId })
|
||||
|
||||
const response = await axios.put(url, {
|
||||
dueDate: dueDate.toISOString(), // timezone of string is always UTC
|
||||
})
|
||||
|
||||
return response.data.ocs.data
|
||||
}
|
||||
|
||||
export const clearReminder = async (fileId: number): Promise<[]> => {
|
||||
const url = generateOcsUrl('/apps/files_reminders/api/v1/remove/{fileId}', { fileId })
|
||||
const response = await axios.delete(url)
|
||||
|
||||
return response.data.ocs.data
|
||||
}
|
||||
28
apps/files_reminders/src/shared/logger.ts
Normal file
28
apps/files_reminders/src/shared/logger.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* @copyright 2023 Christopher Ng <chrng8@gmail.com>
|
||||
*
|
||||
* @author Christopher Ng <chrng8@gmail.com>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
import { getLoggerBuilder } from '@nextcloud/logger'
|
||||
|
||||
export const logger = getLoggerBuilder()
|
||||
.setApp('files_reminders')
|
||||
.detectUser()
|
||||
.build()
|
||||
27
apps/files_reminders/src/shared/types.ts
Normal file
27
apps/files_reminders/src/shared/types.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
* @copyright 2023 Christopher Ng <chrng8@gmail.com>
|
||||
*
|
||||
* @author Christopher Ng <chrng8@gmail.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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
export interface FileAttributes {
|
||||
[key: string]: any
|
||||
id: number
|
||||
name: string
|
||||
}
|
||||
143
apps/files_reminders/src/shared/utils.ts
Normal file
143
apps/files_reminders/src/shared/utils.ts
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
/**
|
||||
* @copyright 2023 Christopher Ng <chrng8@gmail.com>
|
||||
*
|
||||
* @author Christopher Ng <chrng8@gmail.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 moment from '@nextcloud/moment'
|
||||
import { getCanonicalLocale } from '@nextcloud/l10n'
|
||||
|
||||
export enum DateTimePreset {
|
||||
LaterToday,
|
||||
Tomorrow,
|
||||
ThisWeekend,
|
||||
NextWeek,
|
||||
}
|
||||
|
||||
export const getDateTime = (dateTime: DateTimePreset): null | Date => {
|
||||
const matchPreset: Record<DateTimePreset, () => null | Date> = {
|
||||
[DateTimePreset.LaterToday]: () => {
|
||||
const now = moment()
|
||||
const evening = moment()
|
||||
.startOf('day')
|
||||
.add(18, 'hour')
|
||||
if (now.isSameOrAfter(evening)) {
|
||||
return null
|
||||
}
|
||||
return evening.toDate()
|
||||
},
|
||||
|
||||
[DateTimePreset.Tomorrow]: () => {
|
||||
const day = moment()
|
||||
.add(1, 'day')
|
||||
.startOf('day')
|
||||
.add(8, 'hour')
|
||||
return day.toDate()
|
||||
},
|
||||
|
||||
[DateTimePreset.ThisWeekend]: () => {
|
||||
const today = moment()
|
||||
const saturday = moment()
|
||||
.startOf('isoWeek')
|
||||
.add(5, 'day')
|
||||
.add(8, 'hour')
|
||||
const sunday = moment()
|
||||
.startOf('isoWeek')
|
||||
.add(6, 'day')
|
||||
.add(8, 'hour')
|
||||
if (today.isSame(saturday, 'date')) {
|
||||
return saturday
|
||||
.add(1, 'day')
|
||||
.toDate()
|
||||
}
|
||||
if (today.isSame(sunday, 'date')) {
|
||||
return sunday
|
||||
.add(1, 'week')
|
||||
.startOf('isoWeek')
|
||||
.add(5, 'day')
|
||||
.add(8, 'hour')
|
||||
.toDate()
|
||||
}
|
||||
return saturday.toDate()
|
||||
},
|
||||
|
||||
[DateTimePreset.NextWeek]: () => {
|
||||
const day = moment()
|
||||
.startOf('isoWeek')
|
||||
.add(1, 'week')
|
||||
.add(8, 'hour')
|
||||
return day.toDate()
|
||||
},
|
||||
}
|
||||
|
||||
return matchPreset[dateTime]()
|
||||
}
|
||||
|
||||
export const getInitialCustomDueDate = (): Date => {
|
||||
const hour = moment().get('hour')
|
||||
const dueDate = moment()
|
||||
.startOf('day')
|
||||
.add(hour + 2, 'hour')
|
||||
return dueDate.toDate()
|
||||
}
|
||||
|
||||
export const getDateString = (dueDate: Date): string => {
|
||||
let formatOptions: Intl.DateTimeFormatOptions = {
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
}
|
||||
|
||||
const dueDateMoment = moment(dueDate)
|
||||
const today = moment()
|
||||
|
||||
if (!dueDateMoment.isSame(today, 'date')) {
|
||||
formatOptions = {
|
||||
...formatOptions,
|
||||
weekday: 'short',
|
||||
}
|
||||
}
|
||||
|
||||
if (!dueDateMoment.isSame(today, 'week')) {
|
||||
formatOptions = {
|
||||
...formatOptions,
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
}
|
||||
}
|
||||
|
||||
return dueDate.toLocaleString(
|
||||
getCanonicalLocale(),
|
||||
formatOptions,
|
||||
)
|
||||
}
|
||||
|
||||
export const getVerboseDateString = (dueDate: Date): string => {
|
||||
const formatOptions: Intl.DateTimeFormatOptions = {
|
||||
weekday: 'long',
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
}
|
||||
|
||||
return dueDate.toLocaleString(
|
||||
getCanonicalLocale(),
|
||||
formatOptions,
|
||||
)
|
||||
}
|
||||
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
3
dist/files_reminders-main.js
vendored
Normal file
3
dist/files_reminders-main.js
vendored
Normal file
File diff suppressed because one or more lines are too long
23
dist/files_reminders-main.js.LICENSE.txt
vendored
Normal file
23
dist/files_reminders-main.js.LICENSE.txt
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
|
||||
|
||||
/**
|
||||
* @copyright 2023 Christopher Ng <chrng8@gmail.com>
|
||||
*
|
||||
* @author Christopher Ng <chrng8@gmail.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/>.
|
||||
*
|
||||
*/
|
||||
1
dist/files_reminders-main.js.map
vendored
Normal file
1
dist/files_reminders-main.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -57,6 +57,9 @@ module.exports = {
|
|||
files_external: {
|
||||
main: path.join(__dirname, 'apps/files_external/src', 'main.ts'),
|
||||
},
|
||||
files_reminders: {
|
||||
main: path.join(__dirname, 'apps/files_reminders/src', 'main.ts'),
|
||||
},
|
||||
files_sharing: {
|
||||
additionalScripts: path.join(__dirname, 'apps/files_sharing/src', 'additionalScripts.js'),
|
||||
collaboration: path.join(__dirname, 'apps/files_sharing/src', 'collaborationresourceshandler.js'),
|
||||
|
|
|
|||
Loading…
Reference in a new issue