feat: Initial clean release of CHM
一些检查失败了
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
一些检查失败了
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
这个提交包含在:
@@ -0,0 +1,167 @@
|
||||
import PerFolderSortOrderService from './PerFolderSortOrderService';
|
||||
import { setNotesSortOrder } from './notesSortOrderUtils';
|
||||
import Setting from '../../models/Setting';
|
||||
import { defaultState, serializeNotesParent, State } from '../../reducer';
|
||||
import eventManager from '../../eventManager';
|
||||
import { shimInit } from '../../shim-init-node';
|
||||
import { ALL_NOTES_FILTER_ID } from '../../reserved-ids';
|
||||
|
||||
const folderId1 = 'aa012345678901234567890123456789';
|
||||
const folderId2 = 'bb012345678901234567890123456789';
|
||||
|
||||
let appState: State = null;
|
||||
const updateAppState = (update: Partial<State>) => {
|
||||
appState = { ...appState, ...update };
|
||||
eventManager.appStateEmit(appState);
|
||||
};
|
||||
|
||||
const switchToFolder = (id: string) => {
|
||||
updateAppState({
|
||||
notesParentType: 'Folder',
|
||||
selectedFolderId: id,
|
||||
});
|
||||
};
|
||||
|
||||
const switchToAllNotes = () => {
|
||||
updateAppState({
|
||||
notesParentType: 'SmartFilter',
|
||||
selectedSmartFilterId: ALL_NOTES_FILTER_ID,
|
||||
});
|
||||
};
|
||||
|
||||
describe('PerFolderSortOrderService', () => {
|
||||
|
||||
beforeAll(async () => {
|
||||
shimInit();
|
||||
Setting.autoSaveEnabled = false;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
PerFolderSortOrderService.initialize();
|
||||
Setting.setValue('notes.perFolderSortOrderEnabled', true);
|
||||
updateAppState(defaultState);
|
||||
switchToFolder(folderId1);
|
||||
});
|
||||
afterEach(() => {
|
||||
Setting.setValue('notes.perFolderSortOrders', {});
|
||||
});
|
||||
|
||||
test('get(), isSet() and set()', async () => {
|
||||
// Clear all per-folder sort order
|
||||
expect(PerFolderSortOrderService.isSet(folderId1)).toBe(false);
|
||||
expect(PerFolderSortOrderService.isSet(folderId2)).toBe(false);
|
||||
|
||||
// Set shared sort order
|
||||
setNotesSortOrder('user_created_time', false);
|
||||
expect(Setting.value('notes.sortOrder.field')).toBe('user_created_time');
|
||||
expect(Setting.value('notes.sortOrder.reverse')).toBe(false);
|
||||
|
||||
// Manipulate per-folder sort order
|
||||
PerFolderSortOrderService.set(folderId1, true);
|
||||
expect(PerFolderSortOrderService.isSet(folderId1)).toBe(true);
|
||||
PerFolderSortOrderService.set(folderId1, false);
|
||||
expect(PerFolderSortOrderService.isSet(folderId1)).toBe(false);
|
||||
PerFolderSortOrderService.set(folderId1);
|
||||
expect(PerFolderSortOrderService.isSet(folderId1)).toBe(true);
|
||||
|
||||
// Get per-folder sort order from a folder with per-folder sort order
|
||||
expect(PerFolderSortOrderService.get(folderId1)).toBeDefined();
|
||||
|
||||
// Folder without per-folder sort order has no per-folder sort order
|
||||
expect(PerFolderSortOrderService.get(folderId2)).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should allow specifying a sort order specific to a folder', () => {
|
||||
switchToFolder(folderId1);
|
||||
|
||||
expect(PerFolderSortOrderService.isSet(folderId1)).toBe(false);
|
||||
expect(PerFolderSortOrderService.isSet(folderId2)).toBe(false);
|
||||
|
||||
setNotesSortOrder('user_created_time', false);
|
||||
expect(Setting.value('notes.sortOrder.field')).toBe('user_created_time');
|
||||
expect(Setting.value('notes.sortOrder.reverse')).toBe(false);
|
||||
|
||||
// Folder 2 should use the shared sort order.
|
||||
switchToFolder(folderId2);
|
||||
expect(Setting.value('notes.sortOrder.field')).toBe('user_created_time');
|
||||
expect(Setting.value('notes.sortOrder.reverse')).toBe(false);
|
||||
|
||||
// If changing the per-folder sort order for folder 1, folder 2 should continue
|
||||
// to use the shared sort order.
|
||||
switchToFolder(folderId1);
|
||||
PerFolderSortOrderService.set(folderId1, true);
|
||||
|
||||
setNotesSortOrder('title', true);
|
||||
expect(Setting.value('notes.sortOrder.field')).toBe('title');
|
||||
expect(Setting.value('notes.sortOrder.reverse')).toBe(true);
|
||||
|
||||
switchToFolder(folderId2);
|
||||
expect(Setting.value('notes.sortOrder.field')).toBe('user_created_time');
|
||||
expect(Setting.value('notes.sortOrder.reverse')).toBe(false);
|
||||
});
|
||||
|
||||
test('should allow setting a sort order specific to All Notes', () => {
|
||||
switchToFolder(folderId1);
|
||||
|
||||
expect(PerFolderSortOrderService.isSet(ALL_NOTES_FILTER_ID)).toBe(false);
|
||||
expect(PerFolderSortOrderService.isSet(folderId1)).toBe(false);
|
||||
|
||||
// Set default shared sort order
|
||||
setNotesSortOrder('user_created_time', false);
|
||||
expect(Setting.value('notes.sortOrder.field')).toBe('user_created_time');
|
||||
expect(Setting.value('notes.sortOrder.reverse')).toBe(false);
|
||||
|
||||
// Switching to all notes should not change the default sort order.
|
||||
switchToAllNotes();
|
||||
|
||||
expect(Setting.value('notes.sortOrder.field')).toBe('user_created_time');
|
||||
expect(Setting.value('notes.sortOrder.reverse')).toBe(false);
|
||||
|
||||
// It should be possible to enable per-folder sorting for all notes.
|
||||
PerFolderSortOrderService.set(ALL_NOTES_FILTER_ID, true);
|
||||
expect(PerFolderSortOrderService.isSet(ALL_NOTES_FILTER_ID)).toBe(true);
|
||||
|
||||
setNotesSortOrder('user_updated_time', true);
|
||||
|
||||
// Per-folder sorting should be respected for all notes
|
||||
switchToFolder(folderId1);
|
||||
|
||||
expect(Setting.value('notes.sortOrder.field')).toBe('user_created_time');
|
||||
expect(Setting.value('notes.sortOrder.reverse')).toBe(false);
|
||||
|
||||
// Shared sort order should be overriden by per-folder sorting
|
||||
setNotesSortOrder('title', false);
|
||||
|
||||
switchToAllNotes();
|
||||
|
||||
expect(Setting.value('notes.sortOrder.field')).toBe('user_updated_time');
|
||||
expect(Setting.value('notes.sortOrder.reverse')).toBe(true);
|
||||
});
|
||||
|
||||
test('should not let All Notes sort bleed into shared sort order on relaunch', () => {
|
||||
switchToFolder(folderId1);
|
||||
setNotesSortOrder('title', false);
|
||||
|
||||
switchToAllNotes();
|
||||
PerFolderSortOrderService.set(ALL_NOTES_FILTER_ID, true);
|
||||
setNotesSortOrder('user_updated_time', true);
|
||||
|
||||
expect(PerFolderSortOrderService.isSet(ALL_NOTES_FILTER_ID)).toBe(true);
|
||||
expect(Setting.value('notes.sortOrder.field')).toBe('user_updated_time');
|
||||
|
||||
// Simulate relaunch: activeFolderId holds the last notebook, notesParent holds All Notes
|
||||
Setting.setValue('activeFolderId', folderId1);
|
||||
Setting.setValue('notesParent', serializeNotesParent({ type: 'SmartFilter', selectedItemId: ALL_NOTES_FILTER_ID }));
|
||||
|
||||
PerFolderSortOrderService.initialize();
|
||||
Setting.setValue('notes.perFolderSortOrderEnabled', true);
|
||||
|
||||
updateAppState(defaultState);
|
||||
switchToAllNotes();
|
||||
|
||||
// Navigating to the notebook must restore shared sort (title), not All Notes' sort
|
||||
switchToFolder(folderId1);
|
||||
expect(Setting.value('notes.sortOrder.field')).toBe('title');
|
||||
expect(Setting.value('notes.sortOrder.reverse')).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,243 @@
|
||||
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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import { notesSortOrderNextField, setNotesSortOrder } from './notesSortOrderUtils';
|
||||
import Setting from '../../models/Setting';
|
||||
import { shimInit } from '../../shim-init-node';
|
||||
|
||||
describe('notesSortOrderUtils', () => {
|
||||
|
||||
beforeAll(() => {
|
||||
shimInit();
|
||||
Setting.autoSaveEnabled = false;
|
||||
});
|
||||
|
||||
// Disabling as the test doesn't seem very useful.
|
||||
|
||||
// it('should always provide the same ordered fields', async () => {
|
||||
// const expected = ['user_updated_time', 'user_created_time', 'title', 'order'];
|
||||
// expect(notesSortOrderFieldArray()).toStrictEqual(expected);
|
||||
// expect(notesSortOrderFieldArray()).toStrictEqual(expected);
|
||||
// });
|
||||
|
||||
it('should provide the next field cyclically', async () => {
|
||||
expect(notesSortOrderNextField('user_updated_time')).toBe('user_created_time');
|
||||
expect(notesSortOrderNextField('order')).toBe('user_updated_time');
|
||||
});
|
||||
|
||||
test('setNoteSortOrder(), when perFieldReversalEnabled is false', async () => {
|
||||
Setting.setValue('notes.perFieldReversalEnabled', false);
|
||||
|
||||
// It should set field and reverse of sort order.
|
||||
setNotesSortOrder('user_created_time', false);
|
||||
expect(Setting.value('notes.sortOrder.field')).toBe('user_created_time');
|
||||
expect(Setting.value('notes.sortOrder.reverse')).toBe(false);
|
||||
setNotesSortOrder('user_updated_time', true);
|
||||
expect(Setting.value('notes.sortOrder.field')).toBe('user_updated_time');
|
||||
expect(Setting.value('notes.sortOrder.reverse')).toBe(true);
|
||||
setNotesSortOrder('title', true);
|
||||
expect(Setting.value('notes.sortOrder.field')).toBe('title');
|
||||
expect(Setting.value('notes.sortOrder.reverse')).toBe(true);
|
||||
|
||||
// It should affect the current field of sort order, if arg1 is undefined.
|
||||
setNotesSortOrder(undefined, false);
|
||||
expect(Setting.value('notes.sortOrder.field')).toBe('title');
|
||||
expect(Setting.value('notes.sortOrder.reverse')).toBe(false);
|
||||
|
||||
// it should only set field of sort order, if arg2 is undefined.
|
||||
setNotesSortOrder('user_updated_time');
|
||||
expect(Setting.value('notes.sortOrder.field')).toBe('user_updated_time');
|
||||
expect(Setting.value('notes.sortOrder.reverse')).toBe(false);
|
||||
|
||||
// It should select the next field, if arg1 and arg2 are undefined.
|
||||
setNotesSortOrder();
|
||||
expect(Setting.value('notes.sortOrder.field')).toBe('user_created_time');
|
||||
expect(Setting.value('notes.sortOrder.reverse')).toBe(false);
|
||||
});
|
||||
|
||||
test('setNoteSortOrder(), when perFieldReversalEnabled is true', async () => {
|
||||
Setting.setValue('notes.perFieldReversalEnabled', true);
|
||||
// It should set field and reverse of sort order.
|
||||
setNotesSortOrder('user_created_time', false);
|
||||
expect(Setting.value('notes.sortOrder.field')).toBe('user_created_time');
|
||||
expect(Setting.value('notes.sortOrder.reverse')).toBe(false);
|
||||
setNotesSortOrder('user_updated_time', true);
|
||||
expect(Setting.value('notes.sortOrder.field')).toBe('user_updated_time');
|
||||
expect(Setting.value('notes.sortOrder.reverse')).toBe(true);
|
||||
setNotesSortOrder('title', true);
|
||||
expect(Setting.value('notes.sortOrder.field')).toBe('title');
|
||||
expect(Setting.value('notes.sortOrder.reverse')).toBe(true);
|
||||
|
||||
// it should affect the current field of sort order, if arg1 is undefined.
|
||||
setNotesSortOrder(undefined, false);
|
||||
expect(Setting.value('notes.sortOrder.field')).toBe('title');
|
||||
expect(Setting.value('notes.sortOrder.reverse')).toBe(false);
|
||||
|
||||
// It should remember a reverse state, if arg2 is undefined.
|
||||
setNotesSortOrder('user_updated_time');
|
||||
expect(Setting.value('notes.sortOrder.field')).toBe('user_updated_time');
|
||||
expect(Setting.value('notes.sortOrder.reverse')).toBe(true);
|
||||
|
||||
// It should select the next field and remember a reverse state, if arg1 and arg2 are undefined.
|
||||
setNotesSortOrder();
|
||||
expect(Setting.value('notes.sortOrder.field')).toBe('user_created_time');
|
||||
expect(Setting.value('notes.sortOrder.reverse')).toBe(false);
|
||||
});
|
||||
|
||||
it('should not accept an invalid field name', async () => {
|
||||
expect(() => setNotesSortOrder('hoge', true)).toThrow();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,67 @@
|
||||
import Setting from '../../models/Setting';
|
||||
|
||||
let fields: string[] = null;
|
||||
let perFieldReverse: { [field: string]: boolean } = null;
|
||||
|
||||
export const notesSortOrderFieldArray = (): string[] => {
|
||||
// The order of the fields is strictly determinate.
|
||||
if (fields === null) {
|
||||
fields = Setting.enumOptionValues('notes.sortOrder.field').sort().reverse();
|
||||
}
|
||||
return fields;
|
||||
};
|
||||
|
||||
export const notesSortOrderNextField = (currentField: string) => {
|
||||
const fields = notesSortOrderFieldArray();
|
||||
const index = fields.indexOf(currentField);
|
||||
if (index < 0) {
|
||||
return currentField;
|
||||
} else {
|
||||
return fields[(index + 1) % fields.length];
|
||||
}
|
||||
};
|
||||
|
||||
export const setNotesSortOrder = (field?: string, reverse?: boolean) => {
|
||||
// field: Sort order's field. undefined means changing a field cyclically.
|
||||
// reverse: whether the sort order is reversed or not. undefined means toggling.
|
||||
let nextField = field;
|
||||
let nextReverse = reverse;
|
||||
const currentField = Setting.value('notes.sortOrder.field');
|
||||
const currentReverse = Setting.value('notes.sortOrder.reverse');
|
||||
const enabled = Setting.value('notes.perFieldReversalEnabled');
|
||||
if (enabled) {
|
||||
if (perFieldReverse === null) {
|
||||
perFieldReverse = { ...Setting.value('notes.perFieldReverse') };
|
||||
}
|
||||
}
|
||||
if (typeof field === 'undefined') {
|
||||
if (typeof reverse === 'undefined') {
|
||||
// If both arguments are undefined, the next field is selected.
|
||||
nextField = notesSortOrderNextField(currentField);
|
||||
} else {
|
||||
nextField = currentField;
|
||||
}
|
||||
}
|
||||
if (typeof reverse === 'undefined') {
|
||||
if (enabled && perFieldReverse.hasOwnProperty(nextField)) {
|
||||
nextReverse = !!perFieldReverse[nextField];
|
||||
} else {
|
||||
nextReverse = currentReverse;
|
||||
}
|
||||
}
|
||||
if (currentField !== nextField) {
|
||||
Setting.setValue('notes.sortOrder.field', nextField);
|
||||
}
|
||||
if (currentReverse !== nextReverse) {
|
||||
Setting.setValue('notes.sortOrder.reverse', nextReverse);
|
||||
}
|
||||
if (enabled) {
|
||||
// nextField is sane here.
|
||||
nextReverse = !!nextReverse;
|
||||
if (perFieldReverse[nextField] !== nextReverse) {
|
||||
perFieldReverse[nextField] = nextReverse;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Partial refactor of old code before rule was applied
|
||||
Setting.setValue('notes.perFieldReverse', { ...perFieldReverse } as any);
|
||||
}
|
||||
}
|
||||
};
|
||||
在新工单中引用
屏蔽一个用户