Skip to content

Commit e44de26

Browse files
committed
Allow to manually reorder "normal" folders
Special folders like INBOX, Sent, etc. are staying at the top of the list.
1 parent ba60aa8 commit e44de26

File tree

5 files changed

+119
-5
lines changed

5 files changed

+119
-5
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
class rcmail_action_settings_folder_reorder extends rcmail_action_settings_folders
4+
{
5+
protected static $mode = self::MODE_AJAX;
6+
7+
/**
8+
* Request handler.
9+
*
10+
* @param array $args Arguments from the previous step(s)
11+
*/
12+
#[Override]
13+
public function run($args = [])
14+
{
15+
$rcmail = rcmail::get_instance();
16+
$list = array_flip(rcube_utils::get_input_value('folderorder', rcube_utils::INPUT_POST, true));
17+
$rcmail->user->save_prefs(['folder_order' => $list]);
18+
19+
$rcmail->output->show_message('successfullysaved', 'confirmation');
20+
$rcmail->output->send();
21+
}
22+
}

program/actions/settings/folders.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,10 @@ public static function folder_tree_element($folders, &$key, &$js_folders)
262262
'id' => $idx,
263263
'class' => trim($data['class'] . ' mailbox'),
264264
];
265+
// Only allow reordering of non-protected folders.
266+
if ($data['protected']) {
267+
$attribs['class'] .= ' protected';
268+
}
265269

