1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-12-08 23:07:32 +02:00

Compare commits

...

14 Commits

Author SHA1 Message Date
Laurent Cozic
1ac8265aae Fixed button size 2021-10-30 15:43:57 +01:00
Laurent Cozic
5422e2d5cc Merge branch 'sort-order_v2.4.5' of https://github.com/ken1kob/joplin into ken1kob-sort-order_v2.4.5 2021-10-30 15:19:06 +01:00
Kenichi Kobayashi
00bc482308 Sort Order features are adapted to v2.5 and refactored.
- The size of Sort Order buttons is adjusted to fit to v2.5.
- Refactor PerFolderSortOrderService.ts
- Option of Sort Order buttons is move to Appearance section.
- Option of Per-notebook Sort Order is move to Notebook section.
- Some descriptions about sort order settings are added.
2021-10-17 11:10:21 +09:00
Kenichi Kobayashi
7b275d2269 Merge branch 'dev' into sort-order_v2.4.5 2021-10-17 10:02:48 +09:00
Kenichi Kobayashi
083310691f Merge remote-tracking branch 'remotes/laurent/release-2.5' into sort-order_v2.4.5 2021-10-17 09:40:11 +09:00
Kenichi Kobayashi
e75b50cdf3 Make sort-order patch auto-mergeable to 2.5 2021-10-09 10:07:36 +09:00
Kenichi Kobayashi
36e00f8cfe Per-Notebook Sort Order is refactored.
- renames commands and their labels:
	- notesSortOrderSwitch ->  toggleNotesSortOrderField
    - notesSortOrderToggleReverse -> toggleNotesSortOrderReverse
    - perFolderSortOrderSwitch -> togglePerFolderSortOrder
- moves the most of the commands' body to higher-layer services to clean-up module dependencies.
	- app-desktop/services/sortOrder/notesSortOrderUtils.ts
	- app-desktop/services/sortOrder/PerFolderSortOrderService.ts
- PerFolderSortOrderService is initialized in app.ts/start().
2021-10-01 22:38:50 +09:00
Kenichi Kobayashi
5d0b0e3df2 dependency to 'app' is removed from perFolderSortOrder.ts 2021-09-20 17:48:54 +09:00
Kenichi Kobayashi
e9dcc3bad8 add mapStateToProps and connect to avoid using Setting.value(). 2021-09-20 09:18:00 +09:00
Kenichi Kobayashi
4cdba95ba4 remove string constants and internal wrapping functions 2021-09-20 06:53:14 +09:00
Kenichi Kobayashi
c723ebe68d Notebook -> Folder 2021-09-20 01:15:42 +09:00
Kenichi Kobayashi
91de06d2a6 fix typo. 2021-09-09 22:13:26 +09:00
Kenichi Kobayashi
bf100e326c fixes for lint 2021-09-08 23:15:17 +09:00
ken1kob
e644195c8e Initial commit for Sort order buttons, Per-field reversal and Per-notebook sort order. 2021-09-08 21:30:22 +09:00
16 changed files with 567 additions and 26 deletions

View File

@@ -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.js
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.js
packages/app-desktop/gui/MainScreen/commands/toggleSideBar.js.map
@@ -735,6 +744,12 @@ packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.js.map
packages/app-desktop/services/share/invitationRespond.d.ts
packages/app-desktop/services/share/invitationRespond.js
packages/app-desktop/services/share/invitationRespond.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/notesSortOrderUtils.d.ts
packages/app-desktop/services/sortOrder/notesSortOrderUtils.js
packages/app-desktop/services/sortOrder/notesSortOrderUtils.js.map
packages/app-desktop/services/spellChecker/SpellCheckerServiceDriverNative.d.ts
packages/app-desktop/services/spellChecker/SpellCheckerServiceDriverNative.js
packages/app-desktop/services/spellChecker/SpellCheckerServiceDriverNative.js.map

