mirror of
https://github.com/laurent22/joplin.git
synced 2025-02-04 19:16:07 +02:00
parent
12bba9da29
commit
230e7f6914
@ -363,6 +363,7 @@ packages/app-mobile/components/CameraView.js
|
||||
packages/app-mobile/components/CustomButton.js
|
||||
packages/app-mobile/components/Dropdown.js
|
||||
packages/app-mobile/components/ExtendedWebView.js
|
||||
packages/app-mobile/components/FolderPicker.js
|
||||
packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.js
|
||||
packages/app-mobile/components/NoteBodyViewer/hooks/useOnMessage.js
|
||||
packages/app-mobile/components/NoteBodyViewer/hooks/useOnResourceLongPress.js
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -349,6 +349,7 @@ packages/app-mobile/components/CameraView.js
|
||||
packages/app-mobile/components/CustomButton.js
|
||||
packages/app-mobile/components/Dropdown.js
|
||||
packages/app-mobile/components/ExtendedWebView.js
|
||||
packages/app-mobile/components/FolderPicker.js
|
||||
packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.js
|
||||
packages/app-mobile/components/NoteBodyViewer/hooks/useOnMessage.js
|
||||
packages/app-mobile/components/NoteBodyViewer/hooks/useOnResourceLongPress.js
|
||||
|
90
packages/app-mobile/components/FolderPicker.tsx
Normal file
90
packages/app-mobile/components/FolderPicker.tsx
Normal file
@ -0,0 +1,90 @@
|
||||
const React = require('react');
|
||||
|
||||
import { FunctionComponent } from 'react';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import Folder, { FolderEntityWithChildren } from '@joplin/lib/models/Folder';
|
||||
const { themeStyle } = require('./global-style.js');
|
||||
import Dropdown, { DropdownListItem, OnValueChangedListener } from './Dropdown';
|
||||
import { FolderEntity } from '@joplin/lib/services/database/types';
|
||||
|
||||
interface FolderPickerProps {
|
||||
disabled?: boolean;
|
||||
selectedFolderId?: string;
|
||||
onValueChange?: OnValueChangedListener;
|
||||
mustSelect?: boolean;
|
||||
folders: FolderEntity[];
|
||||
placeholder?: string;
|
||||
darkText?: boolean;
|
||||
themeId?: string;
|
||||
}
|
||||
|
||||
|
||||
const FolderPicker: FunctionComponent<FolderPickerProps> = ({
|
||||
disabled,
|
||||
selectedFolderId,
|
||||
onValueChange,
|
||||
mustSelect,
|
||||
folders,
|
||||
placeholder,
|
||||
darkText,
|
||||
themeId,
|
||||
}) => {
|
||||
const theme = themeStyle(themeId);
|
||||
|
||||
const addFolderChildren = (
|
||||
folders: FolderEntityWithChildren[], pickerItems: DropdownListItem[], indent: number
|
||||
) => {
|
||||
folders.sort((a, b) => {
|
||||
const aTitle = a && a.title ? a.title : '';
|
||||
const bTitle = b && b.title ? b.title : '';
|
||||
return aTitle.toLowerCase() < bTitle.toLowerCase() ? -1 : +1;
|
||||
});
|
||||
|
||||
for (let i = 0; i < folders.length; i++) {
|
||||
const f = folders[i];
|
||||
const icon = Folder.unserializeIcon(f.icon);
|
||||
const iconString = icon ? `${icon.emoji} ` : '';
|
||||
pickerItems.push({ label: `${' '.repeat(indent)} ${iconString + Folder.displayTitle(f)}`, value: f.id });
|
||||
pickerItems = addFolderChildren(f.children, pickerItems, indent + 1);
|
||||
}
|
||||
|
||||
return pickerItems;
|
||||
};
|
||||
|
||||
const titlePickerItems = (mustSelect: boolean) => {
|
||||
const folderList = folders.filter(f => f.id !== Folder.conflictFolderId());
|
||||
let output = [];
|
||||
if (mustSelect) output.push({ label: placeholder || _('Move to notebook...'), value: '' });
|
||||
const folderTree = Folder.buildTree(folderList);
|
||||
output = addFolderChildren(folderTree, output, 0);
|
||||
return output;
|
||||
};
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
items={titlePickerItems(!!mustSelect)}
|
||||
disabled={disabled}
|
||||
labelTransform="trim"
|
||||
selectedValue={selectedFolderId || ''}
|
||||
itemListStyle={{
|
||||
backgroundColor: theme.backgroundColor,
|
||||
}}
|
||||
headerStyle={{
|
||||
color: darkText ? theme.colorFaded : theme.colorBright2,
|
||||
fontSize: theme.fontSize,
|
||||
opacity: disabled ? theme.disabledOpacity : 1,
|
||||
}}
|
||||
itemStyle={{
|
||||
color: theme.color,
|
||||
fontSize: theme.fontSize,
|
||||
}}
|
||||
onValueChange={(folderId) => {
|
||||
if (onValueChange) {
|
||||
onValueChange(folderId);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default FolderPicker;
|
@ -10,9 +10,9 @@ import { Menu, MenuOptions, MenuOption, MenuTrigger } from 'react-native-popup-m
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import Note from '@joplin/lib/models/Note';
|
||||
import Folder, { FolderEntityWithChildren } from '@joplin/lib/models/Folder';
|
||||
import Folder from '@joplin/lib/models/Folder';
|
||||
const { themeStyle } = require('./global-style.js');
|
||||
import Dropdown, { DropdownListItem, OnValueChangedListener } from './Dropdown';
|
||||
import { OnValueChangedListener } from './Dropdown';
|
||||
const { dialogs } = require('../utils/dialogs.js');
|
||||
const DialogBox = require('react-native-dialogbox').default;
|
||||
import { localSyncInfoFromState } from '@joplin/lib/services/synchronizer/syncInfoUtils';
|
||||
@ -20,6 +20,7 @@ import { showMissingMasterKeyMessage } from '@joplin/lib/services/e2ee/utils';
|
||||
import { FolderEntity } from '@joplin/lib/services/database/types';
|
||||
import { State } from '@joplin/lib/reducer';
|
||||
import CustomButton from './CustomButton';
|
||||
import FolderPicker from './FolderPicker';
|
||||
|
||||
// We need this to suppress the useless warning
|
||||
// https://github.com/oblador/react-native-vector-icons/issues/1465
|
||||
@ -494,58 +495,14 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade
|
||||
}
|
||||
|
||||
const createTitleComponent = (disabled: boolean) => {
|
||||
const themeId = Setting.value('theme');
|
||||
const theme = themeStyle(themeId);
|
||||
const folderPickerOptions = this.props.folderPickerOptions;
|
||||
|
||||
if (folderPickerOptions && folderPickerOptions.enabled) {
|
||||
const addFolderChildren = (
|
||||
folders: FolderEntityWithChildren[], pickerItems: DropdownListItem[], indent: number
|
||||
) => {
|
||||
folders.sort((a, b) => {
|
||||
const aTitle = a && a.title ? a.title : '';
|
||||
const bTitle = b && b.title ? b.title : '';
|
||||
return aTitle.toLowerCase() < bTitle.toLowerCase() ? -1 : +1;
|
||||
});
|
||||
|
||||
for (let i = 0; i < folders.length; i++) {
|
||||
const f = folders[i];
|
||||
const icon = Folder.unserializeIcon(f.icon);
|
||||
const iconString = icon ? `${icon.emoji} ` : '';
|
||||
pickerItems.push({ label: `${' '.repeat(indent)} ${iconString + Folder.displayTitle(f)}`, value: f.id });
|
||||
pickerItems = addFolderChildren(f.children, pickerItems, indent + 1);
|
||||
}
|
||||
|
||||
return pickerItems;
|
||||
};
|
||||
|
||||
const titlePickerItems = (mustSelect: boolean) => {
|
||||
const folders = this.props.folders.filter(f => f.id !== Folder.conflictFolderId());
|
||||
let output = [];
|
||||
if (mustSelect) output.push({ label: _('Move to notebook...'), value: null });
|
||||
const folderTree = Folder.buildTree(folders);
|
||||
output = addFolderChildren(folderTree, output, 0);
|
||||
return output;
|
||||
};
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
items={titlePickerItems(!!folderPickerOptions.mustSelect)}
|
||||
<FolderPicker
|
||||
themeId={themeId}
|
||||
disabled={disabled}
|
||||
labelTransform="trim"
|
||||
selectedValue={'selectedFolderId' in folderPickerOptions ? folderPickerOptions.selectedFolderId : null}
|
||||
itemListStyle={{
|
||||
backgroundColor: theme.backgroundColor,
|
||||
}}
|
||||
headerStyle={{
|
||||
color: theme.colorBright2,
|
||||
fontSize: theme.fontSize,
|
||||
opacity: disabled ? theme.disabledOpacity : 1,
|
||||
}}
|
||||
itemStyle={{
|
||||
color: theme.color,
|
||||
fontSize: theme.fontSize,
|
||||
}}
|
||||
selectedFolderId={'selectedFolderId' in folderPickerOptions ? folderPickerOptions.selectedFolderId : null}
|
||||
onValueChange={async (folderId) => {
|
||||
// If onValueChange is specified, use this as a callback, otherwise do the default
|
||||
// which is to take the selectedNoteIds from the state and move them to the
|
||||
@ -570,6 +527,8 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade
|
||||
await Note.moveToFolder(noteIds[i], folderId);
|
||||
}
|
||||
}}
|
||||
mustSelect={!!folderPickerOptions.mustSelect}
|
||||
folders={this.props.folders}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
|
@ -1,6 +1,6 @@
|
||||
const React = require('react');
|
||||
|
||||
const { View } = require('react-native');
|
||||
const { View, StyleSheet } = require('react-native');
|
||||
const { connect } = require('react-redux');
|
||||
const Folder = require('@joplin/lib/models/Folder').default;
|
||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||
@ -8,6 +8,7 @@ const { ScreenHeader } = require('../ScreenHeader');
|
||||
const { BaseScreenComponent } = require('../base-screen.js');
|
||||
const { dialogs } = require('../../utils/dialogs.js');
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const { default: FolderPicker } = require('../FolderPicker');
|
||||
const TextInput = require('../TextInput').default;
|
||||
|
||||
class FolderScreenComponent extends BaseScreenComponent {
|
||||
@ -60,10 +61,16 @@ class FolderScreenComponent extends BaseScreenComponent {
|
||||
this.folderComponent_change('title', text);
|
||||
}
|
||||
|
||||
parent_changeValue(parent) {
|
||||
this.folderComponent_change('parent_id', parent);
|
||||
}
|
||||
|
||||
|
||||
async saveFolderButton_press() {
|
||||
let folder = Object.assign({}, this.state.folder);
|
||||
|
||||
try {
|
||||
if (folder.id && !(await Folder.canNestUnder(folder.id, folder.parent_id))) throw new Error(_('Cannot move notebook to this location'));
|
||||
folder = await Folder.save(folder, { userSideValidation: true });
|
||||
} catch (error) {
|
||||
dialogs.error(this, _('The notebook could not be saved: %s', error.message));
|
||||
@ -83,7 +90,7 @@ class FolderScreenComponent extends BaseScreenComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const saveButtonDisabled = !this.isModified();
|
||||
const saveButtonDisabled = !this.isModified() || !this.state.folder.title;
|
||||
|
||||
return (
|
||||
<View style={this.rootStyle(this.props.themeId).root}>
|
||||
@ -94,7 +101,20 @@ class FolderScreenComponent extends BaseScreenComponent {
|
||||
autoFocus={true}
|
||||
value={this.state.folder.title}
|
||||
onChangeText={text => this.title_changeText(text)}
|
||||
disabled={this.state.folder.encryption_applied}
|
||||
/>
|
||||
<View style={styles.folderPickerContainer}>
|
||||
<FolderPicker
|
||||
themeId={this.props.themeId}
|
||||
placeholder={_('Select parent notebook')}
|
||||
folders={this.props.folders}
|
||||
selectedFolderId={this.state.folder.parent_id}
|
||||
onValueChange={newValue => this.parent_changeValue(newValue)}
|
||||
mustSelect
|
||||
darkText
|
||||
/>
|
||||
</View>
|
||||
<View style={{ flex: 1 }} />
|
||||
<dialogs.DialogBox
|
||||
ref={dialogbox => {
|
||||
this.dialogbox = dialogbox;
|
||||
@ -109,7 +129,18 @@ const FolderScreen = connect(state => {
|
||||
return {
|
||||
folderId: state.selectedFolderId,
|
||||
themeId: state.settings.theme,
|
||||
folders: state.folders.filter((folder) => folder.id !== state.selectedFolderId),
|
||||
};
|
||||
})(FolderScreenComponent);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
folderPickerContainer: {
|
||||
height: 46,
|
||||
paddingLeft: 14,
|
||||
paddingRight: 14,
|
||||
paddingTop: 12,
|
||||
paddingBottom: 12,
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = { FolderScreen };
|
||||
|
@ -140,13 +140,8 @@ const SideMenuContentComponent = (props: Props) => {
|
||||
_('Notebook: %s', folder.title),
|
||||
[
|
||||
{
|
||||
text: _('Rename'),
|
||||
text: _('Edit'),
|
||||
onPress: () => {
|
||||
if (folder.encryption_applied) {
|
||||
alert(_('Encrypted notebooks cannot be renamed'));
|
||||
return;
|
||||
}
|
||||
|
||||
props.dispatch({ type: 'SIDE_MENU_CLOSE' });
|
||||
|
||||
props.dispatch({
|
||||
|
Loading…
x
Reference in New Issue
Block a user