Skip to content

Commit 821558f

Browse files
committed
Desktop release v3.3.4
1 parent 5280ec1 commit 821558f

File tree

4 files changed

+287
-1
lines changed

4 files changed

+287
-1
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use strict';
2+
// Based on https://github.com/caroso1222/notyf/blob/master/recipes/react.md
3+
Object.defineProperty(exports, '__esModule', { value: true });
4+
const React = require('react');
5+
const notyf_1 = require('notyf');
6+
const types_1 = require('@joplin/lib/services/plugins/api/types');
7+
exports.default = React.createContext(new notyf_1.Notyf({
8+
// Set your global Notyf configuration here
9+
duration: 6000,
10+
types: [
11+
{
12+
type: types_1.ToastType.Info,
13+
icon: false,
14+
className: 'notyf__toast--info',
15+
background: 'blue', // Need to set a background, otherwise Notyf won't create the background element. But the color will be overriden in CSS.
16+
},
17+
],
18+
}));
19+
// # sourceMappingURL=NotyfContext.js.map

packages/app-desktop/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@joplin/app-desktop",
3-
"version": "3.3.3",
3+
"version": "3.3.4",
44
"description": "Joplin for Desktop",
55
"main": "main.js",
66
"private": true,
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
'use strict';
2+
Object.defineProperty(exports, '__esModule', { value: true });
3+
const React = require('react');
4+
const react_native_1 = require('react-native');
5+
const Modal_1 = require('../Modal');
6+
const react_1 = require('react');
7+
const locale_1 = require('@joplin/lib/locale');
8+
const buttons_1 = require('../buttons');
9+
// react-native-paper's floating action button menu is inaccessible on web
10+
// (can't be activated by a screen reader, and, in some cases, by the tab key).
11+
// This component provides an alternative.
12+
const AccessibleModalMenu = props => {
13+
let _a;
14+
const [open, setOpen] = (0, react_1.useState)(false);
15+
const onClick = (0, react_1.useCallback)(() => {
16+
if (props.onPress) {
17+
props.onPress();
18+
} else {
19+
setOpen(!open);
20+
}
21+
}, [open, props.onPress]);
22+
const options = [];
23+
for (const action of ((_a = props.actions) !== null && _a !== void 0 ? _a : [])) {
24+
options.push(React.createElement(buttons_1.PrimaryButton, { key: action.label, onPress: action.onPress }, action.label));
25+
}
26+
const modal = (React.createElement(Modal_1.default, { visible: open },
27+
options,
28+
React.createElement(buttons_1.SecondaryButton, { onPress: onClick }, (0, locale_1._)('Close menu'))));
29+
return React.createElement(react_native_1.View, { style: { height: 0, overflow: 'visible' } },
30+
modal,
31+
React.createElement(buttons_1.SecondaryButton, { onPress: onClick }, props.label));
32+
};
33+
exports.default = AccessibleModalMenu;
34+
// # sourceMappingURL=AccessibleModalMenu.js.map
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
'use strict';
2+
Object.defineProperty(exports, '__esModule', { value: true });
3+
const React = require('react');
4+
const react_native_1 = require('react-native');
5+
const reducer_1 = require('@joplin/lib/reducer');
6+
const react_redux_1 = require('react-redux');
7+
const NoteList_1 = require('../NoteList');
8+
const Folder_1 = require('@joplin/lib/models/Folder');
9+
const Tag_1 = require('@joplin/lib/models/Tag');
10+
const Note_1 = require('@joplin/lib/models/Note');
11+
const Setting_1 = require('@joplin/lib/models/Setting');
12+
const global_style_1 = require('../global-style');
13+
const ScreenHeader_1 = require('../ScreenHeader');
14+
const locale_1 = require('@joplin/lib/locale');
15+
const FloatingActionButton_1 = require('../buttons/FloatingActionButton');
16+
const base_screen_1 = require('../base-screen');
17+
const trash_1 = require('@joplin/lib/services/trash');
18+
const AccessibleView_1 = require('../accessibility/AccessibleView');
19+
const DialogManager_1 = require('../DialogManager');
20+
const react_1 = require('react');
21+
class NotesScreenComponent extends base_screen_1.BaseScreenComponent {
22+
constructor(props) {
23+
super(props);
24+
this.onAppStateChangeSub_ = null;
25+
this.styles_ = {};
26+
this.onAppStateChange_ = async () => {
27+
// Force an update to the notes list when app state changes
28+
const newProps = { ...this.props };
29+
newProps.notesSource = '';
30+
await this.refreshNotes(newProps);
31+
};
32+
this.sortButton_press = async () => {
33+
const buttons = [];
34+
const sortNoteOptions = Setting_1.default.enumOptions('notes.sortOrder.field');
35+
for (const field in sortNoteOptions) {
36+
if (!sortNoteOptions.hasOwnProperty(field)) { continue; }
37+
buttons.push({
38+
text: sortNoteOptions[field],
39+
iconChecked: 'fas fa-circle',
40+
checked: Setting_1.default.value('notes.sortOrder.field') === field,
41+
id: { name: 'notes.sortOrder.field', value: field },
42+
});
43+
}
44+
buttons.push({
45+
text: `[ ${Setting_1.default.settingMetadata('notes.sortOrder.reverse').label()} ]`,
46+
checked: Setting_1.default.value('notes.sortOrder.reverse'),
47+
id: { name: 'notes.sortOrder.reverse', value: !Setting_1.default.value('notes.sortOrder.reverse') },
48+
});
49+
buttons.push({
50+
text: `[ ${Setting_1.default.settingMetadata('uncompletedTodosOnTop').label()} ]`,
51+
checked: Setting_1.default.value('uncompletedTodosOnTop'),
52+
id: { name: 'uncompletedTodosOnTop', value: !Setting_1.default.value('uncompletedTodosOnTop') },
53+
});
54+
buttons.push({
55+
text: `[ ${Setting_1.default.settingMetadata('showCompletedTodos').label()} ]`,
56+
checked: Setting_1.default.value('showCompletedTodos'),
57+
id: { name: 'showCompletedTodos', value: !Setting_1.default.value('showCompletedTodos') },
58+
});
59+
const r = await this.props.dialogManager.showMenu(Setting_1.default.settingMetadata('notes.sortOrder.field').label(), buttons);
60+
if (!r) { return; }
61+
Setting_1.default.setValue(r.name, r.value);
62+
};
63+
this.newNoteNavigate = async (folderId, isTodo) => {
64+
try {
65+
const newNote = await Note_1.default.save({
66+
parent_id: folderId,
67+
is_todo: isTodo ? 1 : 0,
68+
}, { provisional: true });
69+
this.props.dispatch({
70+
type: 'NAV_GO',
71+
routeName: 'Note',
72+
noteId: newNote.id,
73+
});
74+
} catch (error) {
75+
alert((0, locale_1._)('Cannot create a new note: %s', error.message));
76+
}
77+
};
78+
}
79+
styles() {
80+
if (!this.styles_) { this.styles_ = {}; }
81+
const themeId = this.props.themeId;
82+
const cacheKey = themeId;
83+
if (this.styles_[cacheKey]) { return this.styles_[cacheKey]; }
84+
this.styles_ = {};
85+
const styles = {
86+
noteList: {
87+
flex: 1,
88+
},
89+
};
90+
this.styles_[cacheKey] = react_native_1.StyleSheet.create(styles);
91+
return this.styles_[cacheKey];
92+
}
93+
async componentDidMount() {
94+
await this.refreshNotes();
95+
this.onAppStateChangeSub_ = react_native_1.AppState.addEventListener('change', this.onAppStateChange_);
96+
}
97+
async componentWillUnmount() {
98+
if (this.onAppStateChangeSub_) { this.onAppStateChangeSub_.remove(); }
99+
}
100+
async componentDidUpdate(prevProps) {
101+
if (prevProps.notesOrder !== this.props.notesOrder || prevProps.selectedFolderId !== this.props.selectedFolderId || prevProps.selectedTagId !== this.props.selectedTagId || prevProps.selectedSmartFilterId !== this.props.selectedSmartFilterId || prevProps.notesParentType !== this.props.notesParentType || prevProps.uncompletedTodosOnTop !== this.props.uncompletedTodosOnTop || prevProps.showCompletedTodos !== this.props.showCompletedTodos) {
102+
await this.refreshNotes(this.props);
103+
}
104+
}
105+
async refreshNotes(props = null) {
106+
if (props === null) { props = this.props; }
107+
const options = {
108+
order: props.notesOrder,
109+
uncompletedTodosOnTop: props.uncompletedTodosOnTop,
110+
showCompletedTodos: props.showCompletedTodos,
111+
caseInsensitive: true,
112+
};
113+
const parent = this.parentItem(props);
114+
if (!parent) { return; }
115+
const source = JSON.stringify({
116+
options: options,
117+
parentId: parent.id,
118+
});
119+
if (source === props.notesSource) { return; }
120+
// For now, search refresh is handled by the search screen.
121+
if (props.notesParentType === 'Search') { return; }
122+
let notes = [];
123+
if (props.notesParentType === 'Folder') {
124+
notes = await Note_1.default.previews(props.selectedFolderId, options);
125+
} else if (props.notesParentType === 'Tag') {
126+
notes = await Tag_1.default.notes(props.selectedTagId, options);
127+
} else if (props.notesParentType === 'SmartFilter') {
128+
notes = await Note_1.default.previews(null, options);
129+
}
130+
this.props.dispatch({
131+
type: 'NOTE_UPDATE_ALL',
132+
notes: notes,
133+
notesSource: source,
134+
});
135+
}
136+
parentItem(props = null) {
137+
if (!props) { props = this.props; }
138+
let output = null;
139+
if (props.notesParentType === 'Folder') {
140+
output = Folder_1.default.byId(props.folders, props.selectedFolderId);
141+
} else if (props.notesParentType === 'Tag') {
142+
output = Tag_1.default.byId(props.tags, props.selectedTagId);
143+
} else if (props.notesParentType === 'SmartFilter') {
144+
output = { id: this.props.selectedSmartFilterId, title: (0, locale_1._)('All notes') };
145+
} else {
146+
return null;
147+
// throw new Error('Invalid parent type: ' + props.notesParentType);
148+
}
149+
return output;
150+
}
151+
folderPickerOptions() {
152+
const options = {
153+
visible: this.props.noteSelectionEnabled,
154+
mustSelect: true,
155+
};
156+
if (this.folderPickerOptions_ && options.visible === this.folderPickerOptions_.visible) { return this.folderPickerOptions_; }
157+
this.folderPickerOptions_ = options;
158+
return this.folderPickerOptions_;
159+
}
160+
render() {
161+
const parent = this.parentItem();
162+
const theme = (0, global_style_1.themeStyle)(this.props.themeId);
163+
const rootStyle = this.props.visible ? theme.rootStyle : theme.hiddenRootStyle;
164+
const title = parent ? parent.title : null;
165+
if (!parent) {
166+
return (React.createElement(react_native_1.View, { style: rootStyle },
167+
React.createElement(ScreenHeader_1.ScreenHeader, { title: title, showSideMenuButton: true, showBackButton: false })));
168+
}
169+
const icon = Folder_1.default.unserializeIcon(parent.icon);
170+
const iconString = icon ? `${icon.emoji} ` : '';
171+
let buttonFolderId = this.props.selectedFolderId !== Folder_1.default.conflictFolderId() ? this.props.selectedFolderId : null;
172+
if (!buttonFolderId) { buttonFolderId = this.props.activeFolderId; }
173+
const addFolderNoteButtons = !!buttonFolderId;
174+
const makeActionButtonComp = () => {
175+
if ((this.props.notesParentType === 'Folder' && (0, trash_1.itemIsInTrash)(parent)) || !Folder_1.default.atLeastOneRealFolderExists(this.props.folders)) { return null; }
176+
if (addFolderNoteButtons && this.props.folders.length > 0) {
177+
const buttons = [];
178+
buttons.push({
179+
label: (0, locale_1._)('New to-do'),
180+
onPress: async () => {
181+
const isTodo = true;
182+
void this.newNoteNavigate(buttonFolderId, isTodo);
183+
},
184+
color: '#9b59b6',
185+
icon: 'checkbox-outline',
186+
});
187+
buttons.push({
188+
label: (0, locale_1._)('New note'),
189+
onPress: async () => {
190+
const isTodo = false;
191+
void this.newNoteNavigate(buttonFolderId, isTodo);
192+
},
193+
color: '#9b59b6',
194+
icon: 'document',
195+
});
196+
return React.createElement(FloatingActionButton_1.default, { buttons: buttons, dispatch: this.props.dispatch });
197+
}
198+
return null;
199+
};
200+
const actionButtonComp = this.props.noteSelectionEnabled || !this.props.visible ? null : makeActionButtonComp();
201+
// Ensure that screen readers can't focus the notes list when it isn't visible.
202+
const accessibilityHidden = !this.props.visible;
203+
return (React.createElement(AccessibleView_1.default, { style: rootStyle, inert: accessibilityHidden },
204+
React.createElement(ScreenHeader_1.ScreenHeader, { title: iconString + title, showBackButton: false, sortButton_press: this.sortButton_press, folderPickerOptions: this.folderPickerOptions(), showSearchButton: true, showSideMenuButton: true }),
205+
React.createElement(NoteList_1.default, null),
206+
actionButtonComp));
207+
}
208+
}
209+
const NotesScreenWrapper = props => {
210+
const dialogManager = (0, react_1.useContext)(DialogManager_1.DialogContext);
211+
return React.createElement(NotesScreenComponent, { ...props, dialogManager: dialogManager });
212+
};
213+
const NotesScreen = (0, react_redux_1.connect)((state) => {
214+
return {
215+
folders: state.folders,
216+
tags: state.tags,
217+
activeFolderId: state.settings.activeFolderId,
218+
selectedFolderId: state.selectedFolderId,
219+
selectedNoteIds: state.selectedNoteIds,
220+
selectedTagId: state.selectedTagId,
221+
selectedSmartFilterId: state.selectedSmartFilterId,
222+
notesParentType: state.notesParentType,
223+
notes: state.notes,
224+
notesSource: state.notesSource,
225+
uncompletedTodosOnTop: state.settings.uncompletedTodosOnTop,
226+
showCompletedTodos: state.settings.showCompletedTodos,
227+
themeId: state.settings.theme,
228+
noteSelectionEnabled: state.noteSelectionEnabled,
229+
notesOrder: reducer_1.stateUtils.notesOrder(state.settings),
230+
};
231+
})(NotesScreenWrapper);
232+
exports.default = NotesScreen;
233+
// # sourceMappingURL=Notes.js.map

0 commit comments

Comments
 (0)