This commit is contained in:
the-djmaze 2024-02-13 19:44:40 +01:00
parent 72815f842e
commit 3648b3eb60
7 changed files with 83 additions and 119 deletions

View file

@ -116,6 +116,8 @@ export class FolderCollectionModel extends AbstractCollectionModel
this.namespace; this.namespace;
this.optimized this.optimized
this.capabilities this.capabilities
this.allow; // allow adding
// this.exist;
} }
*/ */
@ -303,6 +305,7 @@ export class FolderModel extends AbstractModel {
super(); super();
this.fullName = ''; this.fullName = '';
this.parentName = '';
this.delimiter = ''; this.delimiter = '';
this.deep = 0; this.deep = 0;
this.expires = 0; this.expires = 0;
@ -323,12 +326,10 @@ export class FolderModel extends AbstractModel {
focused: false, focused: false,
selected: false, selected: false,
editing: false,
isSubscribed: true, isSubscribed: true,
checkable: false, // Check for new messages checkable: false, // Check for new messages
askDelete: false, askDelete: false,
nameForEdit: '',
errorMsg: '', errorMsg: '',
totalEmails: 0, totalEmails: 0,
@ -348,7 +349,6 @@ export class FolderModel extends AbstractModel {
this.addSubscribables({ this.addSubscribables({
kolabType: sValue => this.metadata[FolderMetadataKeys.KolabFolderType] = sValue, kolabType: sValue => this.metadata[FolderMetadataKeys.KolabFolderType] = sValue,
permanentFlags: aValue => this.tagsAllowed(aValue.includes('\\*')), permanentFlags: aValue => this.tagsAllowed(aValue.includes('\\*')),
editing: value => value && this.nameForEdit(this.name()),
unreadEmails: unread => FolderType.Inbox === this.type() && fireEvent('mailbox.inbox-unread-count', unread) unreadEmails: unread => FolderType.Inbox === this.type() && fireEvent('mailbox.inbox-unread-count', unread)
}); });
@ -485,18 +485,21 @@ export class FolderModel extends AbstractModel {
showScreenPopup(FolderPopupView, [this]); showScreenPopup(FolderPopupView, [this]);
} }
rename() { rename(nameToEdit, parentName) {
nameToEdit = nameToEdit.trim();
const folder = this, const folder = this,
nameToEdit = folder.nameForEdit().trim(); oldFullname = folder.fullName,
if (nameToEdit && folder.name() !== nameToEdit) { newFullname = parentName + folder.delimiter + nameToEdit;
if (nameToEdit && newFullname != oldFullname) {
Remote.abort('Folders').post('FolderRename', FolderUserStore.foldersRenaming, { Remote.abort('Folders').post('FolderRename', FolderUserStore.foldersRenaming, {
folder: folder.fullName, oldName: oldFullname,
newFolderName: nameToEdit, newName: newFullname,
subscribe: folder.isSubscribed() ? 1 : 0 subscribe: folder.isSubscribed() ? 1 : 0
}) })
.then(data => { .then(() => {
folder.name(nameToEdit/*data.name*/); folder.fullName = newFullname;
if (folder.subFolders.length) { folder.name(nameToEdit);
if (folder.subFolders.length || folder.parentName != parentName) {
Remote.setTrigger(FolderUserStore.foldersLoading, true); Remote.setTrigger(FolderUserStore.foldersLoading, true);
// clearTimeout(Remote.foldersTimeout); // clearTimeout(Remote.foldersTimeout);
// Remote.foldersTimeout = setTimeout(loadFolders, 500); // Remote.foldersTimeout = setTimeout(loadFolders, 500);
@ -504,7 +507,6 @@ export class FolderModel extends AbstractModel {
// TODO: rename all subfolders with folder.delimiter to prevent reload? // TODO: rename all subfolders with folder.delimiter to prevent reload?
} else { } else {
removeFolderFromCacheList(folder.fullName); removeFolderFromCacheList(folder.fullName);
folder.fullName = data.Result.fullName;
setFolder(folder); setFolder(folder);
const parent = getFolderFromCacheList(folder.parentName); const parent = getFolderFromCacheList(folder.parentName);
sortFolders(parent ? parent.subFolders : FolderUserStore.folderList); sortFolders(parent ? parent.subFolders : FolderUserStore.folderList);

View file

@ -64,16 +64,6 @@ class RemoteUserFetch extends AbstractFetchRemote {
[key]: value [key]: value
}); });
} }
/*
folderMove(sPrevFolderFullName, sNewFolderFullName, bSubscribe) {
return this.post('FolderMove', FolderUserStore.foldersRenaming, {
folder: sPrevFolderFullName,
newFolder: sNewFolderFullName,
subscribe: bSubscribe ? 1 : 0
});
}
*/
} }
export default new RemoteUserFetch(); export default new RemoteUserFetch();

View file

@ -1,24 +1,42 @@
import { AbstractViewPopup } from 'Knoin/AbstractViews'; import { AbstractViewPopup } from 'Knoin/AbstractViews';
import { addObservablesTo } from 'External/ko'; import { addObservablesTo, koComputable } from 'External/ko';
import Remote from 'Remote/User/Fetch'; import Remote from 'Remote/User/Fetch';
import { FolderUserStore } from 'Stores/User/Folder'; import { FolderUserStore } from 'Stores/User/Folder';
import { defaultOptionsAfterRender } from 'Common/Utils';
import { folderListOptionsBuilder } from 'Common/Folders';
export class FolderPopupView extends AbstractViewPopup { export class FolderPopupView extends AbstractViewPopup {
constructor() { constructor() {
super('Folder'); super('Folder');
addObservablesTo(this, { addObservablesTo(this, {
folder: null // FolderModel folder: null, // FolderModel
parentFolder: '',
name: '',
editing: false
}); });
this.ACLAllowed = FolderUserStore.hasCapability('ACL'); this.ACLAllowed = FolderUserStore.hasCapability('ACL');
this.ACL = ko.observableArray(); this.ACL = ko.observableArray();
this.parentFolderSelectList = koComputable(() =>
folderListOptionsBuilder(
[],
[['', '']],
oItem => oItem ? oItem.detailedName() : '',
item => !item.subFolders.allow
|| (FolderUserStore.namespace && !item.fullName.startsWith(FolderUserStore.namespace))
)
);
this.defaultOptionsAfterRender = defaultOptionsAfterRender;
} }
afterHide() { afterHide() {
this.folder().editing(false); this.editing(false);
} }
submitForm(form) { submitForm(form) {
this.folder().rename(); this.folder().rename(this.name(), this.parentFolder());
console.dir({form}); console.dir({form});
this.close(); this.close();
} }
@ -32,7 +50,9 @@ export class FolderPopupView extends AbstractViewPopup {
}, { }, {
folder: folder.fullName folder: folder.fullName
}); });
folder.editing(!folder.type() && folder.exists && folder.selectable()); this.editing(!folder.type() && folder.exists && folder.selectable());
this.name(folder.name()),
this.parentFolder(folder.parentName);
this.folder(folder); this.folder(folder);
} }
} }

View file

@ -29,7 +29,7 @@ class NamespaceResult implements \JsonSerializable
if (\is_array($entry) && 2 <= \count($entry)) { if (\is_array($entry) && 2 <= \count($entry)) {
$this->aPersonal[] = [ $this->aPersonal[] = [
'prefix' => \array_shift($entry), 'prefix' => \array_shift($entry),
'separator' => \array_shift($entry), 'delimiter' => \array_shift($entry),
'extension' => $entry 'extension' => $entry
]; ];
} }
@ -40,7 +40,7 @@ class NamespaceResult implements \JsonSerializable
if (\is_array($entry) && 2 <= \count($entry)) { if (\is_array($entry) && 2 <= \count($entry)) {
$this->aOtherUsers[] = [ $this->aOtherUsers[] = [
'prefix' => \array_shift($entry), 'prefix' => \array_shift($entry),
'separator' => \array_shift($entry), 'delimiter' => \array_shift($entry),
'extension' => $entry 'extension' => $entry
]; ];
} }
@ -51,7 +51,7 @@ class NamespaceResult implements \JsonSerializable
if (\is_array($entry) && 2 <= \count($entry)) { if (\is_array($entry) && 2 <= \count($entry)) {
$this->aShared[] = [ $this->aShared[] = [
'prefix' => \array_shift($entry), 'prefix' => \array_shift($entry),
'separator' => \array_shift($entry), 'delimiter' => \array_shift($entry),
'extension' => $entry 'extension' => $entry
]; ];
} }
@ -64,9 +64,9 @@ class NamespaceResult implements \JsonSerializable
$sPrefix = ''; $sPrefix = '';
if (isset($this->aPersonal[0])) { if (isset($this->aPersonal[0])) {
$sPrefix = $this->aPersonal[0]['prefix']; $sPrefix = $this->aPersonal[0]['prefix'];
$sSeparator = $this->aPersonal[0]['separator']; $sDelimiter = $this->aPersonal[0]['delimiter'];
if ('INBOX'.$sSeparator === \substr(\strtoupper($sPrefix), 0, 6)) { if ('INBOX'.$sDelimiter === \substr(\strtoupper($sPrefix), 0, 6)) {
$sPrefix = 'INBOX'.$sSeparator.\substr($sPrefix, 6); $sPrefix = 'INBOX'.$sDelimiter.\substr($sPrefix, 6);
}; };
} }
return $sPrefix; return $sPrefix;

View file

@ -947,50 +947,22 @@ class MailClient
/** /**
* @throws \InvalidArgumentException * @throws \InvalidArgumentException
*/ */
public function FolderMove(string $sPrevFolderFullName, string $sNextFolderFullNameInUtf, bool $bSubscribeOnMove = true) : self public function FolderRename(string $sPrevFolderFullName, string $sNewFolderFullName, bool $bSubscribe = true) : self
{
if (!$this->oImapClient->FolderHierarchyDelimiter($sPrevFolderFullName)) {
// TODO: Translate
throw new \MailSo\RuntimeException('Cannot move non-existent folder.');
}
return $this->folderModify($sPrevFolderFullName, $sNextFolderFullNameInUtf, $bSubscribeOnMove);
}
/**
* @throws \InvalidArgumentException
*/
public function FolderRename(string $sPrevFolderFullName, string $sNewTopFolderNameInUtf, bool $bSubscribeOnRename = true) : string
{
$sDelimiter = $this->oImapClient->FolderHierarchyDelimiter($sPrevFolderFullName);
if (!$sDelimiter) {
// TODO: Translate
throw new \MailSo\RuntimeException('Cannot rename non-existent folder.');
}
if (\strlen($sDelimiter) && false !== \strpos($sNewTopFolderNameInUtf, $sDelimiter)) {
// TODO: Translate
throw new \MailSo\RuntimeException('New folder name contains delimiter.');
}
$iLast = \strrpos($sPrevFolderFullName, $sDelimiter);
$sNewFolderFullName = (false === $iLast ? '' : \substr($sPrevFolderFullName, 0, $iLast + 1))
. $sNewTopFolderNameInUtf;
$this->folderModify($sPrevFolderFullName, $sNewFolderFullName, $bSubscribeOnRename);
return $sNewFolderFullName;
}
/**
* @throws \ValueError
* @throws \MailSo\RuntimeException
*/
protected function folderModify(string $sPrevFolderFullName, string $sNewFolderFullName, bool $bSubscribe) : self
{ {
if (!\strlen($sPrevFolderFullName) || !\strlen($sNewFolderFullName)) { if (!\strlen($sPrevFolderFullName) || !\strlen($sNewFolderFullName)) {
throw new \ValueError; throw new \ValueError;
} }
if (!$this->oImapClient->FolderHierarchyDelimiter($sPrevFolderFullName)) {
// TODO: Translate
throw new \MailSo\RuntimeException('Cannot rename non-existent folder.');
}
/*
if (\strlen($sDelimiter) && false !== \strpos($sNewFolderFullName, $sDelimiter)) {
// TODO: Translate
throw new \MailSo\RuntimeException('New folder name contains delimiter.');
}
*/
$oSubscribedFolders = array(); $oSubscribedFolders = array();
if ($bSubscribe) { if ($bSubscribe) {
$oSubscribedFolders = $this->oImapClient->FolderSubscribeList($sPrevFolderFullName, '*'); $oSubscribedFolders = $this->oImapClient->FolderSubscribeList($sPrevFolderFullName, '*');

View file

@ -190,15 +190,15 @@ trait Folders
/** /**
* @throws \MailSo\RuntimeException * @throws \MailSo\RuntimeException
*/ */
public function DoFolderMove() : array public function DoFolderRename() : array
{ {
$this->initMailClientConnection(); $this->initMailClientConnection();
try try
{ {
$this->MailClient()->FolderMove( $sFullName = $this->MailClient()->FolderRename(
$this->GetActionParam('folder', ''), $this->GetActionParam('oldName', ''),
$this->GetActionParam('newFolder', ''), $this->GetActionParam('newName', ''),
!empty($this->GetActionParam('subscribe', 1)) !empty($this->GetActionParam('subscribe', 1))
); );
} }
@ -210,34 +210,6 @@ trait Folders
return $this->TrueResponse(); return $this->TrueResponse();
} }
/**
* @throws \MailSo\RuntimeException
*/
public function DoFolderRename() : array
{
$this->initMailClientConnection();
$sName = $this->GetActionParam('newFolderName', '');
try
{
$sFullName = $this->MailClient()->FolderRename(
$this->GetActionParam('folder', ''),
$sName,
!empty($this->GetActionParam('subscribe', 1))
);
}
catch (\Throwable $oException)
{
throw new ClientException(Notifications::CantRenameFolder, $oException);
}
// FolderInformation(string $sFolderName, int $iPrevUidNext = 0, array $aUids = array())
return $this->DefaultResponse(array(
'name' => $sName,
'fullName' => $sFullName,
));
}
/** /**
* @throws \MailSo\RuntimeException * @throws \MailSo\RuntimeException
*/ */

View file

@ -3,13 +3,31 @@
<h3 data-bind="text: folder()?.fullName"></h3> <h3 data-bind="text: folder()?.fullName"></h3>
</header> </header>
<form id="folderform" class="modal-body form-horizontal" autocomplete="off" spellcheck="false" data-bind="submit: submitForm"> <form id="folderform" class="modal-body form-horizontal" autocomplete="off" spellcheck="false" data-bind="submit: submitForm">
<div class="control-group" data-bind="if: editing">
<label data-i18n="POPUPS_CREATE_FOLDER/LABEL_PARENT"></label>
<select name="parent" data-bind="options: parentFolderSelectList, value: parentFolder,
optionsText: 'name', optionsValue: 'id', optionsAfterRender: defaultOptionsAfterRender"></select>
</div>
<div class="control-group" data-bind="if: editing">
<label data-i18n="GLOBAL/NAME"></label>
<input name="folder" type="text"
autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"
required="" data-bind="textInput: name">
</div>
<div data-bind="with: folder"> <div data-bind="with: folder">
<div class="control-group" data-bind="if: editing"> <!-- ko if: canBeSubscribed -->
<label data-i18n="GLOBAL/NAME"></label> <div class="control-group">
<input name="folder" type="text" <label></label>
autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" <div data-bind="component: {
required="" data-bind="textInput: nameForEdit"> name: 'Checkbox',
params: {
name: 'subscribe',
label: 'SETTINGS_FOLDERS/BUTTON_SUBSCRIBE',
value: isSubscribed
}
}"></div>
</div> </div>
<!-- /ko -->
<div class="control-group"> <div class="control-group">
<label data-i18n="POPUPS_FILTER/SELECT_FIELD_SIZE"></label> <label data-i18n="POPUPS_FILTER/SELECT_FIELD_SIZE"></label>
<span data-bind="text: friendlySize"></span> <span data-bind="text: friendlySize"></span>
@ -19,16 +37,6 @@
<span data-bind="text: unreadEmails"></span> <span data-bind="text: unreadEmails"></span>
/ <span style="margin:0" data-bind="text: totalEmails"></span> / <span style="margin:0" data-bind="text: totalEmails"></span>
</div> </div>
<!-- ko if: canBeSubscribed -->
<div class="control-group" data-bind="component: {
name: 'Checkbox',
params: {
name: 'subscribe',
label: 'SETTINGS_FOLDERS/BUTTON_SUBSCRIBE',
value: isSubscribed
}
}"></div>
<!-- /ko -->
</div> </div>
<div class="control-group" data-bind="if: ACLAllowed"> <div class="control-group" data-bind="if: ACLAllowed">
<label>ACL</label> <label>ACL</label>