mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-21 09:38:01 +02:00
Desktop: Sort Order Buttons and Per-Notebook Sort Order (#5437)
This commit is contained in:
parent
e4d5e9cefb
commit
f495db1391
@ -333,6 +333,15 @@ packages/app-desktop/gui/MainScreen/commands/toggleLayoutMoveMode.js.map
|
|||||||
packages/app-desktop/gui/MainScreen/commands/toggleNoteList.d.ts
|
packages/app-desktop/gui/MainScreen/commands/toggleNoteList.d.ts
|
||||||
packages/app-desktop/gui/MainScreen/commands/toggleNoteList.js
|
packages/app-desktop/gui/MainScreen/commands/toggleNoteList.js
|
||||||
packages/app-desktop/gui/MainScreen/commands/toggleNoteList.js.map
|
packages/app-desktop/gui/MainScreen/commands/toggleNoteList.js.map
|
||||||
|
packages/app-desktop/gui/MainScreen/commands/toggleNotesSortOrderField.d.ts
|
||||||
|
packages/app-desktop/gui/MainScreen/commands/toggleNotesSortOrderField.js
|
||||||
|
packages/app-desktop/gui/MainScreen/commands/toggleNotesSortOrderField.js.map
|
||||||
|
packages/app-desktop/gui/MainScreen/commands/toggleNotesSortOrderReverse.d.ts
|
||||||
|
packages/app-desktop/gui/MainScreen/commands/toggleNotesSortOrderReverse.js
|
||||||
|
packages/app-desktop/gui/MainScreen/commands/toggleNotesSortOrderReverse.js.map
|
||||||
|
packages/app-desktop/gui/MainScreen/commands/togglePerFolderSortOrder.d.ts
|
||||||
|
packages/app-desktop/gui/MainScreen/commands/togglePerFolderSortOrder.js
|
||||||
|
packages/app-desktop/gui/MainScreen/commands/togglePerFolderSortOrder.js.map
|
||||||
packages/app-desktop/gui/MainScreen/commands/toggleSideBar.d.ts
|
packages/app-desktop/gui/MainScreen/commands/toggleSideBar.d.ts
|
||||||
packages/app-desktop/gui/MainScreen/commands/toggleSideBar.js
|
packages/app-desktop/gui/MainScreen/commands/toggleSideBar.js
|
||||||
packages/app-desktop/gui/MainScreen/commands/toggleSideBar.js.map
|
packages/app-desktop/gui/MainScreen/commands/toggleSideBar.js.map
|
||||||
@ -735,6 +744,18 @@ packages/app-desktop/services/plugins/hooks/useViewIsReady.js.map
|
|||||||
packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.d.ts
|
packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.d.ts
|
||||||
packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.js
|
packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.js
|
||||||
packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.js.map
|
packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.js.map
|
||||||
|
packages/app-desktop/services/sortOrder/PerFolderSortOrderService.d.ts
|
||||||
|
packages/app-desktop/services/sortOrder/PerFolderSortOrderService.js
|
||||||
|
packages/app-desktop/services/sortOrder/PerFolderSortOrderService.js.map
|
||||||
|
packages/app-desktop/services/sortOrder/PerFolderSortOrderService.test.d.ts
|
||||||
|
packages/app-desktop/services/sortOrder/PerFolderSortOrderService.test.js
|
||||||
|
packages/app-desktop/services/sortOrder/PerFolderSortOrderService.test.js.map
|
||||||
|
packages/app-desktop/services/sortOrder/notesSortOrderUtils.d.ts
|
||||||
|
packages/app-desktop/services/sortOrder/notesSortOrderUtils.js
|
||||||
|
packages/app-desktop/services/sortOrder/notesSortOrderUtils.js.map
|
||||||
|
packages/app-desktop/services/sortOrder/notesSortOrderUtils.test.d.ts
|
||||||
|
packages/app-desktop/services/sortOrder/notesSortOrderUtils.test.js
|
||||||
|
packages/app-desktop/services/sortOrder/notesSortOrderUtils.test.js.map
|
||||||
packages/app-desktop/services/share/invitationRespond.d.ts
|
packages/app-desktop/services/share/invitationRespond.d.ts
|
||||||
packages/app-desktop/services/share/invitationRespond.js
|
packages/app-desktop/services/share/invitationRespond.js
|
||||||
packages/app-desktop/services/share/invitationRespond.js.map
|
packages/app-desktop/services/share/invitationRespond.js.map
|
||||||
|
21
.gitignore
vendored
21
.gitignore
vendored
@ -316,6 +316,15 @@ packages/app-desktop/gui/MainScreen/commands/toggleLayoutMoveMode.js.map
|
|||||||
packages/app-desktop/gui/MainScreen/commands/toggleNoteList.d.ts
|
packages/app-desktop/gui/MainScreen/commands/toggleNoteList.d.ts
|
||||||
packages/app-desktop/gui/MainScreen/commands/toggleNoteList.js
|
packages/app-desktop/gui/MainScreen/commands/toggleNoteList.js
|
||||||
packages/app-desktop/gui/MainScreen/commands/toggleNoteList.js.map
|
packages/app-desktop/gui/MainScreen/commands/toggleNoteList.js.map
|
||||||
|
packages/app-desktop/gui/MainScreen/commands/toggleNotesSortOrderField.d.ts
|
||||||
|
packages/app-desktop/gui/MainScreen/commands/toggleNotesSortOrderField.js
|
||||||
|
packages/app-desktop/gui/MainScreen/commands/toggleNotesSortOrderField.js.map
|
||||||
|
packages/app-desktop/gui/MainScreen/commands/toggleNotesSortOrderReverse.d.ts
|
||||||
|
packages/app-desktop/gui/MainScreen/commands/toggleNotesSortOrderReverse.js
|
||||||
|
packages/app-desktop/gui/MainScreen/commands/toggleNotesSortOrderReverse.js.map
|
||||||
|
packages/app-desktop/gui/MainScreen/commands/togglePerFolderSortOrder.d.ts
|
||||||
|
packages/app-desktop/gui/MainScreen/commands/togglePerFolderSortOrder.js
|
||||||
|
packages/app-desktop/gui/MainScreen/commands/togglePerFolderSortOrder.js.map
|
||||||
packages/app-desktop/gui/MainScreen/commands/toggleSideBar.d.ts
|
packages/app-desktop/gui/MainScreen/commands/toggleSideBar.d.ts
|
||||||
packages/app-desktop/gui/MainScreen/commands/toggleSideBar.js
|
packages/app-desktop/gui/MainScreen/commands/toggleSideBar.js
|
||||||
packages/app-desktop/gui/MainScreen/commands/toggleSideBar.js.map
|
packages/app-desktop/gui/MainScreen/commands/toggleSideBar.js.map
|
||||||
@ -718,6 +727,18 @@ packages/app-desktop/services/plugins/hooks/useViewIsReady.js.map
|
|||||||
packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.d.ts
|
packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.d.ts
|
||||||
packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.js
|
packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.js
|
||||||
packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.js.map
|
packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.js.map
|
||||||
|
packages/app-desktop/services/sortOrder/PerFolderSortOrderService.d.ts
|
||||||
|
packages/app-desktop/services/sortOrder/PerFolderSortOrderService.js
|
||||||
|
packages/app-desktop/services/sortOrder/PerFolderSortOrderService.js.map
|
||||||
|
packages/app-desktop/services/sortOrder/PerFolderSortOrderService.test.d.ts
|
||||||
|
packages/app-desktop/services/sortOrder/PerFolderSortOrderService.test.js
|
||||||
|
packages/app-desktop/services/sortOrder/PerFolderSortOrderService.test.js.map
|
||||||
|
packages/app-desktop/services/sortOrder/notesSortOrderUtils.d.ts
|
||||||
|
packages/app-desktop/services/sortOrder/notesSortOrderUtils.js
|
||||||
|
packages/app-desktop/services/sortOrder/notesSortOrderUtils.js.map
|
||||||
|
packages/app-desktop/services/sortOrder/notesSortOrderUtils.test.d.ts
|
||||||
|
packages/app-desktop/services/sortOrder/notesSortOrderUtils.test.js
|
||||||
|
packages/app-desktop/services/sortOrder/notesSortOrderUtils.test.js.map
|
||||||
packages/app-desktop/services/share/invitationRespond.d.ts
|
packages/app-desktop/services/share/invitationRespond.d.ts
|
||||||
packages/app-desktop/services/share/invitationRespond.js
|
packages/app-desktop/services/share/invitationRespond.js
|
||||||
packages/app-desktop/services/share/invitationRespond.js.map
|
packages/app-desktop/services/share/invitationRespond.js.map
|
||||||
|
@ -58,6 +58,7 @@ const commands = mainScreenCommands
|
|||||||
const globalCommands = appCommands.concat(libCommands);
|
const globalCommands = appCommands.concat(libCommands);
|
||||||
|
|
||||||
import editorCommandDeclarations from './gui/NoteEditor/editorCommandDeclarations';
|
import editorCommandDeclarations from './gui/NoteEditor/editorCommandDeclarations';
|
||||||
|
import PerFolderSortOrderService from './services/sortOrder/PerFolderSortOrderService';
|
||||||
import ShareService from '@joplin/lib/services/share/ShareService';
|
import ShareService from '@joplin/lib/services/share/ShareService';
|
||||||
import checkForUpdates from './checkForUpdates';
|
import checkForUpdates from './checkForUpdates';
|
||||||
import { AppState } from './app.reducer';
|
import { AppState } from './app.reducer';
|
||||||
@ -388,6 +389,8 @@ class Application extends BaseApplication {
|
|||||||
|
|
||||||
this.initRedux();
|
this.initRedux();
|
||||||
|
|
||||||
|
PerFolderSortOrderService.initialize();
|
||||||
|
|
||||||
CommandService.instance().initialize(this.store(), Setting.value('env') == 'dev', stateToWhenClauseContext);
|
CommandService.instance().initialize(this.store(), Setting.value('env') == 'dev', stateToWhenClauseContext);
|
||||||
|
|
||||||
for (const command of commands) {
|
for (const command of commands) {
|
||||||
|
@ -33,9 +33,9 @@ const StyledTitle = styled.span`
|
|||||||
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// const buttonHeight = 32;
|
// const buttonSizePx = 32;
|
||||||
|
|
||||||
const buttonHeight = (props: Props) => {
|
export const buttonSizePx = (props: Props) => {
|
||||||
if (!props.size || props.size === ButtonSize.Normal) return 32;
|
if (!props.size || props.size === ButtonSize.Normal) return 32;
|
||||||
if (props.size === ButtonSize.Small) return 26;
|
if (props.size === ButtonSize.Small) return 26;
|
||||||
throw new Error(`Unknown size: ${props.size}`);
|
throw new Error(`Unknown size: ${props.size}`);
|
||||||
@ -45,13 +45,13 @@ const StyledButtonBase = styled.button`
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
height: ${(props: Props) => buttonHeight(props)}px;
|
height: ${(props: Props) => buttonSizePx(props)}px;
|
||||||
min-height: ${(props: Props) => buttonHeight(props)}px;
|
min-height: ${(props: Props) => buttonSizePx(props)}px;
|
||||||
max-height: ${(props: Props) => buttonHeight(props)}px;
|
max-height: ${(props: Props) => buttonSizePx(props)}px;
|
||||||
width: ${(props: any) => props.iconOnly ? `${buttonHeight}px` : 'auto'};
|
width: ${(props: any) => props.iconOnly ? `${buttonSizePx}px` : 'auto'};
|
||||||
${(props: any) => props.iconOnly ? `min-width: ${buttonHeight}px;` : ''}
|
${(props: any) => props.iconOnly ? `min-width: ${buttonSizePx}px;` : ''}
|
||||||
${(props: any) => !props.iconOnly ? 'min-width: 100px;' : ''}
|
${(props: any) => !props.iconOnly ? 'min-width: 100px;' : ''}
|
||||||
${(props: any) => props.iconOnly ? `max-width: ${buttonHeight}px;` : ''}
|
${(props: any) => props.iconOnly ? `max-width: ${buttonSizePx}px;` : ''}
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
|
@ -28,6 +28,9 @@ import * as showSpellCheckerMenu from './showSpellCheckerMenu';
|
|||||||
import * as toggleEditors from './toggleEditors';
|
import * as toggleEditors from './toggleEditors';
|
||||||
import * as toggleLayoutMoveMode from './toggleLayoutMoveMode';
|
import * as toggleLayoutMoveMode from './toggleLayoutMoveMode';
|
||||||
import * as toggleNoteList from './toggleNoteList';
|
import * as toggleNoteList from './toggleNoteList';
|
||||||
|
import * as toggleNotesSortOrderField from './toggleNotesSortOrderField';
|
||||||
|
import * as toggleNotesSortOrderReverse from './toggleNotesSortOrderReverse';
|
||||||
|
import * as togglePerFolderSortOrder from './togglePerFolderSortOrder';
|
||||||
import * as toggleSideBar from './toggleSideBar';
|
import * as toggleSideBar from './toggleSideBar';
|
||||||
import * as toggleVisiblePanes from './toggleVisiblePanes';
|
import * as toggleVisiblePanes from './toggleVisiblePanes';
|
||||||
|
|
||||||
@ -61,6 +64,9 @@ const index:any[] = [
|
|||||||
toggleEditors,
|
toggleEditors,
|
||||||
toggleLayoutMoveMode,
|
toggleLayoutMoveMode,
|
||||||
toggleNoteList,
|
toggleNoteList,
|
||||||
|
toggleNotesSortOrderField,
|
||||||
|
toggleNotesSortOrderReverse,
|
||||||
|
togglePerFolderSortOrder,
|
||||||
toggleSideBar,
|
toggleSideBar,
|
||||||
toggleVisiblePanes,
|
toggleVisiblePanes,
|
||||||
];
|
];
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
import { CommandContext, CommandDeclaration, CommandRuntime } from '@joplin/lib/services/CommandService';
|
||||||
|
import { setNotesSortOrder } from '../../../services/sortOrder/notesSortOrderUtils';
|
||||||
|
import { _ } from '@joplin/lib/locale';
|
||||||
|
|
||||||
|
export const declaration: CommandDeclaration = {
|
||||||
|
name: 'toggleNotesSortOrderField',
|
||||||
|
label: () => _('Toggle sort order field'),
|
||||||
|
parentLabel: () => _('Notes'),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const runtime = (): CommandRuntime => {
|
||||||
|
return {
|
||||||
|
execute: async (_context: CommandContext, field?: string | Array<any>, reverse?: boolean) => {
|
||||||
|
// field: Sort order's field. undefined means switching a field.
|
||||||
|
// reverse: whether the sort order is reversed or not. undefined means toggling.
|
||||||
|
//
|
||||||
|
// To support CommandService.scheduleExecute(), field accepts an size-two Array,
|
||||||
|
// which means [field, reverse].
|
||||||
|
if (typeof field !== 'object') {
|
||||||
|
setNotesSortOrder(field, reverse);
|
||||||
|
} else {
|
||||||
|
setNotesSortOrder(field[0], field[1]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,19 @@
|
|||||||
|
import { CommandContext, CommandDeclaration, CommandRuntime } from '@joplin/lib/services/CommandService';
|
||||||
|
import Setting from '@joplin/lib/models/Setting';
|
||||||
|
import { _ } from '@joplin/lib/locale';
|
||||||
|
import { setNotesSortOrder } from '../../../services/sortOrder/notesSortOrderUtils';
|
||||||
|
|
||||||
|
export const declaration: CommandDeclaration = {
|
||||||
|
name: 'toggleNotesSortOrderReverse',
|
||||||
|
label: () => _('Reverse sort order'),
|
||||||
|
parentLabel: () => _('Notes'),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const runtime = (): CommandRuntime => {
|
||||||
|
return {
|
||||||
|
execute: async (_context: CommandContext) => {
|
||||||
|
const reverse = Setting.value('notes.sortOrder.reverse');
|
||||||
|
setNotesSortOrder(undefined, !reverse);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,18 @@
|
|||||||
|
import { CommandContext, CommandDeclaration, CommandRuntime } from '@joplin/lib/services/CommandService';
|
||||||
|
import { _ } from '@joplin/lib/locale';
|
||||||
|
import PerFolderSortOrderService from '../../../services/sortOrder/PerFolderSortOrderService';
|
||||||
|
|
||||||
|
export const declaration: CommandDeclaration = {
|
||||||
|
name: 'togglePerFolderSortOrder',
|
||||||
|
label: () => _('Toggle own sort order'),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const runtime = (): CommandRuntime => {
|
||||||
|
return {
|
||||||
|
enabledCondition: 'oneFolderSelected',
|
||||||
|
|
||||||
|
execute: async (_context: CommandContext, folderId?: string, own?: boolean) => {
|
||||||
|
PerFolderSortOrderService.set(folderId, own);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
@ -136,7 +136,8 @@ function useMenuStates(menu: any, props: Props) {
|
|||||||
menuItemSetChecked(`sort:${type}:${field}`, (props as any)[`${type}.sortOrder.field`] === field);
|
menuItemSetChecked(`sort:${type}:${field}`, (props as any)[`${type}.sortOrder.field`] === field);
|
||||||
}
|
}
|
||||||
|
|
||||||
menuItemSetChecked(`sort:${type}:reverse`, (props as any)[`${type}.sortOrder.reverse`]);
|
const id = type == 'notes' ? 'toggleNotesSortOrderReverse' : `sort:${type}:reverse`;
|
||||||
|
menuItemSetChecked(id, (props as any)[`${type}.sortOrder.reverse`]);
|
||||||
}
|
}
|
||||||
|
|
||||||
applySortItemCheckState('notes');
|
applySortItemCheckState('notes');
|
||||||
@ -267,13 +268,23 @@ function useMenu(props: Props) {
|
|||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
// checked: Setting.value(`${type}.sortOrder.field`) === field,
|
// checked: Setting.value(`${type}.sortOrder.field`) === field,
|
||||||
click: () => {
|
click: () => {
|
||||||
|
if (type === 'notes') {
|
||||||
|
void CommandService.instance().execute('toggleNotesSortOrderField', field);
|
||||||
|
} else {
|
||||||
Setting.setValue(`${type}.sortOrder.field`, field);
|
Setting.setValue(`${type}.sortOrder.field`, field);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
sortItems.push({ type: 'separator' });
|
sortItems.push({ type: 'separator' });
|
||||||
|
|
||||||
|
if (type == 'notes') {
|
||||||
|
sortItems.push(
|
||||||
|
{ ...menuItemDic.toggleNotesSortOrderReverse, type: 'checkbox' },
|
||||||
|
{ ...menuItemDic.toggleNotesSortOrderField, visible: false }
|
||||||
|
);
|
||||||
|
} else {
|
||||||
sortItems.push({
|
sortItems.push({
|
||||||
id: `sort:${type}:reverse`,
|
id: `sort:${type}:reverse`,
|
||||||
label: Setting.settingMetadata(`${type}.sortOrder.reverse`).label(),
|
label: Setting.settingMetadata(`${type}.sortOrder.reverse`).label(),
|
||||||
@ -283,6 +294,7 @@ function useMenu(props: Props) {
|
|||||||
Setting.setValue(`${type}.sortOrder.reverse`, !Setting.value(`${type}.sortOrder.reverse`));
|
Setting.setValue(`${type}.sortOrder.reverse`, !Setting.value(`${type}.sortOrder.reverse`));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return sortItems;
|
return sortItems;
|
||||||
};
|
};
|
||||||
|
@ -1,13 +1,19 @@
|
|||||||
|
import { AppState } from '../../app.reducer';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import SearchBar from '../SearchBar/SearchBar';
|
import SearchBar from '../SearchBar/SearchBar';
|
||||||
import Button, { ButtonLevel } from '../Button/Button';
|
import Button, { ButtonLevel, ButtonSize, buttonSizePx } from '../Button/Button';
|
||||||
import CommandService from '@joplin/lib/services/CommandService';
|
import CommandService from '@joplin/lib/services/CommandService';
|
||||||
import { runtime as focusSearchRuntime } from './commands/focusSearch';
|
import { runtime as focusSearchRuntime } from './commands/focusSearch';
|
||||||
|
const { connect } = require('react-redux');
|
||||||
const styled = require('styled-components').default;
|
const styled = require('styled-components').default;
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
showNewNoteButtons: boolean;
|
showNewNoteButtons: boolean;
|
||||||
|
sortOrderButtonsVisible: boolean;
|
||||||
|
sortOrderField: string;
|
||||||
|
sortOrderReverse: boolean;
|
||||||
|
notesParentType: string;
|
||||||
height: number;
|
height: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,12 +34,27 @@ const StyledButton = styled(Button)`
|
|||||||
min-height: 26px;
|
min-height: 26px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const StyledPairButtonL = styled(Button)`
|
||||||
|
margin-left: 8px;
|
||||||
|
border-radius: 5px 0 0 5px;
|
||||||
|
min-width: ${(props: any) => buttonSizePx(props)}px;
|
||||||
|
max-width: ${(props: any) => buttonSizePx(props)}px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledPairButtonR = styled(Button)`
|
||||||
|
min-width: 8px;
|
||||||
|
margin-left: 0px;
|
||||||
|
border-radius: 0 5px 5px 0;
|
||||||
|
border-width: 1px 1px 1px 0;
|
||||||
|
width: auto;
|
||||||
|
`;
|
||||||
|
|
||||||
const ButtonContainer = styled.div`
|
const ButtonContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default function NoteListControls(props: Props) {
|
function NoteListControls(props: Props) {
|
||||||
const searchBarRef = useRef(null);
|
const searchBarRef = useRef(null);
|
||||||
|
|
||||||
useEffect(function() {
|
useEffect(function() {
|
||||||
@ -52,16 +73,66 @@ export default function NoteListControls(props: Props) {
|
|||||||
void CommandService.instance().execute('newNote');
|
void CommandService.instance().execute('newNote');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onSortOrderFieldButtonClick() {
|
||||||
|
void CommandService.instance().execute('toggleNotesSortOrderField');
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSortOrderReverseButtonClick() {
|
||||||
|
void CommandService.instance().execute('toggleNotesSortOrderReverse');
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortOrderFieldIcon() {
|
||||||
|
const field = props.sortOrderField;
|
||||||
|
const iconMap: any = {
|
||||||
|
user_updated_time: 'far fa-calendar-alt',
|
||||||
|
user_created_time: 'far fa-calendar-plus',
|
||||||
|
title: 'fas fa-font',
|
||||||
|
order: 'fas fa-wrench',
|
||||||
|
};
|
||||||
|
return `${iconMap[field] || iconMap['title']} ${field}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortOrderReverseIcon() {
|
||||||
|
return props.sortOrderReverse ? 'fas fa-long-arrow-alt-up' : 'fas fa-long-arrow-alt-down';
|
||||||
|
}
|
||||||
|
|
||||||
|
function showsSortOrderButtons() {
|
||||||
|
let visible = props.sortOrderButtonsVisible;
|
||||||
|
if (props.notesParentType === 'Search') visible = false;
|
||||||
|
return visible;
|
||||||
|
}
|
||||||
|
|
||||||
function renderNewNoteButtons() {
|
function renderNewNoteButtons() {
|
||||||
if (!props.showNewNoteButtons) return null;
|
if (!props.showNewNoteButtons) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ButtonContainer>
|
<ButtonContainer>
|
||||||
|
{showsSortOrderButtons() &&
|
||||||
|
<StyledPairButtonL
|
||||||
|
className="sort-order-field-button"
|
||||||
|
tooltip={CommandService.instance().label('toggleNotesSortOrderField')}
|
||||||
|
iconName={sortOrderFieldIcon()}
|
||||||
|
level={ButtonLevel.Secondary}
|
||||||
|
size={ButtonSize.Small}
|
||||||
|
onClick={onSortOrderFieldButtonClick}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{showsSortOrderButtons() &&
|
||||||
|
<StyledPairButtonR
|
||||||
|
className="sort-order-reverse-button"
|
||||||
|
tooltip={CommandService.instance().label('toggleNotesSortOrderReverse')}
|
||||||
|
iconName={sortOrderReverseIcon()}
|
||||||
|
level={ButtonLevel.Secondary}
|
||||||
|
size={ButtonSize.Small}
|
||||||
|
onClick={onSortOrderReverseButtonClick}
|
||||||
|
/>
|
||||||
|
}
|
||||||
<StyledButton
|
<StyledButton
|
||||||
className="new-todo-button"
|
className="new-todo-button"
|
||||||
tooltip={CommandService.instance().label('newTodo')}
|
tooltip={CommandService.instance().label('newTodo')}
|
||||||
iconName="far fa-check-square"
|
iconName="far fa-check-square"
|
||||||
level={ButtonLevel.Primary}
|
level={ButtonLevel.Primary}
|
||||||
|
size={ButtonSize.Small}
|
||||||
onClick={onNewTodoButtonClick}
|
onClick={onNewTodoButtonClick}
|
||||||
/>
|
/>
|
||||||
<StyledButton
|
<StyledButton
|
||||||
@ -69,6 +140,7 @@ export default function NoteListControls(props: Props) {
|
|||||||
tooltip={CommandService.instance().label('newNote')}
|
tooltip={CommandService.instance().label('newNote')}
|
||||||
iconName="icon-note"
|
iconName="icon-note"
|
||||||
level={ButtonLevel.Primary}
|
level={ButtonLevel.Primary}
|
||||||
|
size={ButtonSize.Small}
|
||||||
onClick={onNewNoteButtonClick}
|
onClick={onNewNoteButtonClick}
|
||||||
/>
|
/>
|
||||||
</ButtonContainer>
|
</ButtonContainer>
|
||||||
@ -82,3 +154,14 @@ export default function NoteListControls(props: Props) {
|
|||||||
</StyledRoot>
|
</StyledRoot>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = (state: AppState) => {
|
||||||
|
return {
|
||||||
|
sortOrderButtonsVisible: state.settings['notes.sortOrder.buttonsVisible'],
|
||||||
|
sortOrderField: state.settings['notes.sortOrder.field'],
|
||||||
|
sortOrderReverse: state.settings['notes.sortOrder.reverse'],
|
||||||
|
notesParentType: state.notesParentType,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(NoteListControls);
|
||||||
|
@ -20,6 +20,7 @@ import Logger from '@joplin/lib/Logger';
|
|||||||
import { FolderEntity } from '@joplin/lib/services/database/types';
|
import { FolderEntity } from '@joplin/lib/services/database/types';
|
||||||
import stateToWhenClauseContext from '../../services/commands/stateToWhenClauseContext';
|
import stateToWhenClauseContext from '../../services/commands/stateToWhenClauseContext';
|
||||||
import { store } from '@joplin/lib/reducer';
|
import { store } from '@joplin/lib/reducer';
|
||||||
|
import PerFolderSortOrderService from '../../services/sortOrder/PerFolderSortOrderService';
|
||||||
import { getFolderCallbackUrl, getTagCallbackUrl } from '@joplin/lib/callbackUrlUtils';
|
import { getFolderCallbackUrl, getTagCallbackUrl } from '@joplin/lib/callbackUrlUtils';
|
||||||
const { connect } = require('react-redux');
|
const { connect } = require('react-redux');
|
||||||
const shared = require('@joplin/lib/components/shared/side-menu-shared.js');
|
const shared = require('@joplin/lib/components/shared/side-menu-shared.js');
|
||||||
@ -332,6 +333,13 @@ class SidebarComponent extends React.Component<Props, State> {
|
|||||||
submenu: exportMenu,
|
submenu: exportMenu,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
if (Setting.value('notes.perFolderSortOrderEnabled')) {
|
||||||
|
menu.append(new MenuItem({
|
||||||
|
...menuUtils.commandToStatefulMenuItem('togglePerFolderSortOrder', itemId),
|
||||||
|
type: 'checkbox',
|
||||||
|
checked: PerFolderSortOrderService.isSet(itemId),
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (itemType === BaseModel.TYPE_FOLDER) {
|
if (itemType === BaseModel.TYPE_FOLDER) {
|
||||||
|
@ -32,6 +32,9 @@ export default function() {
|
|||||||
'toggleExternalEditing',
|
'toggleExternalEditing',
|
||||||
'toggleLayoutMoveMode',
|
'toggleLayoutMoveMode',
|
||||||
'toggleNoteList',
|
'toggleNoteList',
|
||||||
|
'toggleNotesSortOrderField',
|
||||||
|
'toggleNotesSortOrderReverse',
|
||||||
|
'togglePerFolderSortOrder',
|
||||||
'toggleSideBar',
|
'toggleSideBar',
|
||||||
'toggleVisiblePanes',
|
'toggleVisiblePanes',
|
||||||
'editor.deleteLine',
|
'editor.deleteLine',
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
import PerFolderSortOrderService from './PerFolderSortOrderService';
|
||||||
|
import { setNotesSortOrder } from './notesSortOrderUtils';
|
||||||
|
import Setting from '@joplin/lib/models/Setting';
|
||||||
|
const { shimInit } = require('@joplin/lib/shim-init-node.js');
|
||||||
|
|
||||||
|
const folderId1 = 'aa012345678901234567890123456789';
|
||||||
|
const folderId2 = 'bb012345678901234567890123456789';
|
||||||
|
|
||||||
|
beforeAll(async (done) => {
|
||||||
|
shimInit();
|
||||||
|
Setting.autoSaveEnabled = false;
|
||||||
|
PerFolderSortOrderService.initialize();
|
||||||
|
Setting.setValue('notes.perFolderSortOrderEnabled', true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('PerFolderSortOrderService', () => {
|
||||||
|
|
||||||
|
test('get(), isSet() and set()', async (done) => {
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,198 @@
|
|||||||
|
import Setting from '@joplin/lib/models/Setting';
|
||||||
|
import eventManager from '@joplin/lib/eventManager';
|
||||||
|
import { notesSortOrderFieldArray, setNotesSortOrder } from './notesSortOrderUtils';
|
||||||
|
|
||||||
|
const SUFFIX_FIELD = '$field';
|
||||||
|
const SUFFIX_REVERSE = '$reverse';
|
||||||
|
|
||||||
|
export interface SortOrder {
|
||||||
|
field: string;
|
||||||
|
reverse: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FolderState {
|
||||||
|
notesParentType: string;
|
||||||
|
selectedFolderId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SortOrderPool {
|
||||||
|
[key: string]: string | boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class PerFolderSortOrderService {
|
||||||
|
|
||||||
|
private static previousFolderId: string = null;
|
||||||
|
private static folderState: FolderState = { notesParentType: '', selectedFolderId: '' };
|
||||||
|
// 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'));
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PerFolderSortOrderService.setPerFolderSortOrder(targetId, field, reverse);
|
||||||
|
} else {
|
||||||
|
PerFolderSortOrderService.deletePerFolderSortOrder(targetId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static onFolderSelectionMayChange(cause: string, event: any) {
|
||||||
|
if (cause !== 'notesParentType' && cause !== 'selectedFolderId') 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 {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static loadPerFolderSortOrders() {
|
||||||
|
this.perFolderSortOrders = { ...Setting.value('notes.perFolderSortOrders') };
|
||||||
|
}
|
||||||
|
|
||||||
|
private static loadSharedSortOrder() {
|
||||||
|
const validFields = notesSortOrderFieldArray();
|
||||||
|
const value = Setting.value('notes.sharedSortOrder');
|
||||||
|
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,85 @@
|
|||||||
|
import { notesSortOrderFieldArray, notesSortOrderNextField, setNotesSortOrder } from './notesSortOrderUtils';
|
||||||
|
import Setting from '@joplin/lib/models/Setting';
|
||||||
|
const { shimInit } = require('@joplin/lib/shim-init-node.js');
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
shimInit();
|
||||||
|
Setting.autoSaveEnabled = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('notesSortOrderUtils', () => {
|
||||||
|
|
||||||
|
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 cyclicly', 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,66 @@
|
|||||||
|
import Setting from '@joplin/lib/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 cyclicly.
|
||||||
|
// 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;
|
||||||
|
Setting.setValue('notes.perFieldReverse', { ...perFieldReverse });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -488,7 +488,6 @@ export default class BaseApplication {
|
|||||||
// appLogger.debug('Reducer action', this.reducerActionToString(action));
|
// appLogger.debug('Reducer action', this.reducerActionToString(action));
|
||||||
|
|
||||||
const result = next(action);
|
const result = next(action);
|
||||||
const newState = store.getState();
|
|
||||||
let refreshNotes = false;
|
let refreshNotes = false;
|
||||||
let refreshFolders: boolean | string = false;
|
let refreshFolders: boolean | string = false;
|
||||||
// let refreshTags = false;
|
// let refreshTags = false;
|
||||||
@ -496,6 +495,7 @@ export default class BaseApplication {
|
|||||||
let refreshNotesHash = '';
|
let refreshNotesHash = '';
|
||||||
|
|
||||||
await reduxSharedMiddleware(store, next, action);
|
await reduxSharedMiddleware(store, next, action);
|
||||||
|
const newState = store.getState();
|
||||||
|
|
||||||
if (this.hasGui() && ['NOTE_UPDATE_ONE', 'NOTE_DELETE', 'FOLDER_UPDATE_ONE', 'FOLDER_DELETE'].indexOf(action.type) >= 0) {
|
if (this.hasGui() && ['NOTE_UPDATE_ONE', 'NOTE_DELETE', 'FOLDER_UPDATE_ONE', 'FOLDER_DELETE'].indexOf(action.type) >= 0) {
|
||||||
if (!(await reg.syncTarget().syncStarted())) void reg.scheduleSync(30 * 1000, { syncSteps: ['update_remote', 'delete_remote'] });
|
if (!(await reg.syncTarget().syncStarted())) void reg.scheduleSync(30 * 1000, { syncSteps: ['update_remote', 'delete_remote'] });
|
||||||
|
@ -825,6 +825,63 @@ class Setting extends BaseModel {
|
|||||||
storage: SettingStorage.File,
|
storage: SettingStorage.File,
|
||||||
},
|
},
|
||||||
'notes.sortOrder.reverse': { value: true, type: SettingItemType.Bool, storage: SettingStorage.File, section: 'note', public: true, label: () => _('Reverse sort order'), appTypes: [AppType.Cli] },
|
'notes.sortOrder.reverse': { value: true, type: SettingItemType.Bool, storage: SettingStorage.File, section: 'note', public: true, label: () => _('Reverse sort order'), appTypes: [AppType.Cli] },
|
||||||
|
// NOTE: A setting whose name starts with 'notes.sortOrder' is special,
|
||||||
|
// which implies changing the setting automatically triggers the reflesh of notes.
|
||||||
|
// See lib/BaseApplication.ts/generalMiddleware() for details.
|
||||||
|
'notes.sortOrder.buttonsVisible': {
|
||||||
|
value: true,
|
||||||
|
type: SettingItemType.Bool,
|
||||||
|
storage: SettingStorage.File,
|
||||||
|
section: 'appearance',
|
||||||
|
public: true,
|
||||||
|
label: () => _('Show sort order buttons'),
|
||||||
|
description: () => _('If true, sort order buttons (field + reverse) for notes are shown at the top of Note List.'),
|
||||||
|
appTypes: [AppType.Desktop],
|
||||||
|
},
|
||||||
|
'notes.perFieldReversalEnabled': {
|
||||||
|
value: true,
|
||||||
|
type: SettingItemType.Bool,
|
||||||
|
storage: SettingStorage.File,
|
||||||
|
section: 'note',
|
||||||
|
public: false,
|
||||||
|
appTypes: [AppType.Cli, AppType.Desktop],
|
||||||
|
},
|
||||||
|
'notes.perFieldReverse': {
|
||||||
|
value: {
|
||||||
|
user_updated_time: true,
|
||||||
|
user_created_time: true,
|
||||||
|
title: false,
|
||||||
|
order: false,
|
||||||
|
},
|
||||||
|
type: SettingItemType.Object,
|
||||||
|
storage: SettingStorage.File,
|
||||||
|
section: 'note',
|
||||||
|
public: false,
|
||||||
|
appTypes: [AppType.Cli, AppType.Desktop],
|
||||||
|
},
|
||||||
|
'notes.perFolderSortOrderEnabled': {
|
||||||
|
value: true,
|
||||||
|
type: SettingItemType.Bool,
|
||||||
|
storage: SettingStorage.File,
|
||||||
|
section: 'folder',
|
||||||
|
public: false,
|
||||||
|
appTypes: [AppType.Cli, AppType.Desktop],
|
||||||
|
},
|
||||||
|
'notes.perFolderSortOrders': {
|
||||||
|
value: {},
|
||||||
|
type: SettingItemType.Object,
|
||||||
|
storage: SettingStorage.File,
|
||||||
|
section: 'folder',
|
||||||
|
public: false,
|
||||||
|
appTypes: [AppType.Cli, AppType.Desktop],
|
||||||
|
},
|
||||||
|
'notes.sharedSortOrder': {
|
||||||
|
value: {},
|
||||||
|
type: SettingItemType.Object,
|
||||||
|
section: 'folder',
|
||||||
|
public: false,
|
||||||
|
appTypes: [AppType.Cli, AppType.Desktop],
|
||||||
|
},
|
||||||
'folders.sortOrder.field': {
|
'folders.sortOrder.field': {
|
||||||
value: 'title',
|
value: 'title',
|
||||||
type: SettingItemType.String,
|
type: SettingItemType.String,
|
||||||
@ -2044,6 +2101,7 @@ class Setting extends BaseModel {
|
|||||||
if (name === 'sync') return _('Synchronisation');
|
if (name === 'sync') return _('Synchronisation');
|
||||||
if (name === 'appearance') return _('Appearance');
|
if (name === 'appearance') return _('Appearance');
|
||||||
if (name === 'note') return _('Note');
|
if (name === 'note') return _('Note');
|
||||||
|
if (name === 'folder') return _('Notebook');
|
||||||
if (name === 'markdownPlugins') return _('Markdown');
|
if (name === 'markdownPlugins') return _('Markdown');
|
||||||
if (name === 'plugins') return _('Plugins');
|
if (name === 'plugins') return _('Plugins');
|
||||||
if (name === 'application') return _('Application');
|
if (name === 'application') return _('Application');
|
||||||
@ -2071,6 +2129,7 @@ class Setting extends BaseModel {
|
|||||||
if (name === 'sync') return 'icon-sync';
|
if (name === 'sync') return 'icon-sync';
|
||||||
if (name === 'appearance') return 'icon-appearance';
|
if (name === 'appearance') return 'icon-appearance';
|
||||||
if (name === 'note') return 'icon-note';
|
if (name === 'note') return 'icon-note';
|
||||||
|
if (name === 'folder') return 'icon-notebooks';
|
||||||
if (name === 'plugins') return 'icon-plugins';
|
if (name === 'plugins') return 'icon-plugins';
|
||||||
if (name === 'markdownPlugins') return 'fab fa-markdown';
|
if (name === 'markdownPlugins') return 'fab fa-markdown';
|
||||||
if (name === 'application') return 'icon-application';
|
if (name === 'application') return 'icon-application';
|
||||||
|
Loading…
Reference in New Issue
Block a user