15
.gitignore vendored
View File

@@ -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.js
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.js
packages/app-desktop/gui/MainScreen/commands/toggleSideBar.js.map
@@ -718,6 +727,12 @@ packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.js.map
packages/app-desktop/services/share/invitationRespond.d.ts
packages/app-desktop/services/share/invitationRespond.js
packages/app-desktop/services/share/invitationRespond.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/notesSortOrderUtils.d.ts
packages/app-desktop/services/sortOrder/notesSortOrderUtils.js
packages/app-desktop/services/sortOrder/notesSortOrderUtils.js.map
packages/app-desktop/services/spellChecker/SpellCheckerServiceDriverNative.d.ts
packages/app-desktop/services/spellChecker/SpellCheckerServiceDriverNative.js
packages/app-desktop/services/spellChecker/SpellCheckerServiceDriverNative.js.map

View File

@@ -58,6 +58,7 @@ const commands = mainScreenCommands
const globalCommands = appCommands.concat(libCommands);
import editorCommandDeclarations from './gui/NoteEditor/editorCommandDeclarations';
import PerFolderSortOrderService from './services/sortOrder/PerFolderSortOrderService';
import ShareService from '@joplin/lib/services/share/ShareService';
import checkForUpdates from './checkForUpdates';
import { AppState } from './app.reducer';
@@ -388,6 +389,8 @@ class Application extends BaseApplication {
this.initRedux();
PerFolderSortOrderService.initialize();
CommandService.instance().initialize(this.store(), Setting.value('env') == 'dev', stateToWhenClauseContext);
for (const command of commands) {

View File

@@ -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 === ButtonSize.Small) return 26;
throw new Error(`Unknown size: ${props.size}`);
@@ -45,13 +45,13 @@ const StyledButtonBase = styled.button`
display: flex;
align-items: center;
flex-direction: row;
height: ${(props: Props) => buttonHeight(props)}px;
min-height: ${(props: Props) => buttonHeight(props)}px;
max-height: ${(props: Props) => buttonHeight(props)}px;
width: ${(props: any) => props.iconOnly ? `${buttonHeight}px` : 'auto'};
${(props: any) => props.iconOnly ? `min-width: ${buttonHeight}px;` : ''}
height: ${(props: Props) => buttonSizePx(props)}px;
min-height: ${(props: Props) => buttonSizePx(props)}px;
max-height: ${(props: Props) => buttonSizePx(props)}px;
width: ${(props: any) => props.iconOnly ? `${buttonSizePx}px` : 'auto'};
${(props: any) => props.iconOnly ? `min-width: ${buttonSizePx}px;` : ''}
${(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;
border-radius: 3px;
border-style: solid;

View File

@@ -28,6 +28,9 @@ import * as showSpellCheckerMenu from './showSpellCheckerMenu';
import * as toggleEditors from './toggleEditors';
import * as toggleLayoutMoveMode from './toggleLayoutMoveMode';
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 toggleVisiblePanes from './toggleVisiblePanes';
@@ -61,6 +64,9 @@ const index:any[] = [
toggleEditors,
toggleLayoutMoveMode,
toggleNoteList,
toggleNotesSortOrderField,
toggleNotesSortOrderReverse,
togglePerFolderSortOrder,
toggleSideBar,
toggleVisiblePanes,
];

View File

@@ -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]);
}
},
};
};

View File

@@ -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);
},
};
};

View File

@@ -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 per-notebook sort order'),
};
export const runtime = (): CommandRuntime => {
return {
enabledCondition: 'oneFolderSelected',
execute: async (_context: CommandContext, folderId?: string, own?: boolean) => {
PerFolderSortOrderService.set(folderId, own);
},
};
};

View File

@@ -136,7 +136,8 @@ function useMenuStates(menu: any, props: Props) {
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');
@@ -267,22 +268,33 @@ function useMenu(props: Props) {
type: 'checkbox',
// checked: Setting.value(`${type}.sortOrder.field`) === field,
click: () => {
Setting.setValue(`${type}.sortOrder.field`, field);
if (type === 'notes') {
void CommandService.instance().execute('toggleNotesSortOrderField', field);
} else {
Setting.setValue(`${type}.sortOrder.field`, field);
}
},
});
}
sortItems.push({ type: 'separator' });
sortItems.push({
id: `sort:${type}:reverse`,
label: Setting.settingMetadata(`${type}.sortOrder.reverse`).label(),
type: 'checkbox',
// checked: Setting.value(`${type}.sortOrder.reverse`),
click: () => {
Setting.setValue(`${type}.sortOrder.reverse`, !Setting.value(`${type}.sortOrder.reverse`));
},
});
if (type == 'notes') {
sortItems.push(
{ ...menuItemDic.toggleNotesSortOrderReverse, type: 'checkbox' },
{ ...menuItemDic.toggleNotesSortOrderField, visible: false }
);
} else {
sortItems.push({
id: `sort:${type}:reverse`,
label: Setting.settingMetadata(`${type}.sortOrder.reverse`).label(),
type: 'checkbox',
// checked: Setting.value(`${type}.sortOrder.reverse`),
click: () => {
Setting.setValue(`${type}.sortOrder.reverse`, !Setting.value(`${type}.sortOrder.reverse`));
},
});
}
return sortItems;
};

View File

@@ -1,13 +1,21 @@
import { AppState } from '../../app.reducer';
import * as React from 'react';
import { useEffect, useRef } from 'react';
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 { runtime as focusSearchRuntime } from './commands/focusSearch';
import Note from '@joplin/lib/models/Note';
import { notesSortOrderNextField } from '../../services/sortOrder/notesSortOrderUtils';
const { connect } = require('react-redux');
const styled = require('styled-components').default;
interface Props {
showNewNoteButtons: boolean;
sortOrderButtonsVisible: boolean;
sortOrderField: string;
sortOrderReverse: boolean;
notesParentType: string;
height: number;
}
@@ -22,10 +30,21 @@ const StyledRoot = styled.div`
const StyledButton = styled(Button)`
margin-left: 8px;
width: 26px;
height: 26px;
min-width: 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`
@@ -33,7 +52,7 @@ const ButtonContainer = styled.div`
flex-direction: row;
`;
export default function NoteListControls(props: Props) {
function NoteListControls(props: Props) {
const searchBarRef = useRef(null);
useEffect(function() {
@@ -52,16 +71,74 @@ export default function NoteListControls(props: Props) {
void CommandService.instance().execute('newNote');
}
function onSortOrderFieldButtonClick() {
void CommandService.instance().execute('toggleNotesSortOrderField');
}
function onSortOrderReverseButtonClick() {
void CommandService.instance().execute('toggleNotesSortOrderReverse');
}
function sortOrderFieldTooltip() {
const term1 = CommandService.instance().label('toggleNotesSortOrderField');
const field = props.sortOrderField;
const term2 = Note.fieldToLabel(field);
const term3 = Note.fieldToLabel(notesSortOrderNextField(field));
return `${term1}:\n ${term2} -> ${term3}`;
}
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() {
if (!props.showNewNoteButtons) return null;
return (
<ButtonContainer>
{showsSortOrderButtons() &&
<StyledPairButtonL
className="sort-order-field-button"
tooltip={sortOrderFieldTooltip()}
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
className="new-todo-button"
tooltip={CommandService.instance().label('newTodo')}
iconName="far fa-check-square"
level={ButtonLevel.Primary}
size={ButtonSize.Small}
onClick={onNewTodoButtonClick}
/>
<StyledButton
@@ -69,6 +146,7 @@ export default function NoteListControls(props: Props) {
tooltip={CommandService.instance().label('newNote')}
iconName="icon-note"
level={ButtonLevel.Primary}
size={ButtonSize.Small}
onClick={onNewNoteButtonClick}
/>
</ButtonContainer>
@@ -82,3 +160,14 @@ export default function NoteListControls(props: Props) {
</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);

View File

@@ -21,6 +21,7 @@ import { FolderEntity } from '@joplin/lib/services/database/types';
import stateToWhenClauseContext from '../../services/commands/stateToWhenClauseContext';
import { store } from '@joplin/lib/reducer';
import { getFolderCallbackUrl, getTagCallbackUrl } from '@joplin/lib/callbackUrlUtils';
import PerFolderSortOrderService from '../../services/sortOrder/PerFolderSortOrderService';
const { connect } = require('react-redux');
const shared = require('@joplin/lib/components/shared/side-menu-shared.js');
const { themeStyle } = require('@joplin/lib/theme');
@@ -332,6 +333,13 @@ class SidebarComponent extends React.Component<Props, State> {
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) {

View File

@@ -32,6 +32,9 @@ export default function() {
'toggleExternalEditing',
'toggleLayoutMoveMode',
'toggleNoteList',
'toggleNotesSortOrderField',
'toggleNotesSortOrderReverse',
'togglePerFolderSortOrder',
'toggleSideBar',
'toggleVisiblePanes',
'editor.deleteLine',

View File

@@ -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 });
}
}
}

View File

@@ -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 });
}
}
};

View File

@@ -488,7 +488,6 @@ export default class BaseApplication {
// appLogger.debug('Reducer action', this.reducerActionToString(action));
const result = next(action);
const newState = store.getState();
let refreshNotes = false;
let refreshFolders: boolean | string = false;
// let refreshTags = false;
@@ -496,6 +495,7 @@ export default class BaseApplication {
let refreshNotesHash = '';
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 (!(await reg.syncTarget().syncStarted())) void reg.scheduleSync(30 * 1000, { syncSteps: ['update_remote', 'delete_remote'] });

View File

@@ -825,6 +825,67 @@ class Setting extends BaseModel {
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] },
// 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,
label: () => _('Enable per-field sort order reversal'),
description: () => _('If true, normal or reverse is kept for each sort order field for notes.'),
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: 'notebook',
public: true,
label: () => _('Enable per-notebook sort order'),
description: () => _('If true, some notebooks can have their own sort orders (field + reverse) for their notes inside. Any number of notebooks can be specified to have their own sort orders via context menu in the sidebar.'),
appTypes: [AppType.Cli, AppType.Desktop],
},
'notes.perFolderSortOrders': {
value: {},
type: SettingItemType.Object,
storage: SettingStorage.File,
section: 'notebook',
public: false,
appTypes: [AppType.Cli, AppType.Desktop],
},
'notes.sharedSortOrder': {
value: {},
type: SettingItemType.Object,
section: 'notebook',
public: false,
appTypes: [AppType.Cli, AppType.Desktop],
},
'folders.sortOrder.field': {
value: 'title',
type: SettingItemType.String,
@@ -2044,6 +2105,7 @@ class Setting extends BaseModel {
if (name === 'sync') return _('Synchronisation');
if (name === 'appearance') return _('Appearance');
if (name === 'note') return _('Note');
if (name === 'notebook') return _('Notebook');
if (name === 'markdownPlugins') return _('Markdown');
if (name === 'plugins') return _('Plugins');
if (name === 'application') return _('Application');
@@ -2071,6 +2133,7 @@ class Setting extends BaseModel {
if (name === 'sync') return 'icon-sync';
if (name === 'appearance') return 'icon-appearance';
if (name === 'note') return 'icon-note';
if (name === 'notebook') return 'icon-notebooks';
if (name === 'plugins') return 'icon-plugins';
if (name === 'markdownPlugins') return 'fab fa-markdown';
if (name === 'application') return 'icon-application';