266270
if (!isset($data['level'])) {
267271
$data['level'] = 0;

program/js/app.js

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7780,7 +7780,8 @@ function rcube_webmail() {
77807780
// on the list when dragging starts (and stops), this is slow, but
77817781
// I didn't find a method to check droptarget on over event
77827782
accept: function (node) {
7783-
if (!node.is('.mailbox')) {
7783+
// Break on .is-being-sorted to enable sorting.
7784+
if (!node.is('.mailbox') || node.is('.is-being-sorted')) {
77847785
return false;
77857786
}
77867787

@@ -7798,8 +7799,49 @@ function rcube_webmail() {
77987799
dest = ref.folder_id2name(this.id);
77997800

78007801
ref.subscription_move_folder(source, dest);
7802+
ref.make_folder_lists_sortable();
78017803
},
78027804
});
7805+
7806+
this.make_folder_lists_sortable();
7807+
};
7808+
7809+
this.make_folder_lists_sortable = () => {
7810+
var sortableHandle = $('<div>').addClass('sortable-handle');
7811+
// Destroy and re-create all sortable lists because this gets called in scenarios, in which new lists might
7812+
// exist (e.g. if a folder was moved into another one that previously didn't have child-folders).
7813+
$('ul.ui-sortable').sortable('destroy');
7814+
// (Re-)Add handle icons to every sortable folder.
7815+
$('.sortable-handle').remove();
7816+
$('li.mailbox:not(.protected) > .custom-switch', this.subscription_list.container).prepend(sortableHandle);
7817+
// (Re-)create the sorting.
7818+
$(this.subscription_list.container).parent().find('ul').each((_i, el) => {
7819+
$(el).sortable({
7820+
axis: 'y',
7821+
containment: 'parent',
7822+
items: '> li.mailbox:not(.protected)',
7823+
handle: '.sortable-handle',
7824+
update: () => this.save_reordered_folder_list(),
7825+
// Add/remove a marker to disable dropping on other elements during sorting (see accept() of droppable).
7826+
start: (_ev, ui) => ui.item.addClass('is-being-sorted'),
7827+
stop: (_ev, ui) => ui.item.removeClass('is-being-sorted'),
7828+
});
7829+
});
7830+
};
7831+
7832+
this.save_reordered_folder_list = () => {
7833+
const mainList = ref.subscription_list.container.sortable('toArray');
7834+
const subLists = ref.subscription_list.container.find('.ui-sortable').map((i, elem) => ({
7835+
parentId: elem.parentElement.id,
7836+
elems: $(elem).sortable('toArray'),
7837+
})).toArray();
7838+
// Sort sub-lists after their their parent element, so the sorting for the settings page doesn't get confused
7839+
// (which will hook child-folders onto wrong parents if we don't do this).
7840+
subLists.forEach((subList) => {
7841+
mainList.splice(mainList.indexOf(subList.parentId) + 1, 0, ...subList.elems);
7842+
});
7843+
params = mainList.map((e) => e.replace(/^rcmli/, 'folderorder[]=')).join('&');
7844+
this.http_post('folder-reorder', params, this.display_message('', 'loading'));
78037845
};
78047846

78057847
this.folder_id2name = function (id) {
@@ -7855,7 +7897,7 @@ function rcube_webmail() {
78557897
};
78567898

78577899
// Add folder row to the table and initialize it
7858-
this.add_folder_row = function (id, name, display_name, is_protected, subscribed, class_name, refrow, subfolders) {
7900+
this.add_folder_row = function (id, name, display_name, is_protected, subscribed, class_name, refrow, subfolders, insert_before_elem) {
78597901
if (!this.gui_objects.subscriptionlist) {
78607902
return false;
78617903
}
@@ -7993,7 +8035,11 @@ function rcube_webmail() {
79938035
}
79948036
}
79958037

7996-
if (parent && n == parent) {
8038+
if (insert_before_elem && $(insert_before_elem).parents('li')[0] === parent) {
8039+
// In this case we theoretically could have skipped the sorting above, but trying to do that resulted in
8040+
// strange side effects, so I kept the code in.
8041+
$(insert_before_elem).before(row);
8042+
} else if (parent && n == parent) {
79978043
$('ul', parent).first().append(row);
79988044
} else {
79998045
while (p = $(n).parent().parent().get(0)) {
@@ -8034,6 +8080,8 @@ function rcube_webmail() {
80348080
this.triggerEvent('clonerow', { row: row, id: id });
80358081
}
80368082

8083+
this.make_folder_lists_sortable();
8084+
80378085
return row;
80388086
};
80398087

@@ -8082,6 +8130,12 @@ function rcube_webmail() {
80828130
delete ref.env.subscriptionrows[fname];
80838131
});
80848132

8133+
if (this.env.folder_ordered_manually) {
8134+
// We need to store this information now, because it's not available anymore after removing the row from
8135+
// the DOM.
8136+
next_sibling = row.nextElementSibling;
8137+
}
8138+
80858139
// get row off the list
80868140
row = $(row).detach();
80878141

@@ -8093,7 +8147,11 @@ function rcube_webmail() {
80938147
}
80948148

80958149
// move the existing table row
8096-
this.add_folder_row(id, name, display_name, is_protected, subscribed, class_name, row, subfolders);
8150+
this.add_folder_row(id, name, display_name, is_protected, subscribed, class_name, row, subfolders, next_sibling);
8151+
8152+
if (this.env.folder_ordered_manually) {
8153+
this.save_reordered_folder_list();
8154+
}
80978155
};
80988156

80998157
// remove the table row of a specific mailbox from the table

program/lib/Roundcube/rcube_imap.php

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4401,9 +4401,27 @@ public function sort_folder_list($a_folders, $skip_special = false)
44014401
// sort folders
44024402
// asort($folders, SORT_LOCALE_STRING) is not properly sorting case sensitive names
44034403
uasort($folders, [$this, 'sort_folder_comparator']);
4404-
44054404
$folders = array_keys($folders);
44064405

4406+
$prefs = rcube::get_instance()->user->get_prefs();
4407+
if (isset($prefs['folder_order'])) {
4408+
$folder_order = $prefs['folder_order'];
4409+
$new_list = [];
4410+
foreach ($folders as $folder_name) {
4411+
if (!is_string($folder_name)) {
4412+
continue;
4413+
}
4414+
$folder_id = rcube_utils::html_identifier($folder_name, true);
4415+
// If the folder doesn't have a position, e.g. because it wasn't yet part of a list that has been
4416+
// reordered, just append it to the end (but keep the original sorting order within the "un-ordered"
4417+
// folders).
4418+
$index = $folder_order[$folder_id] ?? 100000 + count($new_list);
4419+
$new_list[$index] = $folder_name;
4420+
}
4421+
ksort($new_list);
4422+
$folders = array_values($new_list);
4423+
}
4424+
44074425
if ($skip_special || empty($folders)) {
44084426
return $folders;
44094427
}

skins/elastic/styles/widgets/lists.less

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,3 +1082,15 @@ html.touch {
10821082
padding-right: 2.5rem;
10831083
}
10841084
}
1085+
1086+
.sortable-handle {
1087+
display: inline-block;
1088+
1089+
&::before {
1090+
font-family: 'Icons';
1091+
font-style: normal;
1092+
.font-icon-solid(@fa-var-arrows-alt-v);
1093+
padding-right: 0.2rem;
1094+
}
1095+
1096+
}

0 commit comments

Comments
 (0)