一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
244 行
8.0 KiB
TypeScript
244 行
8.0 KiB
TypeScript
import Setting from '../../models/Setting';
|
|
import eventManager from '../../eventManager';
|
|
import { notesSortOrderFieldArray, setNotesSortOrder } from './notesSortOrderUtils';
|
|
import { parseNotesParent } from '../../reducer';
|
|
|
|
const SUFFIX_FIELD = '$field';
|
|
const SUFFIX_REVERSE = '$reverse';
|
|
|
|
export interface SortOrder {
|
|
field: string;
|
|
reverse: boolean;
|
|
}
|
|
|
|
interface FolderState {
|
|
notesParentType: string;
|
|
selectedFolderId: string;
|
|
selectedSmartFilterId: string;
|
|
}
|
|
|
|
interface SortOrderPool {
|
|
[key: string]: string | boolean;
|
|
}
|
|
|
|
export default class PerFolderSortOrderService {
|
|
|
|
// To support a custom sort order in "all notebooks", previousFolderId
|
|
// can also be a smart filter ID.
|
|
private static previousFolderId: string = null;
|
|
private static folderState: FolderState = { notesParentType: '', selectedFolderId: '', selectedSmartFilterId: '' };
|
|
// Since perFolderSortOrders and sharedSortOrder is persisted using Setting,
|
|
// their structures are not nested.
|
|
private static perFolderSortOrders: SortOrderPool = null;
|
|
private static sharedSortOrder: SortOrder & SortOrderPool = {
|
|
field: 'user_updated_time',
|
|
reverse: true,
|
|
user_updated_time: true,
|
|
user_created_time: true,
|
|
title: false,
|
|
order: false,
|
|
};
|
|
|
|
public static initialize() {
|
|
this.loadPerFolderSortOrders();
|
|
this.loadSharedSortOrder();
|
|
eventManager.appStateOn('notesParentType', this.onFolderSelectionMayChange.bind(this, 'notesParentType'));
|
|
eventManager.appStateOn('selectedFolderId', this.onFolderSelectionMayChange.bind(this, 'selectedFolderId'));
|
|
eventManager.appStateOn('selectedSmartFilterId', this.onFolderSelectionMayChange.bind(this, 'selectedSmartFilterId'));
|
|
// notesParent persists the last view type (including smart filters like All Notes),
|
|
// unlike activeFolderId which only tracks folder selections.
|
|
const notesParent = parseNotesParent(Setting.value('notesParent'), Setting.value('activeFolderId'));
|
|
if (notesParent.type === 'Folder' || notesParent.type === 'SmartFilter') {
|
|
this.previousFolderId = notesParent.selectedItemId;
|
|
} else {
|
|
this.previousFolderId = null;
|
|
}
|
|
}
|
|
|
|
public static isSet(folderId: string): boolean {
|
|
return folderId && this.perFolderSortOrders && this.perFolderSortOrders.hasOwnProperty(folderId + SUFFIX_FIELD);
|
|
}
|
|
|
|
public static get(folderId: string): SortOrder {
|
|
if (folderId && this.perFolderSortOrders) {
|
|
const field = this.perFolderSortOrders[folderId + SUFFIX_FIELD] as string;
|
|
const reverse = this.perFolderSortOrders[folderId + SUFFIX_REVERSE] as boolean;
|
|
if (field) return { field, reverse };
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
public static set(folderId?: string, own?: boolean) {
|
|
let targetId = folderId;
|
|
const selectedId = this.getSelectedFolderId();
|
|
if (!targetId) {
|
|
targetId = selectedId; // default: selected folder
|
|
if (!targetId) return;
|
|
}
|
|
const targetOwn = this.isSet(targetId);
|
|
let newOwn;
|
|
if (typeof own === 'undefined') {
|
|
newOwn = !targetOwn; // default: toggling
|
|
} else {
|
|
newOwn = !!own;
|
|
if (newOwn === targetOwn) return;
|
|
}
|
|
if (newOwn) {
|
|
let field: string, reverse: boolean;
|
|
if (!this.isSet(selectedId)) {
|
|
field = Setting.value('notes.sortOrder.field');
|
|
reverse = Setting.value('notes.sortOrder.reverse');
|
|
} else {
|
|
field = this.sharedSortOrder.field;
|
|
if (Setting.value('notes.perFieldReversalEnabled')) {
|
|
reverse = this.sharedSortOrder[field] as boolean;
|
|
} else {
|
|
reverse = this.sharedSortOrder.reverse;
|
|
}
|
|
}
|
|
this.setSharedSortOrder(field, reverse);
|
|
PerFolderSortOrderService.setPerFolderSortOrder(targetId, field, reverse);
|
|
} else {
|
|
PerFolderSortOrderService.deletePerFolderSortOrder(targetId);
|
|
if (targetId === selectedId) {
|
|
this.loadSharedSortOrder();
|
|
|
|
const field = this.sharedSortOrder.field;
|
|
let reverse: boolean;
|
|
if (Setting.value('notes.perFieldReversalEnabled')) {
|
|
reverse = this.sharedSortOrder[field] as boolean;
|
|
} else {
|
|
reverse = this.sharedSortOrder.reverse;
|
|
}
|
|
setNotesSortOrder(field, reverse);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static onSortOrderChange(folderId: string) {
|
|
const field = Setting.value('notes.sortOrder.field');
|
|
const reverse = Setting.value('notes.sortOrder.reverse');
|
|
if (!folderId) {
|
|
this.setSharedSortOrder(field, reverse);
|
|
return;
|
|
}
|
|
if (this.isSet(folderId)) {
|
|
// Folder has per-folder sort enabled, update its entry
|
|
this.setPerFolderSortOrder(folderId, field, reverse);
|
|
} else {
|
|
// Folder uses shared sort, update the shared sort
|
|
this.setSharedSortOrder(field, reverse);
|
|
}
|
|
}
|
|
|
|
private static onFolderSelectionMayChange(cause: string, event: { value: string }) {
|
|
if (cause !== 'notesParentType' && cause !== 'selectedFolderId' && cause !== 'selectedSmartFilterId') {
|
|
return;
|
|
}
|
|
this.folderState[cause] = event.value;
|
|
const selectedId = this.getSelectedFolderId();
|
|
if (this.previousFolderId === selectedId) return;
|
|
const field: string = Setting.value('notes.sortOrder.field');
|
|
const reverse: boolean = Setting.value('notes.sortOrder.reverse');
|
|
let previousFolderHasPerFolderSortOrder = false;
|
|
if (this.previousFolderId !== null) {
|
|
previousFolderHasPerFolderSortOrder = this.isSet(this.previousFolderId);
|
|
if (previousFolderHasPerFolderSortOrder) {
|
|
this.setPerFolderSortOrder(this.previousFolderId, field, reverse);
|
|
} else {
|
|
this.setSharedSortOrder(field, reverse);
|
|
}
|
|
}
|
|
this.previousFolderId = selectedId;
|
|
let next: SortOrder;
|
|
if (this.isSet(selectedId)) {
|
|
next = this.get(selectedId);
|
|
} else if (previousFolderHasPerFolderSortOrder) {
|
|
next = this.sharedSortOrder;
|
|
} else {
|
|
return;
|
|
}
|
|
if (Setting.value('notes.perFolderSortOrderEnabled')) {
|
|
if (next.field !== field || next.reverse !== reverse) {
|
|
setNotesSortOrder(next.field, next.reverse);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static getSelectedFolderId(): string {
|
|
if (this.folderState.notesParentType === 'Folder') {
|
|
return this.folderState.selectedFolderId;
|
|
} else if (this.folderState.notesParentType === 'SmartFilter') {
|
|
return this.folderState.selectedSmartFilterId;
|
|
} else {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
private static loadPerFolderSortOrders() {
|
|
this.perFolderSortOrders = { ...Setting.value('notes.perFolderSortOrders') };
|
|
}
|
|
|
|
private static loadSharedSortOrder() {
|
|
const validFields = notesSortOrderFieldArray();
|
|
const value = Setting.value('notes.sharedSortOrder') as { field?: string; reverse?: boolean } & Record<string, string | boolean>;
|
|
for (const key in this.sharedSortOrder) {
|
|
if (value.hasOwnProperty(key)) {
|
|
if (key !== 'field' || validFields.includes(value.field)) {
|
|
this.sharedSortOrder[key] = value[key];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static setPerFolderSortOrder(folderId: string, field: string, reverse: boolean) {
|
|
const old = this.get(folderId);
|
|
let dirty = false;
|
|
if (!(old?.field === field)) {
|
|
this.perFolderSortOrders[folderId + SUFFIX_FIELD] = field;
|
|
dirty = true;
|
|
}
|
|
if (!(old?.reverse === reverse)) {
|
|
this.perFolderSortOrders[folderId + SUFFIX_REVERSE] = reverse;
|
|
dirty = true;
|
|
}
|
|
if (dirty) {
|
|
Setting.setValue('notes.perFolderSortOrders', { ...this.perFolderSortOrders });
|
|
}
|
|
}
|
|
|
|
private static deletePerFolderSortOrder(folderId: string) {
|
|
let dirty = false;
|
|
if (this.perFolderSortOrders.hasOwnProperty(folderId + SUFFIX_FIELD)) {
|
|
delete this.perFolderSortOrders[folderId + SUFFIX_FIELD];
|
|
dirty = true;
|
|
}
|
|
if (this.perFolderSortOrders.hasOwnProperty(folderId + SUFFIX_REVERSE)) {
|
|
delete this.perFolderSortOrders[folderId + SUFFIX_REVERSE];
|
|
dirty = true;
|
|
}
|
|
if (dirty) {
|
|
Setting.setValue('notes.perFolderSortOrders', { ...this.perFolderSortOrders });
|
|
}
|
|
}
|
|
|
|
private static setSharedSortOrder(field: string, reverse: boolean) {
|
|
let dirty = false;
|
|
if (this.sharedSortOrder.field !== field) {
|
|
this.sharedSortOrder.field = field;
|
|
dirty = true;
|
|
}
|
|
if (this.sharedSortOrder.reverse !== reverse) {
|
|
this.sharedSortOrder.reverse = reverse;
|
|
dirty = true;
|
|
}
|
|
if (this.sharedSortOrder[field] !== reverse) {
|
|
this.sharedSortOrder[field] = reverse;
|
|
dirty = true;
|
|
}
|
|
if (dirty) {
|
|
Setting.setValue('notes.sharedSortOrder', { ...this.sharedSortOrder });
|
|
}
|
|
}
|
|
}
|