mirror of
https://github.com/laurent22/joplin.git
synced 2024-11-27 08:21:03 +02:00
Desktop: UI update (#3586)
This commit is contained in:
parent
bdedf69439
commit
056285deda
@ -69,8 +69,12 @@ ElectronClient/commands/focusElement.js
|
||||
ElectronClient/commands/startExternalEditing.js
|
||||
ElectronClient/commands/stopExternalEditing.js
|
||||
ElectronClient/global.d.js
|
||||
ElectronClient/gui/Button/Button.js
|
||||
ElectronClient/gui/ConfigScreen/ButtonBar.js
|
||||
ElectronClient/gui/ConfigScreen/ConfigScreen.js
|
||||
ElectronClient/gui/ConfigScreen/SideBar.js
|
||||
ElectronClient/gui/DropboxLoginScreen.js
|
||||
ElectronClient/gui/ErrorBoundary.js
|
||||
ElectronClient/gui/Header/commands/focusSearch.js
|
||||
ElectronClient/gui/KeymapConfig/KeymapConfigScreen.js
|
||||
ElectronClient/gui/KeymapConfig/ShortcutRecorder.js
|
||||
ElectronClient/gui/KeymapConfig/styles/index.js
|
||||
@ -81,8 +85,8 @@ ElectronClient/gui/MainScreen/commands/editAlarm.js
|
||||
ElectronClient/gui/MainScreen/commands/exportPdf.js
|
||||
ElectronClient/gui/MainScreen/commands/hideModalMessage.js
|
||||
ElectronClient/gui/MainScreen/commands/moveToFolder.js
|
||||
ElectronClient/gui/MainScreen/commands/newFolder.js
|
||||
ElectronClient/gui/MainScreen/commands/newNote.js
|
||||
ElectronClient/gui/MainScreen/commands/newNotebook.js
|
||||
ElectronClient/gui/MainScreen/commands/newTodo.js
|
||||
ElectronClient/gui/MainScreen/commands/print.js
|
||||
ElectronClient/gui/MainScreen/commands/renameFolder.js
|
||||
@ -94,9 +98,11 @@ ElectronClient/gui/MainScreen/commands/showModalMessage.js
|
||||
ElectronClient/gui/MainScreen/commands/showNoteContentProperties.js
|
||||
ElectronClient/gui/MainScreen/commands/showNoteProperties.js
|
||||
ElectronClient/gui/MainScreen/commands/showShareNoteDialog.js
|
||||
ElectronClient/gui/MainScreen/commands/toggleEditors.js
|
||||
ElectronClient/gui/MainScreen/commands/toggleNoteList.js
|
||||
ElectronClient/gui/MainScreen/commands/toggleSidebar.js
|
||||
ElectronClient/gui/MainScreen/commands/toggleVisiblePanes.js
|
||||
ElectronClient/gui/MainScreen/MainScreen.js
|
||||
ElectronClient/gui/MultiNoteActions.js
|
||||
ElectronClient/gui/NoteContentPropertiesDialog.js
|
||||
ElectronClient/gui/NoteEditor/commands/editorCommandDeclarations.js
|
||||
@ -116,6 +122,7 @@ ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useJoplinMode.js
|
||||
ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useLineSorting.js
|
||||
ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useListIdent.js
|
||||
ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useScrollUtils.js
|
||||
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/styles/index.js
|
||||
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.js
|
||||
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/utils/useScroll.js
|
||||
ElectronClient/gui/NoteEditor/NoteEditor.js
|
||||
@ -125,19 +132,40 @@ ElectronClient/gui/NoteEditor/utils/index.js
|
||||
ElectronClient/gui/NoteEditor/utils/resourceHandling.js
|
||||
ElectronClient/gui/NoteEditor/utils/types.js
|
||||
ElectronClient/gui/NoteEditor/utils/useDropHandler.js
|
||||
ElectronClient/gui/NoteEditor/utils/useFolder.js
|
||||
ElectronClient/gui/NoteEditor/utils/useFormNote.js
|
||||
ElectronClient/gui/NoteEditor/utils/useMarkupToHtml.js
|
||||
ElectronClient/gui/NoteEditor/utils/useMessageHandler.js
|
||||
ElectronClient/gui/NoteEditor/utils/useNoteSearchBar.js
|
||||
ElectronClient/gui/NoteEditor/utils/useNoteToolbarButtons.js
|
||||
ElectronClient/gui/NoteEditor/utils/useSearchMarkers.js
|
||||
ElectronClient/gui/NoteEditor/utils/useWindowCommandHandler.js
|
||||
ElectronClient/gui/NoteList/commands/focusElementNoteList.js
|
||||
ElectronClient/gui/NoteList/NoteList.js
|
||||
ElectronClient/gui/NoteListControls/commands/focusSearch.js
|
||||
ElectronClient/gui/NoteListControls/NoteListControls.js
|
||||
ElectronClient/gui/NoteListItem.js
|
||||
ElectronClient/gui/NoteToolbar/NoteToolbar.js
|
||||
ElectronClient/gui/OneDriveLoginScreen.js
|
||||
ElectronClient/gui/ResizableLayout/hooks/useLayoutItemSizes.js
|
||||
ElectronClient/gui/ResizableLayout/hooks/useWindowResizeEvent.js
|
||||
ElectronClient/gui/ResizableLayout/ResizableLayout.js
|
||||
ElectronClient/gui/ResourceScreen.js
|
||||
ElectronClient/gui/Root_UpgradeSyncTarget.js
|
||||
ElectronClient/gui/SearchBar/hooks/useSearch.js
|
||||
ElectronClient/gui/SearchBar/SearchBar.js
|
||||
ElectronClient/gui/SearchBar/styles/index.js
|
||||
ElectronClient/gui/ShareNoteDialog.js
|
||||
ElectronClient/gui/SideBar/commands/focusElementSideBar.js
|
||||
ElectronClient/gui/SideBar/SideBar.js
|
||||
ElectronClient/gui/SideBar/styles/index.js
|
||||
ElectronClient/gui/StatusScreen/StatusScreen.js
|
||||
ElectronClient/gui/style/StyledInput.js
|
||||
ElectronClient/gui/style/StyledTextInput.js
|
||||
ElectronClient/gui/ToggleEditorsButton/styles/index.js
|
||||
ElectronClient/gui/ToggleEditorsButton/ToggleEditorsButton.js
|
||||
ElectronClient/gui/ToolbarButton/styles/index.js
|
||||
ElectronClient/gui/ToolbarButton/ToolbarButton.js
|
||||
ReactNativeClient/lib/AsyncActionQueue.js
|
||||
ReactNativeClient/lib/checkPermissions.js
|
||||
ReactNativeClient/lib/commands/historyBackward.js
|
||||
@ -176,6 +204,16 @@ ReactNativeClient/lib/services/synchronizer/utils/types.js
|
||||
ReactNativeClient/lib/services/UndoRedoService.js
|
||||
ReactNativeClient/lib/ShareExtension.js
|
||||
ReactNativeClient/lib/shareHandler.js
|
||||
ReactNativeClient/lib/theme.js
|
||||
ReactNativeClient/lib/themes/aritimDark.js
|
||||
ReactNativeClient/lib/themes/dark.js
|
||||
ReactNativeClient/lib/themes/dracula.js
|
||||
ReactNativeClient/lib/themes/light.js
|
||||
ReactNativeClient/lib/themes/nord.js
|
||||
ReactNativeClient/lib/themes/oledDark.js
|
||||
ReactNativeClient/lib/themes/solarizedDark.js
|
||||
ReactNativeClient/lib/themes/solarizedLight.js
|
||||
ReactNativeClient/lib/themes/type.js
|
||||
ReactNativeClient/lib/versionInfo.js
|
||||
ReactNativeClient/PluginAssetsLoader.js
|
||||
ReactNativeClient/setUpQuickActions.js
|
||||
|
42
.gitignore
vendored
42
.gitignore
vendored
@ -62,8 +62,12 @@ ElectronClient/commands/focusElement.js
|
||||
ElectronClient/commands/startExternalEditing.js
|
||||
ElectronClient/commands/stopExternalEditing.js
|
||||
ElectronClient/global.d.js
|
||||
ElectronClient/gui/Button/Button.js
|
||||
ElectronClient/gui/ConfigScreen/ButtonBar.js
|
||||
ElectronClient/gui/ConfigScreen/ConfigScreen.js
|
||||
ElectronClient/gui/ConfigScreen/SideBar.js
|
||||
ElectronClient/gui/DropboxLoginScreen.js
|
||||
ElectronClient/gui/ErrorBoundary.js
|
||||
ElectronClient/gui/Header/commands/focusSearch.js
|
||||
ElectronClient/gui/KeymapConfig/KeymapConfigScreen.js
|
||||
ElectronClient/gui/KeymapConfig/ShortcutRecorder.js
|
||||
ElectronClient/gui/KeymapConfig/styles/index.js
|
||||
@ -74,8 +78,8 @@ ElectronClient/gui/MainScreen/commands/editAlarm.js
|
||||
ElectronClient/gui/MainScreen/commands/exportPdf.js
|
||||
ElectronClient/gui/MainScreen/commands/hideModalMessage.js
|
||||
ElectronClient/gui/MainScreen/commands/moveToFolder.js
|
||||
ElectronClient/gui/MainScreen/commands/newFolder.js
|
||||
ElectronClient/gui/MainScreen/commands/newNote.js
|
||||
ElectronClient/gui/MainScreen/commands/newNotebook.js
|
||||
ElectronClient/gui/MainScreen/commands/newTodo.js
|
||||
ElectronClient/gui/MainScreen/commands/print.js
|
||||
ElectronClient/gui/MainScreen/commands/renameFolder.js
|
||||
@ -87,9 +91,11 @@ ElectronClient/gui/MainScreen/commands/showModalMessage.js
|
||||
ElectronClient/gui/MainScreen/commands/showNoteContentProperties.js
|
||||
ElectronClient/gui/MainScreen/commands/showNoteProperties.js
|
||||
ElectronClient/gui/MainScreen/commands/showShareNoteDialog.js
|
||||
ElectronClient/gui/MainScreen/commands/toggleEditors.js
|
||||
ElectronClient/gui/MainScreen/commands/toggleNoteList.js
|
||||
ElectronClient/gui/MainScreen/commands/toggleSidebar.js
|
||||
ElectronClient/gui/MainScreen/commands/toggleVisiblePanes.js
|
||||
ElectronClient/gui/MainScreen/MainScreen.js
|
||||
ElectronClient/gui/MultiNoteActions.js
|
||||
ElectronClient/gui/NoteContentPropertiesDialog.js
|
||||
ElectronClient/gui/NoteEditor/commands/editorCommandDeclarations.js
|
||||
@ -109,6 +115,7 @@ ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useJoplinMode.js
|
||||
ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useLineSorting.js
|
||||
ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useListIdent.js
|
||||
ElectronClient/gui/NoteEditor/NoteBody/CodeMirror/utils/useScrollUtils.js
|
||||
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/styles/index.js
|
||||
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.js
|
||||
ElectronClient/gui/NoteEditor/NoteBody/TinyMCE/utils/useScroll.js
|
||||
ElectronClient/gui/NoteEditor/NoteEditor.js
|
||||
@ -118,19 +125,40 @@ ElectronClient/gui/NoteEditor/utils/index.js
|
||||
ElectronClient/gui/NoteEditor/utils/resourceHandling.js
|
||||
ElectronClient/gui/NoteEditor/utils/types.js
|
||||
ElectronClient/gui/NoteEditor/utils/useDropHandler.js
|
||||
ElectronClient/gui/NoteEditor/utils/useFolder.js
|
||||
ElectronClient/gui/NoteEditor/utils/useFormNote.js
|
||||
ElectronClient/gui/NoteEditor/utils/useMarkupToHtml.js
|
||||
ElectronClient/gui/NoteEditor/utils/useMessageHandler.js
|
||||
ElectronClient/gui/NoteEditor/utils/useNoteSearchBar.js
|
||||
ElectronClient/gui/NoteEditor/utils/useNoteToolbarButtons.js
|
||||
ElectronClient/gui/NoteEditor/utils/useSearchMarkers.js
|
||||
ElectronClient/gui/NoteEditor/utils/useWindowCommandHandler.js
|
||||
ElectronClient/gui/NoteList/commands/focusElementNoteList.js
|
||||
ElectronClient/gui/NoteList/NoteList.js
|
||||
ElectronClient/gui/NoteListControls/commands/focusSearch.js
|
||||
ElectronClient/gui/NoteListControls/NoteListControls.js
|
||||
ElectronClient/gui/NoteListItem.js
|
||||
ElectronClient/gui/NoteToolbar/NoteToolbar.js
|
||||
ElectronClient/gui/OneDriveLoginScreen.js
|
||||
ElectronClient/gui/ResizableLayout/hooks/useLayoutItemSizes.js
|
||||
ElectronClient/gui/ResizableLayout/hooks/useWindowResizeEvent.js
|
||||
ElectronClient/gui/ResizableLayout/ResizableLayout.js
|
||||
ElectronClient/gui/ResourceScreen.js
|
||||
ElectronClient/gui/Root_UpgradeSyncTarget.js
|
||||
ElectronClient/gui/SearchBar/hooks/useSearch.js
|
||||
ElectronClient/gui/SearchBar/SearchBar.js
|
||||
ElectronClient/gui/SearchBar/styles/index.js
|
||||
ElectronClient/gui/ShareNoteDialog.js
|
||||
ElectronClient/gui/SideBar/commands/focusElementSideBar.js
|
||||
ElectronClient/gui/SideBar/SideBar.js
|
||||
ElectronClient/gui/SideBar/styles/index.js
|
||||
ElectronClient/gui/StatusScreen/StatusScreen.js
|
||||
ElectronClient/gui/style/StyledInput.js
|
||||
ElectronClient/gui/style/StyledTextInput.js
|
||||
ElectronClient/gui/ToggleEditorsButton/styles/index.js
|
||||
ElectronClient/gui/ToggleEditorsButton/ToggleEditorsButton.js
|
||||
ElectronClient/gui/ToolbarButton/styles/index.js
|
||||
ElectronClient/gui/ToolbarButton/ToolbarButton.js
|
||||
ReactNativeClient/lib/AsyncActionQueue.js
|
||||
ReactNativeClient/lib/checkPermissions.js
|
||||
ReactNativeClient/lib/commands/historyBackward.js
|
||||
@ -169,6 +197,16 @@ ReactNativeClient/lib/services/synchronizer/utils/types.js
|
||||
ReactNativeClient/lib/services/UndoRedoService.js
|
||||
ReactNativeClient/lib/ShareExtension.js
|
||||
ReactNativeClient/lib/shareHandler.js
|
||||
ReactNativeClient/lib/theme.js
|
||||
ReactNativeClient/lib/themes/aritimDark.js
|
||||
ReactNativeClient/lib/themes/dark.js
|
||||
ReactNativeClient/lib/themes/dracula.js
|
||||
ReactNativeClient/lib/themes/light.js
|
||||
ReactNativeClient/lib/themes/nord.js
|
||||
ReactNativeClient/lib/themes/oledDark.js
|
||||
ReactNativeClient/lib/themes/solarizedDark.js
|
||||
ReactNativeClient/lib/themes/solarizedLight.js
|
||||
ReactNativeClient/lib/themes/type.js
|
||||
ReactNativeClient/lib/versionInfo.js
|
||||
ReactNativeClient/PluginAssetsLoader.js
|
||||
ReactNativeClient/setUpQuickActions.js
|
||||
|
3
BUILD.md
3
BUILD.md
@ -11,6 +11,7 @@ Note that all the applications share the same library, which, for historical rea
|
||||
- macOS, Linux: Install rsync - https://nodejs.org/en/
|
||||
- macOS: Install Cocoapods - `brew install cocoapods`
|
||||
- Windows: Install Windows Build Tools - `npm install -g windows-build-tools`
|
||||
- Linux: Install dependencies - `sudo apt install libnss3 libsecret-1-dev`
|
||||
|
||||
## Building
|
||||
|
||||
@ -25,6 +26,8 @@ Then you can test the various applications:
|
||||
cd ElectronClient
|
||||
npm start
|
||||
|
||||
You can also run it under WSL 2. To do so, [follow these instructions](https://www.beekeeperstudio.io/blog/building-electron-windows-ubuntu-wsl2) to setup your environment.
|
||||
|
||||
## Testing the Terminal application
|
||||
|
||||
cd CliClient
|
||||
|
@ -26,10 +26,11 @@ describe('timeUtils', function() {
|
||||
startDate = new Date('3 Aug 2020 07:30:20');
|
||||
expect(time.goBackInTime(startDate, 1, 'day')).toBe(endDate.getTime().toString());
|
||||
|
||||
// Note: this test randomly fails - https://github.com/laurent22/joplin/issues/3722
|
||||
|
||||
startDate = new Date('11 Aug 2020');
|
||||
endDate = new Date('9 Aug 2020'); // week start;
|
||||
expect(time.goBackInTime(startDate, 0, 'week')).toBe(endDate.getTime().toString());
|
||||
// startDate = new Date('11 Aug 2020');
|
||||
// endDate = new Date('9 Aug 2020'); // week start;
|
||||
// expect(time.goBackInTime(startDate, 0, 'week')).toBe(endDate.getTime().toString());
|
||||
|
||||
startDate = new Date('02 Feb 2020');
|
||||
endDate = new Date('01 Jan 2020');
|
||||
|
@ -37,13 +37,13 @@ const resourceEditWatcherReducer = require('lib/services/ResourceEditWatcher/red
|
||||
const versionInfo = require('lib/versionInfo').default;
|
||||
|
||||
const commands = [
|
||||
require('./gui/Header/commands/focusSearch'),
|
||||
require('./gui/NoteListControls/commands/focusSearch'),
|
||||
require('./gui/MainScreen/commands/editAlarm'),
|
||||
require('./gui/MainScreen/commands/exportPdf'),
|
||||
require('./gui/MainScreen/commands/hideModalMessage'),
|
||||
require('./gui/MainScreen/commands/moveToFolder'),
|
||||
require('./gui/MainScreen/commands/newNote'),
|
||||
require('./gui/MainScreen/commands/newNotebook'),
|
||||
require('./gui/MainScreen/commands/newFolder'),
|
||||
require('./gui/MainScreen/commands/newTodo'),
|
||||
require('./gui/MainScreen/commands/print'),
|
||||
require('./gui/MainScreen/commands/renameFolder'),
|
||||
@ -58,6 +58,7 @@ const commands = [
|
||||
require('./gui/MainScreen/commands/toggleNoteList'),
|
||||
require('./gui/MainScreen/commands/toggleSidebar'),
|
||||
require('./gui/MainScreen/commands/toggleVisiblePanes'),
|
||||
require('./gui/MainScreen/commands/toggleEditors'),
|
||||
require('./gui/NoteEditor/commands/focusElementNoteBody'),
|
||||
require('./gui/NoteEditor/commands/focusElementNoteTitle'),
|
||||
require('./gui/NoteEditor/commands/showLocalSearch'),
|
||||
@ -286,10 +287,11 @@ class Application extends BaseApplication {
|
||||
}
|
||||
|
||||
newState = resourceEditWatcherReducer(newState, action);
|
||||
newState = super.reducer(newState, action);
|
||||
|
||||
CommandService.instance().scheduleMapStateToProps(newState);
|
||||
|
||||
return super.reducer(newState, action);
|
||||
return newState;
|
||||
}
|
||||
|
||||
toggleDevTools(visible) {
|
||||
@ -519,7 +521,7 @@ class Application extends BaseApplication {
|
||||
|
||||
const newNoteItem = cmdService.commandToMenuItem('newNote');
|
||||
const newTodoItem = cmdService.commandToMenuItem('newTodo');
|
||||
const newNotebookItem = cmdService.commandToMenuItem('newNotebook');
|
||||
const newFolderItem = cmdService.commandToMenuItem('newFolder');
|
||||
const printItem = cmdService.commandToMenuItem('print');
|
||||
|
||||
toolsItemsFirst.push(syncStatusItem, {
|
||||
@ -650,7 +652,7 @@ class Application extends BaseApplication {
|
||||
},
|
||||
shim.isMac() ? noItem : newNoteItem,
|
||||
shim.isMac() ? noItem : newTodoItem,
|
||||
shim.isMac() ? noItem : newNotebookItem, {
|
||||
shim.isMac() ? noItem : newFolderItem, {
|
||||
type: 'separator',
|
||||
visible: shim.isMac() ? false : true,
|
||||
}, {
|
||||
@ -699,7 +701,7 @@ class Application extends BaseApplication {
|
||||
submenu: [
|
||||
newNoteItem,
|
||||
newTodoItem,
|
||||
newNotebookItem, {
|
||||
newFolderItem, {
|
||||
label: _('Close Window'),
|
||||
platforms: ['darwin'],
|
||||
accelerator: shim.isMac() && keymapService.getAccelerator('closeWindow'),
|
||||
|
@ -11,7 +11,7 @@ interface Props {
|
||||
export const declaration:CommandDeclaration = {
|
||||
name: 'startExternalEditing',
|
||||
label: () => _('Edit in external editor'),
|
||||
iconName: 'fa-share-square',
|
||||
iconName: 'icon-share',
|
||||
};
|
||||
|
||||
export const runtime = ():CommandRuntime => {
|
||||
|
195
ElectronClient/gui/Button/Button.tsx
Normal file
195
ElectronClient/gui/Button/Button.tsx
Normal file
@ -0,0 +1,195 @@
|
||||
import * as React from 'react';
|
||||
const styled = require('styled-components').default;
|
||||
const { space } = require('styled-system');
|
||||
|
||||
export enum ButtonLevel {
|
||||
Primary = 'primary',
|
||||
Secondary = 'secondary',
|
||||
Tertiary = 'tertiary',
|
||||
SideBarSecondary = 'sideBarSecondary',
|
||||
}
|
||||
|
||||
interface Props {
|
||||
title?: string,
|
||||
iconName?: string,
|
||||
level?: ButtonLevel,
|
||||
className?:string,
|
||||
onClick:Function,
|
||||
color?: string,
|
||||
iconAnimation?: string,
|
||||
tooltip?: string,
|
||||
disabled?: boolean,
|
||||
style?:any,
|
||||
}
|
||||
|
||||
const StyledTitle = styled.span`
|
||||
|
||||
`;
|
||||
|
||||
const StyledButtonBase = styled.button`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
height: ${(props:any) => `${props.theme.toolbarHeight}px`};
|
||||
min-height: ${(props:any) => `${props.theme.toolbarHeight}px`};
|
||||
max-height: ${(props:any) => `${props.theme.toolbarHeight}px`};
|
||||
width: ${(props:any) => props.iconOnly ? `${props.theme.toolbarHeight}px` : 'auto'};
|
||||
${(props:any) => props.iconOnly ? `min-width: ${props.theme.toolbarHeight}px;` : ''}
|
||||
${(props:any) => !props.iconOnly ? 'min-width: 100px;' : ''}
|
||||
${(props:any) => props.iconOnly ? `max-width: ${props.theme.toolbarHeight}px;` : ''}
|
||||
box-sizing: border-box;
|
||||
border-radius: 3px;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
font-size: ${(props:any) => props.theme.fontSize}px;
|
||||
padding: 0 ${(props:any) => props.iconOnly ? 4 : 8}px;
|
||||
justify-content: center;
|
||||
opacity: ${(props:any) => props.disabled ? 0.5 : 1};
|
||||
user-select: none;
|
||||
`;
|
||||
|
||||
const StyledIcon = styled(styled.span(space))`
|
||||
font-size: ${(props:any) => props.theme.toolbarIconSize}px;
|
||||
${(props:any) => props.animation ? `animation: ${props.animation}` : ''};
|
||||
`;
|
||||
|
||||
const StyledButtonPrimary = styled(StyledButtonBase)`
|
||||
border: none;
|
||||
background-color: ${(props:any) => props.theme.backgroundColor5};
|
||||
|
||||
&:hover {
|
||||
background-color: ${(props:any) => props.theme.backgroundColorHover5};
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: ${(props:any) => props.theme.backgroundColorActive5};
|
||||
}
|
||||
|
||||
${StyledIcon} {
|
||||
color: ${(props:any) => props.theme.color5};
|
||||
}
|
||||
|
||||
${StyledTitle} {
|
||||
color: ${(props:any) => props.theme.color5};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledButtonSecondary = styled(StyledButtonBase)`
|
||||
border: 1px solid ${(props:any) => props.theme.borderColor4};
|
||||
background-color: ${(props:any) => props.theme.backgroundColor4};
|
||||
|
||||
&:hover {
|
||||
background-color: ${(props:any) => props.theme.backgroundColorHover4};
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: ${(props:any) => props.theme.backgroundColorActive4};
|
||||
}
|
||||
|
||||
${StyledIcon} {
|
||||
color: ${(props:any) => props.theme.color4};
|
||||
}
|
||||
|
||||
${StyledTitle} {
|
||||
color: ${(props:any) => props.theme.color4};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledButtonTertiary = styled(StyledButtonBase)`
|
||||
border: 1px solid ${(props:any) => props.theme.color3};
|
||||
background-color: ${(props:any) => props.theme.backgroundColor3};
|
||||
|
||||
&:hover {
|
||||
background-color: ${(props:any) => props.theme.backgroundColorHoverDim3};
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: ${(props:any) => props.theme.backgroundColorActive3};
|
||||
}
|
||||
|
||||
${StyledIcon} {
|
||||
color: ${(props:any) => props.theme.color};
|
||||
}
|
||||
|
||||
${StyledTitle} {
|
||||
color: ${(props:any) => props.theme.color};
|
||||
opacity: 0.9;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledButtonSideBarSecondary = styled(StyledButtonBase)`
|
||||
background: none;
|
||||
border-color: ${(props:any) => props.theme.color2};
|
||||
color: ${(props:any) => props.theme.color2};
|
||||
|
||||
&:hover {
|
||||
color: ${(props:any) => props.theme.colorHover2};
|
||||
border-color: ${(props:any) => props.theme.colorHover2};
|
||||
background: none;
|
||||
|
||||
${StyledTitle} {
|
||||
color: ${(props:any) => props.theme.colorHover2};
|
||||
}
|
||||
|
||||
${StyledIcon} {
|
||||
color: ${(props:any) => props.theme.colorHover2};
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
color: ${(props:any) => props.theme.colorActive2};
|
||||
border-color: ${(props:any) => props.theme.colorActive2};
|
||||
background: none;
|
||||
|
||||
${StyledTitle} {
|
||||
color: ${(props:any) => props.theme.colorActive2};
|
||||
}
|
||||
|
||||
${StyledIcon} {
|
||||
color: ${(props:any) => props.theme.colorActive2};
|
||||
}
|
||||
}
|
||||
|
||||
${StyledTitle} {
|
||||
color: ${(props:any) => props.theme.color2};
|
||||
}
|
||||
|
||||
${StyledIcon} {
|
||||
color: ${(props:any) => props.theme.color2};
|
||||
}
|
||||
`;
|
||||
|
||||
function buttonClass(level:ButtonLevel) {
|
||||
if (level === ButtonLevel.Primary) return StyledButtonPrimary;
|
||||
if (level === ButtonLevel.Tertiary) return StyledButtonTertiary;
|
||||
if (level === ButtonLevel.SideBarSecondary) return StyledButtonSideBarSecondary;
|
||||
return StyledButtonSecondary;
|
||||
}
|
||||
|
||||
export default function Button(props:Props) {
|
||||
const iconOnly = props.iconName && !props.title;
|
||||
|
||||
const StyledButton = buttonClass(props.level);
|
||||
|
||||
function renderIcon() {
|
||||
if (!props.iconName) return null;
|
||||
return <StyledIcon animation={props.iconAnimation} mr={iconOnly ? '0' : '6px'} color={props.color} className={props.iconName}/>;
|
||||
}
|
||||
|
||||
function renderTitle() {
|
||||
if (!props.title) return null;
|
||||
return <StyledTitle color={props.color}>{props.title}</StyledTitle>;
|
||||
}
|
||||
|
||||
function onClick() {
|
||||
if (props.disabled) return;
|
||||
props.onClick();
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledButton style={props.style} disabled={props.disabled} title={props.tooltip} className={props.className} iconOnly={iconOnly} onClick={onClick}>
|
||||
{renderIcon()}
|
||||
{renderTitle()}
|
||||
</StyledButton>
|
||||
);
|
||||
}
|
@ -40,10 +40,12 @@ class ClipperConfigScreenComponent extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
const containerStyle = Object.assign({}, theme.containerStyle, {
|
||||
overflowY: 'scroll',
|
||||
padding: theme.configScreenPadding,
|
||||
backgroundColor: theme.backgroundColor3,
|
||||
});
|
||||
|
||||
const buttonStyle = Object.assign({}, theme.buttonStyle, { marginRight: 10 });
|
||||
@ -106,8 +108,8 @@ class ClipperConfigScreenComponent extends React.Component {
|
||||
return (
|
||||
<div>
|
||||
<div style={containerStyle}>
|
||||
<div style={{ padding: theme.margin }}>
|
||||
<p style={theme.textStyle}>{_('Joplin Web Clipper allows saving web pages and screenshots from your browser to Joplin.')}</p>
|
||||
<div>
|
||||
<p style={Object.assign({}, theme.textStyle, { marginTop: 0 })}>{_('Joplin Web Clipper allows saving web pages and screenshots from your browser to Joplin.')}</p>
|
||||
<p style={theme.textStyle}>{_('In order to use the web clipper, you need to do the following:')}</p>
|
||||
|
||||
<div style={stepBoxStyle}>
|
||||
@ -120,8 +122,8 @@ class ClipperConfigScreenComponent extends React.Component {
|
||||
<p style={theme.h1Style}>{_('Step 2: Install the extension')}</p>
|
||||
<p style={theme.textStyle}>{_('Download and install the relevant extension for your browser:')}</p>
|
||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||
<ExtensionBadge theme={this.props.theme} type="firefox" url="https://addons.mozilla.org/en-US/firefox/addon/joplin-web-clipper/"/>
|
||||
<ExtensionBadge style={{ marginLeft: 10 }} theme={this.props.theme} type="chrome" url="https://chrome.google.com/webstore/detail/joplin-web-clipper/alofnhikmmkdbbbgpnglcpdollgjjfek"/>
|
||||
<ExtensionBadge themeId={this.props.themeId} type="firefox" url="https://addons.mozilla.org/en-US/firefox/addon/joplin-web-clipper/"/>
|
||||
<ExtensionBadge style={{ marginLeft: 10 }} themeId={this.props.themeId} type="chrome" url="https://chrome.google.com/webstore/detail/joplin-web-clipper/alofnhikmmkdbbbgpnglcpdollgjjfek"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -145,7 +147,7 @@ class ClipperConfigScreenComponent extends React.Component {
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
theme: state.settings.theme,
|
||||
themeId: state.settings.theme,
|
||||
clipperServer: state.clipperServer,
|
||||
clipperServerAutoStart: state.settings['clipperServer.autoStart'],
|
||||
apiToken: state.settings['api.token'],
|
||||
|
@ -1,44 +0,0 @@
|
||||
const React = require('react');
|
||||
const styleSelector = require('./style/ConfigMenuBar');
|
||||
const Setting = require('lib/models/Setting');
|
||||
|
||||
function ConfigMenuBarButton(props) {
|
||||
const style = styleSelector(null, props);
|
||||
|
||||
const iconStyle = props.selected ? style.buttonIconSelected : style.buttonIcon;
|
||||
const labelStyle = props.selected ? style.buttonLabelSelected : style.buttonLabel;
|
||||
|
||||
return (
|
||||
<button style={style.button} onClick={props.onClick}>
|
||||
<i style={iconStyle} className={props.iconName}></i>
|
||||
<span style={labelStyle}>{props.label}</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
function ConfigMenuBar(props) {
|
||||
const buttons = [];
|
||||
|
||||
const style = styleSelector(null, props);
|
||||
|
||||
for (const section of props.sections) {
|
||||
buttons.push(<ConfigMenuBarButton
|
||||
selected={props.selection === section.name}
|
||||
theme={props.theme}
|
||||
key={section.name}
|
||||
iconName={Setting.sectionNameToIcon(section.name)}
|
||||
label={Setting.sectionNameToLabel(section.name)}
|
||||
onClick={() => { props.onSelectionChange({ section: section }); }}
|
||||
/>);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={style.root} className="config-menu-bar">
|
||||
<div style={style.barButtons}>
|
||||
{buttons}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = ConfigMenuBar;
|
52
ElectronClient/gui/ConfigScreen/ButtonBar.tsx
Normal file
52
ElectronClient/gui/ConfigScreen/ButtonBar.tsx
Normal file
@ -0,0 +1,52 @@
|
||||
import * as React from 'react';
|
||||
import Button, { ButtonLevel } from '../Button/Button';
|
||||
const styled = require('styled-components').default;
|
||||
const { _ } = require('lib/locale.js');
|
||||
|
||||
interface Props {
|
||||
backButtonTitle?: string,
|
||||
hasChanges?: boolean,
|
||||
onCancelClick: Function,
|
||||
onSaveClick?: Function,
|
||||
onApplyClick?: Function,
|
||||
}
|
||||
|
||||
export const StyledRoot = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
background-color: ${(props:any) => props.theme.backgroundColor3};
|
||||
padding-left: ${(props:any) => props.theme.configScreenPadding}px;
|
||||
border-top-width: 1px;
|
||||
border-top-style: solid;
|
||||
border-top-color: ${(props:any) => props.theme.dividerColor};
|
||||
`;
|
||||
|
||||
export default function ButtonBar(props:Props) {
|
||||
function renderOkButton() {
|
||||
if (!props.onSaveClick) return null;
|
||||
return <Button style={{ marginRight: 10 }} level={ButtonLevel.Primary} disabled={!props.hasChanges} onClick={props.onSaveClick} title={_('OK')}/>;
|
||||
}
|
||||
|
||||
function renderApplyButton() {
|
||||
if (!props.onApplyClick) return null;
|
||||
return <Button level={ButtonLevel.Primary} disabled={!props.hasChanges} onClick={props.onApplyClick} title={_('Apply')}/>;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledRoot>
|
||||
<Button
|
||||
onClick={props.onCancelClick}
|
||||
level={ButtonLevel.Secondary}
|
||||
iconName="fa fa-chevron-left"
|
||||
title={props.backButtonTitle ? props.backButtonTitle : _('Back')}
|
||||
/>
|
||||
{ (props.onApplyClick || props.onSaveClick) && (
|
||||
<div style={{ display: 'flex', flexDirection: 'row', marginLeft: 30 }}>
|
||||
{renderOkButton()}
|
||||
{renderApplyButton()}
|
||||
</div>
|
||||
)}
|
||||
</StyledRoot>
|
||||
);
|
||||
}
|
@ -1,48 +1,63 @@
|
||||
const React = require('react');
|
||||
import * as React from 'react';
|
||||
import SideBar from './SideBar';
|
||||
import ButtonBar from './ButtonBar';
|
||||
import Button, { ButtonLevel } from '../Button/Button';
|
||||
const { connect } = require('react-redux');
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
const { bridge } = require('electron').remote.require('./bridge');
|
||||
const { themeStyle } = require('lib/theme');
|
||||
const pathUtils = require('lib/path-utils.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const SyncTargetRegistry = require('lib/SyncTargetRegistry');
|
||||
const shared = require('lib/components/shared/config-shared.js');
|
||||
const ConfigMenuBar = require('./ConfigMenuBar.min.js');
|
||||
const { EncryptionConfigScreen } = require('./EncryptionConfigScreen.min');
|
||||
const { ClipperConfigScreen } = require('./ClipperConfigScreen.min');
|
||||
const { KeymapConfigScreen } = require('./KeymapConfig/KeymapConfigScreen');
|
||||
const { bridge } = require('electron').remote.require('./bridge');
|
||||
const { EncryptionConfigScreen } = require('../EncryptionConfigScreen.min');
|
||||
const { ClipperConfigScreen } = require('../ClipperConfigScreen.min');
|
||||
const { KeymapConfigScreen } = require('../KeymapConfig/KeymapConfigScreen');
|
||||
|
||||
class ConfigScreenComponent extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
class ConfigScreenComponent extends React.Component<any, any> {
|
||||
|
||||
rowStyle_:any = null;
|
||||
|
||||
constructor(props:any) {
|
||||
super(props);
|
||||
|
||||
shared.init(this);
|
||||
|
||||
this.state.selectedSectionName = 'general';
|
||||
this.state.screenName = '';
|
||||
|
||||
this.checkSyncConfig_ = async () => {
|
||||
await shared.checkSyncConfig(this, this.state.settings);
|
||||
};
|
||||
|
||||
this.checkNextcloudAppButton_click = async () => {
|
||||
this.setState({ showNextcloudAppLog: true });
|
||||
await shared.checkNextcloudApp(this, this.state.settings);
|
||||
};
|
||||
|
||||
this.showLogButton_click = () => {
|
||||
this.setState({ showNextcloudAppLog: true });
|
||||
};
|
||||
|
||||
this.nextcloudAppHelpLink_click = () => {
|
||||
bridge().openExternal('https://joplinapp.org/nextcloud_app');
|
||||
this.state = {
|
||||
selectedSectionName: 'general',
|
||||
screenName: '',
|
||||
changedSettingKeys: [],
|
||||
};
|
||||
|
||||
this.rowStyle_ = {
|
||||
marginBottom: 10,
|
||||
};
|
||||
|
||||
this.configMenuBar_selectionChange = this.configMenuBar_selectionChange.bind(this);
|
||||
this.sideBar_selectionChange = this.sideBar_selectionChange.bind(this);
|
||||
this.checkSyncConfig_ = this.checkSyncConfig_.bind(this);
|
||||
this.checkNextcloudAppButton_click = this.checkNextcloudAppButton_click.bind(this);
|
||||
this.showLogButton_click = this.showLogButton_click.bind(this);
|
||||
this.nextcloudAppHelpLink_click = this.nextcloudAppHelpLink_click.bind(this);
|
||||
this.onCancelClick = this.onCancelClick.bind(this);
|
||||
this.onSaveClick = this.onSaveClick.bind(this);
|
||||
this.onApplyClick = this.onApplyClick.bind(this);
|
||||
}
|
||||
|
||||
async checkSyncConfig_() {
|
||||
await shared.checkSyncConfig(this, this.state.settings);
|
||||
}
|
||||
|
||||
async checkNextcloudAppButton_click() {
|
||||
this.setState({ showNextcloudAppLog: true });
|
||||
await shared.checkNextcloudApp(this, this.state.settings);
|
||||
}
|
||||
|
||||
showLogButton_click() {
|
||||
this.setState({ showNextcloudAppLog: true });
|
||||
}
|
||||
|
||||
nextcloudAppHelpLink_click() {
|
||||
bridge().openExternal('https://joplinapp.org/nextcloud_app');
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
@ -57,7 +72,7 @@ class ConfigScreenComponent extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
sectionByName(name) {
|
||||
sectionByName(name:string) {
|
||||
const sections = shared.settingsSections({ device: 'desktop', settings: this.state.settings });
|
||||
for (const section of sections) {
|
||||
if (section.name === name) return section;
|
||||
@ -66,15 +81,15 @@ class ConfigScreenComponent extends React.Component {
|
||||
throw new Error(`Invalid section name: ${name}`);
|
||||
}
|
||||
|
||||
screenFromName(screenName) {
|
||||
if (screenName === 'encryption') return <EncryptionConfigScreen theme={this.props.theme}/>;
|
||||
if (screenName === 'server') return <ClipperConfigScreen theme={this.props.theme}/>;
|
||||
if (screenName === 'keymap') return <KeymapConfigScreen themeId={this.props.theme}/>;
|
||||
screenFromName(screenName:string) {
|
||||
if (screenName === 'encryption') return <EncryptionConfigScreen themeId={this.props.themeId}/>;
|
||||
if (screenName === 'server') return <ClipperConfigScreen themeId={this.props.themeId}/>;
|
||||
if (screenName === 'keymap') return <KeymapConfigScreen themeId={this.props.themeId}/>;
|
||||
|
||||
throw new Error(`Invalid screen name: ${screenName}`);
|
||||
}
|
||||
|
||||
switchSection(name) {
|
||||
switchSection(name:string) {
|
||||
const section = this.sectionByName(name);
|
||||
let screenName = '';
|
||||
if (section.isScreen) {
|
||||
@ -89,11 +104,11 @@ class ConfigScreenComponent extends React.Component {
|
||||
this.setState({ selectedSectionName: section.name, screenName: screenName });
|
||||
}
|
||||
|
||||
configMenuBar_selectionChange(event) {
|
||||
sideBar_selectionChange(event:any) {
|
||||
this.switchSection(event.section.name);
|
||||
}
|
||||
|
||||
keyValueToArray(kv) {
|
||||
keyValueToArray(kv:any) {
|
||||
const output = [];
|
||||
for (const k in kv) {
|
||||
if (!kv.hasOwnProperty(k)) continue;
|
||||
@ -106,11 +121,11 @@ class ConfigScreenComponent extends React.Component {
|
||||
return output;
|
||||
}
|
||||
|
||||
renderSectionDescription(section) {
|
||||
renderSectionDescription(section:any) {
|
||||
const description = Setting.sectionDescription(section.name);
|
||||
if (!description) return null;
|
||||
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
return (
|
||||
<div style={Object.assign({}, theme.textStyle, { marginBottom: 15 })}>
|
||||
{description}
|
||||
@ -118,10 +133,10 @@ class ConfigScreenComponent extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
sectionToComponent(key, section, settings, selected) {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
sectionToComponent(key:string, section:any, settings:any, selected:boolean) {
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
const createSettingComponents = (advanced) => {
|
||||
const createSettingComponents = (advanced:boolean) => {
|
||||
const output = [];
|
||||
for (let i = 0; i < section.metadatas.length; i++) {
|
||||
const md = section.metadatas[i];
|
||||
@ -135,9 +150,10 @@ class ConfigScreenComponent extends React.Component {
|
||||
const settingComps = createSettingComponents(false);
|
||||
const advancedSettingComps = createSettingComponents(true);
|
||||
|
||||
const sectionStyle = {
|
||||
const sectionStyle:any = {
|
||||
marginTop: 20,
|
||||
marginBottom: 20,
|
||||
maxWidth: 640,
|
||||
};
|
||||
|
||||
if (!selected) sectionStyle.display = 'none';
|
||||
@ -161,9 +177,12 @@ class ConfigScreenComponent extends React.Component {
|
||||
|
||||
settingComps.push(
|
||||
<div key="check_sync_config_button" style={this.rowStyle_}>
|
||||
<button disabled={this.state.checkSyncConfigResult === 'checking'} style={theme.buttonStyle} onClick={this.checkSyncConfig_}>
|
||||
{_('Check synchronisation configuration')}
|
||||
</button>
|
||||
<Button
|
||||
title={_('Check synchronisation configuration')}
|
||||
level={ButtonLevel.Secondary}
|
||||
disabled={this.state.checkSyncConfigResult === 'checking'}
|
||||
onClick={this.checkSyncConfig_}
|
||||
/>
|
||||
{statusComp}
|
||||
</div>
|
||||
);
|
||||
@ -204,9 +223,7 @@ class ConfigScreenComponent extends React.Component {
|
||||
|
||||
{showLogButton}
|
||||
|
||||
<button disabled={this.state.checkNextcloudAppResult === 'checking'} style={theme.buttonStyle} onClick={this.checkNextcloudAppButton_click}>
|
||||
{_('Check Status')}
|
||||
</button>
|
||||
<Button level={ButtonLevel.Secondary} style={{ display: 'inline-block' }} title={_('Check Status')} disabled={this.state.checkNextcloudAppResult === 'checking'} onClick={this.checkNextcloudAppButton_click}/>
|
||||
|
||||
<a style={theme.urlStyle} href="#" onClick={this.nextcloudAppHelpLink_click}>[{_('Help')}]</a>
|
||||
{statusComp}
|
||||
@ -220,8 +237,17 @@ class ConfigScreenComponent extends React.Component {
|
||||
|
||||
if (advancedSettingComps.length) {
|
||||
const iconName = this.state.showAdvancedSettings ? 'fa fa-angle-down' : 'fa fa-angle-right';
|
||||
const advancedSettingsButtonStyle = Object.assign({}, theme.buttonStyle, { marginBottom: 10 });
|
||||
advancedSettingsButton = <button onClick={() => shared.advancedSettingsButton_click(this)} style={advancedSettingsButtonStyle}><i style={{ fontSize: 14 }} className={iconName}></i> {_('Show Advanced Settings')}</button>;
|
||||
// const advancedSettingsButtonStyle = Object.assign({}, theme.buttonStyle, { marginBottom: 10 });
|
||||
advancedSettingsButton = (
|
||||
<div style={{ marginBottom: 10 }}>
|
||||
<Button
|
||||
level={ButtonLevel.Secondary}
|
||||
onClick={() => shared.advancedSettingsButton_click(this)}
|
||||
iconName={iconName}
|
||||
title={_('Show Advanced Settings')}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
advancedSettingsSectionStyle.display = this.state.showAdvancedSettings ? 'block' : 'none';
|
||||
}
|
||||
|
||||
@ -235,35 +261,39 @@ class ConfigScreenComponent extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
settingToComponent(key, value) {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
settingToComponent(key:string, value:any) {
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
const output = null;
|
||||
const output:any = null;
|
||||
|
||||
const rowStyle = this.rowStyle_;
|
||||
const rowStyle = {
|
||||
marginBottom: theme.mainPadding,
|
||||
};
|
||||
|
||||
const labelStyle = Object.assign({}, theme.textStyle, {
|
||||
display: 'inline-block',
|
||||
marginRight: 10,
|
||||
display: 'block',
|
||||
color: theme.color,
|
||||
fontSize: theme.fontSize * 1.083333,
|
||||
fontWeight: 500,
|
||||
marginBottom: theme.mainPadding / 4,
|
||||
});
|
||||
|
||||
const subLabel = Object.assign({}, labelStyle, {
|
||||
display: 'block',
|
||||
opacity: 0.7,
|
||||
marginBottom: Math.round(rowStyle.marginBottom * 0.7),
|
||||
});
|
||||
|
||||
const invisibleLabel = Object.assign({}, labelStyle, {
|
||||
opacity: 0,
|
||||
marginBottom: labelStyle.marginBottom,
|
||||
});
|
||||
|
||||
const checkboxLabelStyle = Object.assign({}, labelStyle, {
|
||||
marginLeft: 8,
|
||||
display: 'inline',
|
||||
backgroundColor: 'transparent',
|
||||
});
|
||||
|
||||
const controlStyle = {
|
||||
display: 'inline-block',
|
||||
color: theme.color,
|
||||
fontFamily: theme.fontFamily,
|
||||
backgroundColor: theme.backgroundColor,
|
||||
};
|
||||
|
||||
@ -275,13 +305,19 @@ class ConfigScreenComponent extends React.Component {
|
||||
});
|
||||
|
||||
const textInputBaseStyle = Object.assign({}, controlStyle, {
|
||||
fontFamily: theme.fontFamily,
|
||||
border: '1px solid',
|
||||
padding: '4px 6px',
|
||||
borderColor: theme.dividerColor,
|
||||
borderRadius: 4,
|
||||
boxSizing: 'border-box',
|
||||
borderColor: theme.borderColor4,
|
||||
borderRadius: 3,
|
||||
paddingLeft: 6,
|
||||
paddingRight: 6,
|
||||
paddingTop: 4,
|
||||
paddingBottom: 4,
|
||||
});
|
||||
|
||||
const updateSettingValue = (key, value) => {
|
||||
const updateSettingValue = (key:string, value:any) => {
|
||||
// console.info(key + ' = ' + value);
|
||||
return shared.updateSettingValue(this, key, value);
|
||||
};
|
||||
@ -306,7 +342,14 @@ class ConfigScreenComponent extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
const selectStyle = Object.assign({}, controlStyle, { height: 22, borderColor: theme.dividerColor });
|
||||
const selectStyle = Object.assign({}, controlStyle, {
|
||||
paddingLeft: 6,
|
||||
paddingRight: 6,
|
||||
paddingTop: 4,
|
||||
paddingBottom: 4,
|
||||
borderColor: theme.borderColor4,
|
||||
borderRadius: 3,
|
||||
});
|
||||
|
||||
return (
|
||||
<div key={key} style={rowStyle}>
|
||||
@ -316,7 +359,7 @@ class ConfigScreenComponent extends React.Component {
|
||||
<select
|
||||
value={value}
|
||||
style={selectStyle}
|
||||
onChange={event => {
|
||||
onChange={(event:any) => {
|
||||
updateSettingValue(key, event.target.value);
|
||||
}}
|
||||
>
|
||||
@ -330,35 +373,38 @@ class ConfigScreenComponent extends React.Component {
|
||||
updateSettingValue(key, !value);
|
||||
};
|
||||
|
||||
const checkboxSize = theme.fontSize * 1.1666666666666;
|
||||
|
||||
// Hack: The {key+value.toString()} is needed as otherwise the checkbox doesn't update when the state changes.
|
||||
// There's probably a better way to do this but can't figure it out.
|
||||
|
||||
return (
|
||||
<div key={key + value.toString()} style={rowStyle}>
|
||||
<div style={controlStyle}>
|
||||
<div style={{ ...controlStyle, backgroundColor: 'transparent', display: 'flex', alignItems: 'center' }}>
|
||||
<input
|
||||
id={`setting_checkbox_${key}`}
|
||||
type="checkbox"
|
||||
checked={!!value}
|
||||
onChange={event => {
|
||||
onCheckboxClick(event);
|
||||
onChange={() => {
|
||||
onCheckboxClick();
|
||||
}}
|
||||
style={{ marginLeft: 0, width: checkboxSize, height: checkboxSize }}
|
||||
/>
|
||||
<label
|
||||
onClick={event => {
|
||||
onCheckboxClick(event);
|
||||
onClick={() => {
|
||||
onCheckboxClick();
|
||||
}}
|
||||
style={checkboxLabelStyle}
|
||||
style={{ ...checkboxLabelStyle, marginLeft: 5, marginBottom: 0 }}
|
||||
htmlFor={`setting_checkbox_${key}`}
|
||||
>
|
||||
{md.label()}
|
||||
</label>
|
||||
{descriptionComp}
|
||||
</div>
|
||||
{descriptionComp}
|
||||
</div>
|
||||
);
|
||||
} else if (md.type === Setting.TYPE_STRING) {
|
||||
const inputStyle = Object.assign({}, textInputBaseStyle, {
|
||||
const inputStyle:any = Object.assign({}, textInputBaseStyle, {
|
||||
width: '50%',
|
||||
minWidth: '20em',
|
||||
});
|
||||
@ -367,13 +413,13 @@ class ConfigScreenComponent extends React.Component {
|
||||
if (md.subType === 'file_path_and_args') {
|
||||
inputStyle.marginBottom = subLabel.marginBottom;
|
||||
|
||||
const splitCmd = cmdString => {
|
||||
const splitCmd = (cmdString:string) => {
|
||||
const path = pathUtils.extractExecutablePath(cmdString);
|
||||
const args = cmdString.substr(path.length + 1);
|
||||
return [pathUtils.unquotePath(path), args];
|
||||
};
|
||||
|
||||
const joinCmd = cmdArray => {
|
||||
const joinCmd = (cmdArray:string[]) => {
|
||||
if (!cmdArray[0] && !cmdArray[1]) return '';
|
||||
let cmdString = pathUtils.quotePath(cmdArray[0]);
|
||||
if (!cmdString) cmdString = '""';
|
||||
@ -381,13 +427,13 @@ class ConfigScreenComponent extends React.Component {
|
||||
return cmdString;
|
||||
};
|
||||
|
||||
const onPathChange = event => {
|
||||
const onPathChange = (event:any) => {
|
||||
const cmd = splitCmd(this.state.settings[key]);
|
||||
cmd[0] = event.target.value;
|
||||
updateSettingValue(key, joinCmd(cmd));
|
||||
};
|
||||
|
||||
const onArgsChange = event => {
|
||||
const onArgsChange = (event:any) => {
|
||||
const cmd = splitCmd(this.state.settings[key]);
|
||||
cmd[1] = event.target.value;
|
||||
updateSettingValue(key, joinCmd(cmd));
|
||||
@ -405,53 +451,51 @@ class ConfigScreenComponent extends React.Component {
|
||||
|
||||
return (
|
||||
<div key={key} style={rowStyle}>
|
||||
<div style={labelStyle}>
|
||||
<label>{md.label()}</label>
|
||||
</div>
|
||||
<div style={{ display: 'flex' }}>
|
||||
<div style={{ flex: 0, whiteSpace: 'nowrap' }}>
|
||||
<div style={labelStyle}>
|
||||
<label>{md.label()}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ flex: 0 }}>
|
||||
<div style={subLabel}>Path:</div>
|
||||
<div style={subLabel}>Arguments:</div>
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', marginBottom: inputStyle.marginBottom }}>
|
||||
<div style={{ ...rowStyle, marginBottom: 5 }}>
|
||||
<div style={subLabel}>Path:</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', marginBottom: inputStyle.marginBottom }}>
|
||||
<input
|
||||
type={inputType}
|
||||
style={Object.assign({}, inputStyle, { marginBottom: 0, marginRight: 5 })}
|
||||
onChange={(event:any) => {
|
||||
onPathChange(event);
|
||||
}}
|
||||
value={cmd[0]}
|
||||
/>
|
||||
<Button
|
||||
level={ButtonLevel.Secondary}
|
||||
title={_('Browse...')}
|
||||
onClick={browseButtonClick}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ ...rowStyle, marginBottom: 5 }}>
|
||||
<div style={subLabel}>Arguments:</div>
|
||||
<input
|
||||
type={inputType}
|
||||
style={Object.assign({}, inputStyle, { marginBottom: 0 })}
|
||||
onChange={event => {
|
||||
onPathChange(event);
|
||||
style={inputStyle}
|
||||
onChange={(event:any) => {
|
||||
onArgsChange(event);
|
||||
}}
|
||||
value={cmd[0]}
|
||||
value={cmd[1]}
|
||||
/>
|
||||
<button onClick={browseButtonClick} style={Object.assign({}, theme.buttonStyle, { marginLeft: 5 })}>
|
||||
{_('Browse...')}
|
||||
</button>
|
||||
<div style={{ width: inputStyle.width }}>
|
||||
{descriptionComp}
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
type={inputType}
|
||||
style={inputStyle}
|
||||
onChange={event => {
|
||||
onArgsChange(event);
|
||||
}}
|
||||
value={cmd[1]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex' }}>
|
||||
<div style={{ flex: 0, whiteSpace: 'nowrap' }}>
|
||||
<div style={invisibleLabel}>
|
||||
<label>{md.label()}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>{descriptionComp}</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
const onTextChange = event => {
|
||||
const onTextChange = (event:any) => {
|
||||
updateSettingValue(key, event.target.value);
|
||||
};
|
||||
|
||||
@ -464,16 +508,18 @@ class ConfigScreenComponent extends React.Component {
|
||||
type={inputType}
|
||||
style={inputStyle}
|
||||
value={this.state.settings[key]}
|
||||
onChange={event => {
|
||||
onChange={(event:any) => {
|
||||
onTextChange(event);
|
||||
}}
|
||||
/>
|
||||
{descriptionComp}
|
||||
<div style={{ width: inputStyle.width }}>
|
||||
{descriptionComp}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
} else if (md.type === Setting.TYPE_INT) {
|
||||
const onNumChange = event => {
|
||||
const onNumChange = (event:any) => {
|
||||
updateSettingValue(key, event.target.value);
|
||||
};
|
||||
|
||||
@ -491,7 +537,7 @@ class ConfigScreenComponent extends React.Component {
|
||||
type="number"
|
||||
style={inputStyle}
|
||||
value={this.state.settings[key]}
|
||||
onChange={event => {
|
||||
onChange={(event:any) => {
|
||||
onNumChange(event);
|
||||
}}
|
||||
min={md.minimum}
|
||||
@ -502,20 +548,12 @@ class ConfigScreenComponent extends React.Component {
|
||||
</div>
|
||||
);
|
||||
} else if (md.type === Setting.TYPE_BUTTON) {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const buttonStyle = Object.assign({}, theme.buttonStyle, {
|
||||
display: 'inline-block',
|
||||
marginRight: 10,
|
||||
});
|
||||
|
||||
return (
|
||||
<div key={key} style={rowStyle}>
|
||||
<div style={labelStyle}>
|
||||
<label>{md.label()}</label>
|
||||
</div>
|
||||
<button style={buttonStyle} onClick={md.onClick}>
|
||||
{_('Edit')}
|
||||
</button>
|
||||
<Button level={ButtonLevel.Secondary} title={_('Edit')} onClick={md.onClick}/>
|
||||
{descriptionComp}
|
||||
</div>
|
||||
);
|
||||
@ -544,46 +582,35 @@ class ConfigScreenComponent extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
const style = Object.assign(
|
||||
{
|
||||
backgroundColor: theme.backgroundColor,
|
||||
},
|
||||
const style = Object.assign({},
|
||||
this.props.style,
|
||||
{
|
||||
overflow: 'hidden',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
backgroundColor: theme.backgroundColor3,
|
||||
}
|
||||
);
|
||||
|
||||
const settings = this.state.settings;
|
||||
|
||||
const containerStyle = Object.assign({}, theme.containerStyle, { padding: 10, paddingTop: 0, display: 'flex', flex: 1 });
|
||||
const containerStyle = {
|
||||
overflow: 'auto',
|
||||
padding: theme.configScreenPadding,
|
||||
paddingTop: 0,
|
||||
display: 'flex',
|
||||
flex: 1,
|
||||
};
|
||||
|
||||
const hasChanges = this.hasChanges();
|
||||
|
||||
const buttonStyle = Object.assign({}, theme.buttonStyle, {
|
||||
display: 'inline-block',
|
||||
marginRight: 10,
|
||||
});
|
||||
|
||||
const buttonStyleApprove = Object.assign({}, buttonStyle, {
|
||||
opacity: hasChanges ? 1 : theme.disabledOpacity,
|
||||
});
|
||||
|
||||
const settingComps = shared.settingsToComponents2(this, 'desktop', settings, this.state.selectedSectionName);
|
||||
|
||||
const buttonBarStyle = {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: 10,
|
||||
borderTopWidth: 1,
|
||||
borderTopStyle: 'solid',
|
||||
borderTopColor: theme.dividerColor,
|
||||
};
|
||||
|
||||
// screenComp is a custom config screen, such as the encryption config screen or keymap config screen.
|
||||
// These screens handle their own loading/saving of settings and have bespoke rendering.
|
||||
// When screenComp is null, it means we are viewing the regular settings.
|
||||
const screenComp = this.state.screenName ? <div style={{ overflow: 'scroll', flex: 1 }}>{this.screenFromName(this.state.screenName)}</div> : null;
|
||||
|
||||
if (screenComp) containerStyle.display = 'none';
|
||||
@ -591,45 +618,35 @@ class ConfigScreenComponent extends React.Component {
|
||||
const sections = shared.settingsSections({ device: 'desktop', settings });
|
||||
|
||||
return (
|
||||
<div style={style}>
|
||||
<ConfigMenuBar
|
||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||
<SideBar
|
||||
selection={this.state.selectedSectionName}
|
||||
onSelectionChange={this.configMenuBar_selectionChange}
|
||||
onSelectionChange={this.sideBar_selectionChange}
|
||||
sections={sections}
|
||||
theme={this.props.theme}
|
||||
/>
|
||||
{screenComp}
|
||||
<div style={containerStyle}>{settingComps}</div>
|
||||
<div style={buttonBarStyle}>
|
||||
<button
|
||||
onClick={() => {
|
||||
this.onCancelClick();
|
||||
}}
|
||||
style={buttonStyle}
|
||||
>
|
||||
<i style={theme.buttonIconStyle} className={'fa fa-chevron-left'}></i>
|
||||
{hasChanges && !screenComp ? _('Cancel') : _('Back')}
|
||||
</button>
|
||||
{ !screenComp && (
|
||||
<div>
|
||||
<button disabled={!hasChanges} onClick={() => { this.onSaveClick(); }} style={buttonStyleApprove}>{_('OK')}</button>
|
||||
<button disabled={!hasChanges} onClick={() => { this.onApplyClick(); }} style={buttonStyleApprove}>{_('Apply')}</button>
|
||||
</div>
|
||||
)}
|
||||
<div style={style}>
|
||||
{screenComp}
|
||||
<div style={containerStyle}>{settingComps}</div>
|
||||
<ButtonBar
|
||||
hasChanges={hasChanges}
|
||||
backButtonTitle={hasChanges && !screenComp ? _('Cancel') : _('Back')}
|
||||
onCancelClick={this.onCancelClick}
|
||||
onSaveClick={screenComp ? null : this.onSaveClick}
|
||||
onApplyClick={screenComp ? null : this.onApplyClick}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const mapStateToProps = (state:any) => {
|
||||
return {
|
||||
theme: state.settings.theme,
|
||||
themeId: state.settings.theme,
|
||||
settings: state.settings,
|
||||
locale: state.settings.locale,
|
||||
};
|
||||
};
|
||||
|
||||
const ConfigScreen = connect(mapStateToProps)(ConfigScreenComponent);
|
||||
export default connect(mapStateToProps)(ConfigScreenComponent);
|
||||
|
||||
module.exports = { ConfigScreen };
|
74
ElectronClient/gui/ConfigScreen/SideBar.tsx
Normal file
74
ElectronClient/gui/ConfigScreen/SideBar.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
import * as React from 'react';
|
||||
const styled = require('styled-components').default;
|
||||
const Setting = require('lib/models/Setting');
|
||||
|
||||
interface Props {
|
||||
selection: string,
|
||||
onSelectionChange: Function,
|
||||
sections: any[],
|
||||
}
|
||||
|
||||
export const StyledRoot = styled.div`
|
||||
display: flex;
|
||||
background-color: ${(props:any) => props.theme.backgroundColor2};
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
export const StyledListItem = styled.a`
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: ${(props:any) => props.theme.mainPadding}px;
|
||||
background: ${(props:any) => props.selected ? props.theme.selectedColor2 : 'none'};
|
||||
transition: 0.1s;
|
||||
text-decoration: none;
|
||||
cursor: default;
|
||||
opacity: ${(props:any) => props.selected ? 1 : 0.8};
|
||||
|
||||
&:hover {
|
||||
background-color: ${(props:any) => props.theme.backgroundColorHover2};
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledListItemLabel = styled.span`
|
||||
font-size: ${(props:any) => Math.round(props.theme.fontSize * 1.2)}px;
|
||||
font-weight: 500;
|
||||
color: ${(props:any) => props.theme.color2};
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
user-select: none;
|
||||
`;
|
||||
|
||||
export const StyledListItemIcon = styled.i`
|
||||
font-size: ${(props:any) => Math.round(props.theme.fontSize * 1.4)}px;
|
||||
color: ${(props:any) => props.theme.color2};
|
||||
margin-right: ${(props:any) => props.theme.mainPadding / 1.5}px;
|
||||
`;
|
||||
|
||||
export default function SideBar(props:Props) {
|
||||
const buttons:any[] = [];
|
||||
|
||||
function renderButton(section:any) {
|
||||
const selected = props.selection === section.name;
|
||||
return (
|
||||
<StyledListItem key={section.name} selected={selected} onClick={() => { props.onSelectionChange({ section: section }); }}>
|
||||
<StyledListItemIcon className={Setting.sectionNameToIcon(section.name)} />
|
||||
<StyledListItemLabel>
|
||||
{Setting.sectionNameToLabel(section.name)}
|
||||
</StyledListItemLabel>
|
||||
</StyledListItem>
|
||||
);
|
||||
}
|
||||
|
||||
for (const section of props.sections) {
|
||||
buttons.push(renderButton(section));
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledRoot>
|
||||
{buttons}
|
||||
</StyledRoot>
|
||||
);
|
||||
}
|
@ -3,7 +3,7 @@ const { _ } = require('lib/locale.js');
|
||||
const { themeStyle } = require('lib/theme');
|
||||
|
||||
function DialogButtonRow(props) {
|
||||
const theme = themeStyle(props.theme);
|
||||
const theme = themeStyle(props.themeId);
|
||||
|
||||
const okButton_click = () => {
|
||||
if (props.onClick) props.onClick({ buttonName: 'ok' });
|
||||
|
@ -1,16 +1,24 @@
|
||||
const React = require('react');
|
||||
import * as React from 'react';
|
||||
import ButtonBar from './ConfigScreen/ButtonBar';
|
||||
|
||||
const { connect } = require('react-redux');
|
||||
const { bridge } = require('electron').remote.require('./bridge');
|
||||
const { Header } = require('./Header/Header.min.js');
|
||||
const { themeStyle } = require('lib/theme');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const Shared = require('lib/components/shared/dropbox-login-shared');
|
||||
|
||||
class DropboxLoginScreenComponent extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
interface Props {
|
||||
themeId: string,
|
||||
}
|
||||
|
||||
this.shared_ = new Shared(this, msg => bridge().showInfoMessageBox(msg), msg => bridge().showErrorMessageBox(msg));
|
||||
class DropboxLoginScreenComponent extends React.Component<any, any> {
|
||||
|
||||
shared_:any;
|
||||
|
||||
constructor(props:Props) {
|
||||
super(props);
|
||||
|
||||
this.shared_ = new Shared(this, (msg:string) => bridge().showInfoMessageBox(msg), (msg:string) => bridge().showErrorMessageBox(msg));
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
@ -19,19 +27,18 @@ class DropboxLoginScreenComponent extends React.Component {
|
||||
|
||||
render() {
|
||||
const style = this.props.style;
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
const headerStyle = Object.assign({}, theme.headerStyle, { width: style.width });
|
||||
const containerStyle = Object.assign({}, theme.containerStyle, {
|
||||
padding: theme.margin,
|
||||
height: style.height - theme.headerHeight - theme.margin * 2,
|
||||
padding: theme.configScreenPadding,
|
||||
height: style.height - theme.margin * 2,
|
||||
flex: 1,
|
||||
});
|
||||
|
||||
const inputStyle = Object.assign({}, theme.inputStyle, { width: 500 });
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Header style={headerStyle} />
|
||||
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||
<div style={containerStyle}>
|
||||
<p style={theme.textStyle}>{_('To allow Joplin to synchronise with Dropbox, please follow the steps below:')}</p>
|
||||
<p style={theme.textStyle}>{_('Step 1: Open this URL in your browser to authorise the application:')}</p>
|
||||
@ -46,17 +53,18 @@ class DropboxLoginScreenComponent extends React.Component {
|
||||
{_('Submit')}
|
||||
</button>
|
||||
</div>
|
||||
<ButtonBar
|
||||
onCancelClick={() => this.props.dispatch({ type: 'NAV_BACK' })}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const mapStateToProps = (state:any) => {
|
||||
return {
|
||||
theme: state.settings.theme,
|
||||
themeId: state.settings.theme,
|
||||
};
|
||||
};
|
||||
|
||||
const DropboxLoginScreen = connect(mapStateToProps)(DropboxLoginScreenComponent);
|
||||
|
||||
module.exports = { DropboxLoginScreen };
|
||||
export default connect(mapStateToProps)(DropboxLoginScreenComponent);
|
@ -35,7 +35,7 @@ class EncryptionConfigScreenComponent extends React.Component {
|
||||
}
|
||||
|
||||
renderMasterKey(mk) {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
const passwordStyle = {
|
||||
color: theme.color,
|
||||
@ -80,7 +80,7 @@ class EncryptionConfigScreenComponent extends React.Component {
|
||||
const needUpgradeMasterKeys = EncryptionService.instance().masterKeysThatNeedUpgrading(this.props.masterKeys);
|
||||
if (!needUpgradeMasterKeys.length) return null;
|
||||
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
const rows = [];
|
||||
const comp = this;
|
||||
@ -114,7 +114,7 @@ class EncryptionConfigScreenComponent extends React.Component {
|
||||
renderReencryptData() {
|
||||
if (!shim.isElectron()) return null;
|
||||
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
const buttonLabel = _('Re-encrypt data');
|
||||
|
||||
const intro = this.props.shouldReencrypt ? _('The default encryption method has been changed to a more secure one and it is recommended that you apply it to your data.') : _('You may use the tool below to re-encrypt your data, for example if you know that some of your notes are encrypted with an obsolete encryption method.');
|
||||
@ -139,13 +139,13 @@ class EncryptionConfigScreenComponent extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
const masterKeys = this.props.masterKeys;
|
||||
const containerPadding = 10;
|
||||
|
||||
const containerStyle = Object.assign({}, theme.containerStyle, {
|
||||
padding: containerPadding,
|
||||
padding: theme.configScreenPadding,
|
||||
overflow: 'auto',
|
||||
backgroundColor: theme.backgroundColor3,
|
||||
});
|
||||
|
||||
const mkComps = [];
|
||||
@ -289,7 +289,7 @@ class EncryptionConfigScreenComponent extends React.Component {
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
theme: state.settings.theme,
|
||||
themeId: state.settings.theme,
|
||||
masterKeys: state.masterKeys,
|
||||
passwords: state.settings['encryption.passwordCache'],
|
||||
encryptionEnabled: state.settings['encryption.enabled'],
|
||||
|
@ -1,329 +0,0 @@
|
||||
const React = require('react');
|
||||
const { connect } = require('react-redux');
|
||||
const { themeStyle } = require('lib/theme');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { bridge } = require('electron').remote.require('./bridge');
|
||||
const CommandService = require('lib/services/CommandService').default;
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
|
||||
const commands = [
|
||||
require('./commands/focusSearch'),
|
||||
];
|
||||
|
||||
class HeaderComponent extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
searchQuery: '',
|
||||
showSearchUsageLink: false,
|
||||
showButtonLabels: true,
|
||||
};
|
||||
|
||||
for (const command of commands) {
|
||||
CommandService.instance().registerRuntime(command.declaration.name, command.runtime(this));
|
||||
}
|
||||
|
||||
this.scheduleSearchChangeEventIid_ = null;
|
||||
this.searchOnQuery_ = null;
|
||||
this.searchElement_ = null;
|
||||
|
||||
const triggerOnQuery = query => {
|
||||
clearTimeout(this.scheduleSearchChangeEventIid_);
|
||||
if (this.searchOnQuery_) this.searchOnQuery_(query, Setting.value('db.fuzzySearchEnabled'));
|
||||
this.scheduleSearchChangeEventIid_ = null;
|
||||
};
|
||||
|
||||
this.search_onChange = event => {
|
||||
this.setState({ searchQuery: event.target.value });
|
||||
|
||||
if (this.scheduleSearchChangeEventIid_) clearTimeout(this.scheduleSearchChangeEventIid_);
|
||||
|
||||
this.scheduleSearchChangeEventIid_ = setTimeout(() => {
|
||||
triggerOnQuery(this.state.searchQuery);
|
||||
}, 500);
|
||||
};
|
||||
|
||||
this.search_onClear = () => {
|
||||
this.resetSearch();
|
||||
if (this.searchElement_) this.searchElement_.focus();
|
||||
};
|
||||
|
||||
this.search_onFocus = () => {
|
||||
if (this.hideSearchUsageLinkIID_) {
|
||||
clearTimeout(this.hideSearchUsageLinkIID_);
|
||||
this.hideSearchUsageLinkIID_ = null;
|
||||
}
|
||||
|
||||
this.setState({ showSearchUsageLink: true });
|
||||
};
|
||||
|
||||
this.search_onBlur = () => {
|
||||
if (this.hideSearchUsageLinkIID_) return;
|
||||
|
||||
this.hideSearchUsageLinkIID_ = setTimeout(() => {
|
||||
this.setState({ showSearchUsageLink: false });
|
||||
}, 5000);
|
||||
};
|
||||
|
||||
this.search_keyDown = event => {
|
||||
if (event.keyCode === 27) {
|
||||
// ESCAPE
|
||||
this.resetSearch();
|
||||
}
|
||||
};
|
||||
|
||||
this.resetSearch = () => {
|
||||
this.setState({ searchQuery: '' });
|
||||
triggerOnQuery('');
|
||||
};
|
||||
|
||||
this.searchUsageLink_click = () => {
|
||||
bridge().openExternal('https://joplinapp.org/#searching');
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.notesParentType !== this.props.notesParentType && this.props.notesParentType !== 'Search' && this.state.searchQuery) {
|
||||
this.resetSearch();
|
||||
}
|
||||
|
||||
if (this.props.zoomFactor !== prevProps.zoomFactor || this.props.size !== prevProps.size) {
|
||||
this.determineButtonLabelState();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.determineButtonLabelState();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.hideSearchUsageLinkIID_) {
|
||||
clearTimeout(this.hideSearchUsageLinkIID_);
|
||||
this.hideSearchUsageLinkIID_ = null;
|
||||
}
|
||||
|
||||
for (const command of commands) {
|
||||
CommandService.instance().unregisterRuntime(command.declaration.name);
|
||||
}
|
||||
}
|
||||
|
||||
determineButtonLabelState() {
|
||||
const mediaQuery = window.matchMedia(`(max-width: ${780 * this.props.zoomFactor}px)`);
|
||||
const showButtonLabels = !mediaQuery.matches;
|
||||
|
||||
if (this.state.showButtonLabels !== showButtonLabels) {
|
||||
this.setState({
|
||||
showButtonLabels: !mediaQuery.matches,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
back_click() {
|
||||
this.props.dispatch({ type: 'NAV_BACK' });
|
||||
}
|
||||
|
||||
makeButton(key, style, options) {
|
||||
// TODO: "tab" type is not finished
|
||||
if (options.type === 'tab') {
|
||||
const buttons = [];
|
||||
for (let i = 0; i < options.items.length; i++) {
|
||||
const item = options.items[i];
|
||||
buttons.push(this.makeButton(key + item.title, style, Object.assign({}, options, {
|
||||
title: item.title,
|
||||
type: 'button',
|
||||
})));
|
||||
}
|
||||
|
||||
return <span style={{ display: 'flex', flexDirection: 'row' }}>{buttons}</span>;
|
||||
}
|
||||
|
||||
const theme = themeStyle(this.props.theme);
|
||||
|
||||
let icon = null;
|
||||
if (options.iconName) {
|
||||
const iconStyle = {
|
||||
fontSize: Math.round(style.fontSize * 1.1),
|
||||
color: theme.iconColor,
|
||||
};
|
||||
if (options.title) iconStyle.marginRight = 5;
|
||||
if ('undefined' != typeof options.iconRotation) {
|
||||
iconStyle.transition = 'transform 0.15s ease-in-out';
|
||||
iconStyle.transform = `rotate(${options.iconRotation}deg)`;
|
||||
}
|
||||
icon = <i style={iconStyle} className={`fas ${options.iconName}`}></i>;
|
||||
}
|
||||
|
||||
const isEnabled = !('enabled' in options) || options.enabled;
|
||||
const classes = ['button'];
|
||||
if (!isEnabled) classes.push('disabled');
|
||||
|
||||
const finalStyle = Object.assign({}, style, {
|
||||
opacity: isEnabled ? 1 : 0.4,
|
||||
});
|
||||
|
||||
const title = options.title ? options.title : '';
|
||||
|
||||
if (options.type === 'checkbox' && options.checked) {
|
||||
finalStyle.backgroundColor = theme.selectedColor;
|
||||
finalStyle.borderWidth = 1;
|
||||
finalStyle.borderTopColor = theme.selectedDividerColor;
|
||||
finalStyle.borderLeftColor = theme.selectedDividerColor;
|
||||
finalStyle.borderTopStyle = 'solid';
|
||||
finalStyle.borderLeftStyle = 'solid';
|
||||
finalStyle.paddingLeft++;
|
||||
finalStyle.paddingTop++;
|
||||
finalStyle.paddingBottom--;
|
||||
finalStyle.paddingRight--;
|
||||
finalStyle.boxSizing = 'border-box';
|
||||
}
|
||||
|
||||
return (
|
||||
<a
|
||||
className={classes.join(' ')}
|
||||
style={finalStyle}
|
||||
key={key}
|
||||
href="#"
|
||||
title={title}
|
||||
onClick={() => {
|
||||
if (isEnabled) options.onClick();
|
||||
}}
|
||||
>
|
||||
{icon}
|
||||
<span className="title" style={{
|
||||
display: this.state.showButtonLabels ? 'inline-block' : 'none',
|
||||
}}>{title}</span>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
makeSearch(key, style, options, state) {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
|
||||
const inputStyle = {
|
||||
display: 'flex',
|
||||
flex: 1,
|
||||
marginLeft: 10,
|
||||
paddingLeft: 6,
|
||||
paddingRight: 6,
|
||||
paddingTop: 1, // vertical alignment with buttons
|
||||
paddingBottom: 0, // vertical alignment with buttons
|
||||
height: style.fontSize * 2,
|
||||
maxWidth: 300,
|
||||
color: style.color,
|
||||
fontSize: style.fontSize,
|
||||
fontFamily: style.fontFamily,
|
||||
backgroundColor: style.searchColor,
|
||||
border: '1px solid',
|
||||
borderColor: style.dividerColor,
|
||||
};
|
||||
|
||||
const searchButton = {
|
||||
paddingLeft: 4,
|
||||
paddingRight: 4,
|
||||
paddingTop: 2,
|
||||
paddingBottom: 2,
|
||||
textDecoration: 'none',
|
||||
};
|
||||
|
||||
const iconStyle = {
|
||||
display: 'flex',
|
||||
fontSize: Math.round(style.fontSize) * 1.2,
|
||||
color: style.color,
|
||||
};
|
||||
|
||||
const containerStyle = {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
flexGrow: 1,
|
||||
alignItems: 'center',
|
||||
};
|
||||
|
||||
const iconName = state.searchQuery ? 'fa-times' : 'fa-search';
|
||||
const icon = <i style={iconStyle} className={`fas ${iconName}`}></i>;
|
||||
if (options.onQuery) this.searchOnQuery_ = options.onQuery;
|
||||
|
||||
const usageLink = !this.state.showSearchUsageLink ? null : (
|
||||
<a onClick={this.searchUsageLink_click} style={theme.urlStyle} href="#">
|
||||
{_('Usage')}
|
||||
</a>
|
||||
);
|
||||
|
||||
return (
|
||||
<div key={key} style={containerStyle}>
|
||||
<input type="text" style={inputStyle} placeholder={options.title} value={state.searchQuery} onChange={this.search_onChange} ref={elem => (this.searchElement_ = elem)} onFocus={this.search_onFocus} onBlur={this.search_onBlur} onKeyDown={this.search_keyDown} />
|
||||
<a href="#" style={searchButton} onClick={this.search_onClear}>
|
||||
{icon}
|
||||
</a>
|
||||
{usageLink}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const style = Object.assign({}, this.props.style);
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const showBackButton = this.props.showBackButton === undefined || this.props.showBackButton === true;
|
||||
style.height = theme.headerHeight;
|
||||
style.display = 'flex';
|
||||
style.flexDirection = 'row';
|
||||
style.borderBottom = `1px solid ${theme.dividerColor}`;
|
||||
style.boxSizing = 'border-box';
|
||||
|
||||
const items = [];
|
||||
|
||||
const itemStyle = {
|
||||
height: theme.headerHeight,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
paddingTop: 1,
|
||||
paddingBottom: 1,
|
||||
paddingLeft: theme.headerButtonHPadding,
|
||||
paddingRight: theme.headerButtonHPadding,
|
||||
color: theme.color,
|
||||
searchColor: theme.backgroundColor,
|
||||
dividerColor: theme.dividerColor,
|
||||
textDecoration: 'none',
|
||||
fontFamily: theme.fontFamily,
|
||||
fontSize: theme.fontSize,
|
||||
boxSizing: 'border-box',
|
||||
cursor: 'default',
|
||||
whiteSpace: 'nowrap',
|
||||
userSelect: 'none',
|
||||
};
|
||||
|
||||
if (showBackButton) {
|
||||
items.push(this.makeButton('back', itemStyle, { title: _('Back'), onClick: () => this.back_click(), iconName: 'fa-chevron-left ' }));
|
||||
}
|
||||
|
||||
if (this.props.items) {
|
||||
for (let i = 0; i < this.props.items.length; i++) {
|
||||
const item = this.props.items[i];
|
||||
|
||||
if (item.type === 'search') {
|
||||
items.push(this.makeSearch(`item_${i}_search`, itemStyle, item, this.state));
|
||||
} else {
|
||||
items.push(this.makeButton(`item_${i}_${item.title}`, itemStyle, item));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="header" style={style}>
|
||||
{items}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
theme: state.settings.theme,
|
||||
notesParentType: state.notesParentType,
|
||||
size: state.windowContentSize,
|
||||
zoomFactor: state.settings.windowContentZoomFactor / 100,
|
||||
};
|
||||
};
|
||||
|
||||
const Header = connect(mapStateToProps)(HeaderComponent);
|
||||
|
||||
module.exports = { Header };
|
@ -14,7 +14,7 @@ class HelpButtonComponent extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
const style = Object.assign({}, this.props.style, { color: theme.color, textDecoration: 'none' });
|
||||
const helpIconStyle = { flex: 0, width: 16, height: 16, marginLeft: 10 };
|
||||
const extraProps = {};
|
||||
@ -29,7 +29,7 @@ class HelpButtonComponent extends React.Component {
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
theme: state.settings.theme,
|
||||
themeId: state.settings.theme,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -4,7 +4,7 @@ const { themeStyle } = require('lib/theme');
|
||||
class IconButton extends React.Component {
|
||||
render() {
|
||||
const style = this.props.style;
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
const iconStyle = {
|
||||
color: theme.color,
|
||||
fontSize: theme.fontSize * 1.4,
|
||||
|
@ -1,7 +1,6 @@
|
||||
const React = require('react');
|
||||
const { connect } = require('react-redux');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const { Header } = require('./Header/Header.min.js');
|
||||
const { themeStyle } = require('lib/theme');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { filename, basename } = require('lib/path-utils.js');
|
||||
@ -94,8 +93,7 @@ class ImportScreenComponent extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const style = this.props.style;
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
const messages = this.uniqueMessages();
|
||||
|
||||
const messagesStyle = {
|
||||
@ -105,10 +103,6 @@ class ImportScreenComponent extends React.Component {
|
||||
backgroundColor: theme.backgroundColor,
|
||||
};
|
||||
|
||||
const headerStyle = {
|
||||
width: style.width,
|
||||
};
|
||||
|
||||
const messageComps = [];
|
||||
for (let i = 0; i < messages.length; i++) {
|
||||
messageComps.push(<div key={messages[i].key}>{messages[i].text}</div>);
|
||||
@ -116,7 +110,6 @@ class ImportScreenComponent extends React.Component {
|
||||
|
||||
return (
|
||||
<div style={{}}>
|
||||
<Header style={headerStyle} />
|
||||
<div style={messagesStyle}>{messageComps}</div>
|
||||
</div>
|
||||
);
|
||||
@ -125,7 +118,7 @@ class ImportScreenComponent extends React.Component {
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
theme: state.settings.theme,
|
||||
themeId: state.settings.theme,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -36,6 +36,10 @@ class ItemList extends React.Component {
|
||||
return this.listRef.current ? this.listRef.current.offsetTop : 0;
|
||||
}
|
||||
|
||||
offsetScroll() {
|
||||
return this.scrollTop_;
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
this.updateStateItemIndexes();
|
||||
}
|
||||
|
@ -5,7 +5,8 @@ export default function styles(themeId: number) {
|
||||
return {
|
||||
container: {
|
||||
...theme.containerStyle,
|
||||
padding: 16,
|
||||
padding: theme.configScreenPadding,
|
||||
backgroundColor: theme.backgroundColor3,
|
||||
},
|
||||
actionsContainer: {
|
||||
display: 'flex',
|
||||
|
@ -1,24 +1,26 @@
|
||||
const React = require('react');
|
||||
import * as React from 'react';
|
||||
import ResizableLayout, { findItemByKey, LayoutItem, LayoutItemDirection } from '../ResizableLayout/ResizableLayout';
|
||||
import NoteList from '../NoteList/NoteList.js';
|
||||
import NoteEditor from '../NoteEditor/NoteEditor.js';
|
||||
import NoteContentPropertiesDialog from '../NoteContentPropertiesDialog.js';
|
||||
import ShareNoteDialog from '../ShareNoteDialog.js';
|
||||
import NoteListControls from '../NoteListControls/NoteListControls.js';
|
||||
import CommandService from 'lib/services/CommandService';
|
||||
|
||||
const produce = require('immer').default;
|
||||
const { connect } = require('react-redux');
|
||||
const { Header } = require('../Header/Header.min.js');
|
||||
const { SideBar } = require('../SideBar/SideBar.min.js');
|
||||
const { NoteList } = require('../NoteList/NoteList.min.js');
|
||||
const NoteEditor = require('../NoteEditor/NoteEditor.js').default;
|
||||
const { SideBar } = require('../SideBar/SideBar.js');
|
||||
const { stateUtils } = require('lib/reducer.js');
|
||||
const { PromptDialog } = require('../PromptDialog.min.js');
|
||||
const NoteContentPropertiesDialog = require('../NoteContentPropertiesDialog.js').default;
|
||||
const NotePropertiesDialog = require('../NotePropertiesDialog.min.js');
|
||||
const ShareNoteDialog = require('../ShareNoteDialog.js').default;
|
||||
const InteropServiceHelper = require('../../InteropServiceHelper.js');
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
const { shim } = require('lib/shim');
|
||||
const { themeStyle } = require('lib/theme.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { bridge } = require('electron').remote.require('./bridge');
|
||||
const VerticalResizer = require('../VerticalResizer.min');
|
||||
const PluginManager = require('lib/services/PluginManager');
|
||||
const EncryptionService = require('lib/services/EncryptionService');
|
||||
const CommandService = require('lib/services/CommandService').default;
|
||||
const ipcRenderer = require('electron').ipcRenderer;
|
||||
const { time } = require('lib/time-utils.js');
|
||||
|
||||
@ -28,7 +30,7 @@ const commands = [
|
||||
require('./commands/hideModalMessage'),
|
||||
require('./commands/moveToFolder'),
|
||||
require('./commands/newNote'),
|
||||
require('./commands/newNotebook'),
|
||||
require('./commands/newFolder'),
|
||||
require('./commands/newTodo'),
|
||||
require('./commands/print'),
|
||||
require('./commands/renameFolder'),
|
||||
@ -40,14 +42,76 @@ const commands = [
|
||||
require('./commands/showNoteContentProperties'),
|
||||
require('./commands/showNoteProperties'),
|
||||
require('./commands/showShareNoteDialog'),
|
||||
require('./commands/toggleEditors'),
|
||||
require('./commands/toggleNoteList'),
|
||||
require('./commands/toggleSidebar'),
|
||||
require('./commands/toggleVisiblePanes'),
|
||||
];
|
||||
|
||||
class MainScreenComponent extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
class MainScreenComponent extends React.Component<any, any> {
|
||||
|
||||
waitForNotesSavedIID_:any;
|
||||
isPrinting_:boolean;
|
||||
styleKey_:string;
|
||||
styles_:any;
|
||||
promptOnClose_:Function;
|
||||
|
||||
constructor(props:any) {
|
||||
super(props);
|
||||
|
||||
const rootLayoutSize = this.rootLayoutSize();
|
||||
const theme = themeStyle(props.themeId);
|
||||
const sideBarMinWidth = 200;
|
||||
|
||||
const layout:LayoutItem = {
|
||||
key: 'root',
|
||||
direction: LayoutItemDirection.Row,
|
||||
resizable: false,
|
||||
width: rootLayoutSize.width,
|
||||
height: rootLayoutSize.height,
|
||||
children: [
|
||||
{
|
||||
key: 'sidebarColumn',
|
||||
direction: LayoutItemDirection.Column,
|
||||
resizable: true,
|
||||
width: Setting.value('style.sidebar.width') < sideBarMinWidth ? sideBarMinWidth : Setting.value('style.sidebar.width'),
|
||||
visible: Setting.value('sidebarVisibility'),
|
||||
minWidth: sideBarMinWidth,
|
||||
children: [
|
||||
{
|
||||
key: 'sideBar',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'noteListColumn',
|
||||
direction: LayoutItemDirection.Column,
|
||||
resizable: true,
|
||||
width: Setting.value('style.noteList.width') < sideBarMinWidth ? sideBarMinWidth : Setting.value('style.noteList.width'),
|
||||
visible: Setting.value('noteListVisibility'),
|
||||
minWidth: sideBarMinWidth,
|
||||
children: [
|
||||
{
|
||||
height: theme.topRowHeight,
|
||||
key: 'noteListControls',
|
||||
},
|
||||
{
|
||||
key: 'noteList',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'editorColumn',
|
||||
direction: LayoutItemDirection.Column,
|
||||
resizable: false,
|
||||
children: [
|
||||
{
|
||||
key: 'editor',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
this.state = {
|
||||
promptOptions: null,
|
||||
@ -58,6 +122,7 @@ class MainScreenComponent extends React.Component {
|
||||
notePropertiesDialogOptions: {},
|
||||
noteContentPropertiesDialogOptions: {},
|
||||
shareNoteDialogOptions: {},
|
||||
layout: layout,
|
||||
};
|
||||
|
||||
this.registerCommands();
|
||||
@ -70,6 +135,16 @@ class MainScreenComponent extends React.Component {
|
||||
this.shareNoteDialog_close = this.shareNoteDialog_close.bind(this);
|
||||
this.sidebar_onDrag = this.sidebar_onDrag.bind(this);
|
||||
this.noteList_onDrag = this.noteList_onDrag.bind(this);
|
||||
this.resizableLayout_resize = this.resizableLayout_resize.bind(this);
|
||||
this.resizableLayout_renderItem = this.resizableLayout_renderItem.bind(this);
|
||||
this.window_resize = this.window_resize.bind(this);
|
||||
this.rowHeight = this.rowHeight.bind(this);
|
||||
|
||||
window.addEventListener('resize', this.window_resize);
|
||||
}
|
||||
|
||||
window_resize() {
|
||||
this.updateRootLayoutSize();
|
||||
}
|
||||
|
||||
setupAppCloseHandling() {
|
||||
@ -103,11 +178,11 @@ class MainScreenComponent extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
sidebar_onDrag(event) {
|
||||
sidebar_onDrag(event:any) {
|
||||
Setting.setValue('style.sidebar.width', this.props.sidebarWidth + event.deltaX);
|
||||
}
|
||||
|
||||
noteList_onDrag(event) {
|
||||
noteList_onDrag(event:any) {
|
||||
Setting.setValue('style.noteList.width', Setting.value('style.noteList.width') + event.deltaX);
|
||||
}
|
||||
|
||||
@ -123,13 +198,13 @@ class MainScreenComponent extends React.Component {
|
||||
this.setState({ shareNoteDialogOptions: {} });
|
||||
}
|
||||
|
||||
commandService_commandsEnabledStateChange(event) {
|
||||
commandService_commandsEnabledStateChange(event:any) {
|
||||
const buttonCommandNames = [
|
||||
'toggleSidebar',
|
||||
'toggleNoteList',
|
||||
'newNote',
|
||||
'newTodo',
|
||||
'newNotebook',
|
||||
'newFolder',
|
||||
'toggleVisiblePanes',
|
||||
];
|
||||
|
||||
@ -141,13 +216,40 @@ class MainScreenComponent extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
updateRootLayoutSize() {
|
||||
this.setState({ layout: produce(this.state.layout, (draftState:any) => {
|
||||
const s = this.rootLayoutSize();
|
||||
draftState.width = s.width;
|
||||
draftState.height = s.height;
|
||||
}) });
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps:any) {
|
||||
if (this.props.noteListVisibility !== prevProps.noteListVisibility || this.props.sidebarVisibility !== prevProps.sidebarVisibility) {
|
||||
this.setState({ layout: produce(this.state.layout, (draftState:any) => {
|
||||
const noteListColumn = findItemByKey(draftState, 'noteListColumn');
|
||||
noteListColumn.visible = this.props.noteListVisibility;
|
||||
|
||||
const sidebarColumn = findItemByKey(draftState, 'sidebarColumn');
|
||||
sidebarColumn.visible = this.props.sidebarVisibility;
|
||||
}) });
|
||||
}
|
||||
|
||||
if (prevProps.style.width !== this.props.style.width || prevProps.style.height !== this.props.style.height) {
|
||||
this.updateRootLayoutSize();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
CommandService.instance().on('commandsEnabledStateChange', this.commandService_commandsEnabledStateChange);
|
||||
this.updateRootLayoutSize();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
CommandService.instance().off('commandsEnabledStateChange', this.commandService_commandsEnabledStateChange);
|
||||
this.unregisterCommands();
|
||||
|
||||
window.removeEventListener('resize', this.window_resize);
|
||||
}
|
||||
|
||||
toggleSidebar() {
|
||||
@ -162,14 +264,14 @@ class MainScreenComponent extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
async waitForNoteToSaved(noteId) {
|
||||
async waitForNoteToSaved(noteId:string) {
|
||||
while (noteId && this.props.editorNoteStatuses[noteId] === 'saving') {
|
||||
console.info('Waiting for note to be saved...', this.props.editorNoteStatuses);
|
||||
await time.msleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
async printTo_(target, options) {
|
||||
async printTo_(target:string, options:any) {
|
||||
// Concurrent print calls are disallowed to avoid incorrect settings being restored upon completion
|
||||
if (this.isPrinting_) {
|
||||
console.info(`Printing ${options.path} to ${target} disallowed, already printing.`);
|
||||
@ -208,7 +310,23 @@ class MainScreenComponent extends React.Component {
|
||||
this.isPrinting_ = false;
|
||||
}
|
||||
|
||||
styles(themeId, width, height, messageBoxVisible, isSidebarVisible, isNoteListVisible, sidebarWidth, noteListWidth) {
|
||||
rootLayoutSize() {
|
||||
return {
|
||||
width: window.innerWidth,
|
||||
height: this.rowHeight(),
|
||||
};
|
||||
}
|
||||
|
||||
rowHeight() {
|
||||
if (!this.props) return 0;
|
||||
return this.props.style.height - (this.messageBoxVisible() ? this.messageBoxHeight() : 0);
|
||||
}
|
||||
|
||||
messageBoxHeight() {
|
||||
return 50;
|
||||
}
|
||||
|
||||
styles(themeId:number, width:number, height:number, messageBoxVisible:boolean, isSidebarVisible:any, isNoteListVisible:any, sidebarWidth:number, noteListWidth:number) {
|
||||
const styleKey = [themeId, width, height, messageBoxVisible, +isSidebarVisible, +isNoteListVisible, sidebarWidth, noteListWidth].join('_');
|
||||
if (styleKey === this.styleKey_) return this.styles_;
|
||||
|
||||
@ -224,14 +342,16 @@ class MainScreenComponent extends React.Component {
|
||||
|
||||
this.styles_.messageBox = {
|
||||
width: width,
|
||||
height: 50,
|
||||
height: this.messageBoxHeight(),
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
paddingLeft: 10,
|
||||
backgroundColor: theme.warningBackgroundColor,
|
||||
};
|
||||
|
||||
const rowHeight = height - theme.headerHeight - (messageBoxVisible ? this.styles_.messageBox.height : 0);
|
||||
const rowHeight = height - (messageBoxVisible ? this.styles_.messageBox.height : 0);
|
||||
|
||||
this.styles_.rowHeight = rowHeight;
|
||||
|
||||
this.styles_.verticalResizerSidebar = {
|
||||
width: 5,
|
||||
@ -241,6 +361,10 @@ class MainScreenComponent extends React.Component {
|
||||
display: 'inline-block',
|
||||
};
|
||||
|
||||
this.styles_.resizableLayout = {
|
||||
height: rowHeight,
|
||||
};
|
||||
|
||||
this.styles_.verticalResizerNotelist = Object.assign({}, this.styles_.verticalResizerSidebar);
|
||||
|
||||
this.styles_.sideBar = {
|
||||
@ -295,7 +419,7 @@ class MainScreenComponent extends React.Component {
|
||||
return this.styles_;
|
||||
}
|
||||
|
||||
renderNotification(theme, styles) {
|
||||
renderNotification(theme:any, styles:any) {
|
||||
if (!this.messageBoxVisible()) return null;
|
||||
|
||||
const onViewStatusScreen = () => {
|
||||
@ -401,8 +525,34 @@ class MainScreenComponent extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
resizableLayout_resize(event:any) {
|
||||
this.setState({ layout: event.layout });
|
||||
|
||||
const col1 = findItemByKey(event.layout, 'sidebarColumn');
|
||||
const col2 = findItemByKey(event.layout, 'noteListColumn');
|
||||
Setting.setValue('style.sidebar.width', col1.width);
|
||||
Setting.setValue('style.noteList.width', col2.width);
|
||||
}
|
||||
|
||||
resizableLayout_renderItem(key:string, event:any) {
|
||||
const eventEmitter = event.eventEmitter;
|
||||
|
||||
if (key === 'sideBar') {
|
||||
return <SideBar key={key} />;
|
||||
} else if (key === 'noteList') {
|
||||
return <NoteList key={key} resizableLayoutEventEmitter={eventEmitter} size={event.size} visible={event.visible}/>;
|
||||
} else if (key === 'editor') {
|
||||
const bodyEditor = this.props.settingEditorCodeView ? 'CodeMirror' : 'TinyMCE';
|
||||
return <NoteEditor key={key} bodyEditor={bodyEditor} />;
|
||||
} else if (key === 'noteListControls') {
|
||||
return <NoteListControls key={key} />;
|
||||
}
|
||||
|
||||
throw new Error(`Invalid layout component: ${key}`);
|
||||
}
|
||||
|
||||
render() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
const style = Object.assign(
|
||||
{
|
||||
color: theme.color,
|
||||
@ -411,48 +561,12 @@ class MainScreenComponent extends React.Component {
|
||||
this.props.style
|
||||
);
|
||||
const promptOptions = this.state.promptOptions;
|
||||
const notes = this.props.notes;
|
||||
const sidebarVisibility = this.props.sidebarVisibility;
|
||||
const noteListVisibility = this.props.noteListVisibility;
|
||||
const styles = this.styles(this.props.theme, style.width, style.height, this.messageBoxVisible(), sidebarVisibility, noteListVisibility, this.props.sidebarWidth, this.props.noteListWidth);
|
||||
|
||||
const headerItems = [];
|
||||
|
||||
headerItems.push(CommandService.instance().commandToToolbarButton('toggleSidebar', { iconRotation: sidebarVisibility ? 0 : 90 }));
|
||||
headerItems.push(CommandService.instance().commandToToolbarButton('toggleNoteList', { iconRotation: noteListVisibility ? 0 : 90 }));
|
||||
headerItems.push(CommandService.instance().commandToToolbarButton('newNote'));
|
||||
headerItems.push(CommandService.instance().commandToToolbarButton('newTodo'));
|
||||
headerItems.push(CommandService.instance().commandToToolbarButton('newNotebook'));
|
||||
|
||||
headerItems.push({
|
||||
title: _('Code View'),
|
||||
iconName: 'fa-file-code ',
|
||||
enabled: !!notes.length,
|
||||
type: 'checkbox',
|
||||
checked: this.props.settingEditorCodeView,
|
||||
onClick: () => {
|
||||
// A bit of a hack, but for now don't allow changing code view
|
||||
// while a note is being saved as it will cause a problem with
|
||||
// TinyMCE because it won't have time to send its content before
|
||||
// being switch to the Code Editor.
|
||||
if (this.props.hasNotesBeingSaved) return;
|
||||
Setting.toggle('editor.codeView');
|
||||
},
|
||||
});
|
||||
|
||||
headerItems.push(CommandService.instance().commandToToolbarButton('toggleVisiblePanes'));
|
||||
|
||||
headerItems.push({
|
||||
title: _('Search...'),
|
||||
iconName: 'fa-search',
|
||||
onQuery: (query, fuzzy = false) => {
|
||||
CommandService.instance().execute('search', { query, fuzzy });
|
||||
},
|
||||
type: 'search',
|
||||
});
|
||||
const styles = this.styles(this.props.themeId, style.width, style.height, this.messageBoxVisible(), sidebarVisibility, noteListVisibility, this.props.sidebarWidth, this.props.noteListWidth);
|
||||
|
||||
if (!this.promptOnClose_) {
|
||||
this.promptOnClose_ = (answer, buttonType) => {
|
||||
this.promptOnClose_ = (answer:any, buttonType:any) => {
|
||||
return this.state.promptOptions.onClose(answer, buttonType);
|
||||
};
|
||||
}
|
||||
@ -468,34 +582,33 @@ class MainScreenComponent extends React.Component {
|
||||
const noteContentPropertiesDialogOptions = this.state.noteContentPropertiesDialogOptions;
|
||||
const shareNoteDialogOptions = this.state.shareNoteDialogOptions;
|
||||
|
||||
const bodyEditor = this.props.settingEditorCodeView ? 'CodeMirror' : 'TinyMCE';
|
||||
|
||||
return (
|
||||
<div style={style}>
|
||||
<div style={modalLayerStyle}>{this.state.modalLayer.message}</div>
|
||||
|
||||
{noteContentPropertiesDialogOptions.visible && <NoteContentPropertiesDialog theme={this.props.theme} onClose={this.noteContentPropertiesDialog_close} text={noteContentPropertiesDialogOptions.text}/>}
|
||||
{notePropertiesDialogOptions.visible && <NotePropertiesDialog theme={this.props.theme} noteId={notePropertiesDialogOptions.noteId} onClose={this.notePropertiesDialog_close} onRevisionLinkClick={notePropertiesDialogOptions.onRevisionLinkClick} />}
|
||||
{shareNoteDialogOptions.visible && <ShareNoteDialog theme={this.props.theme} noteIds={shareNoteDialogOptions.noteIds} onClose={this.shareNoteDialog_close} />}
|
||||
{noteContentPropertiesDialogOptions.visible && <NoteContentPropertiesDialog markupLanguage={noteContentPropertiesDialogOptions.markupLanguage} themeId={this.props.themeId} onClose={this.noteContentPropertiesDialog_close} text={noteContentPropertiesDialogOptions.text}/>}
|
||||
{notePropertiesDialogOptions.visible && <NotePropertiesDialog themeId={this.props.themeId} noteId={notePropertiesDialogOptions.noteId} onClose={this.notePropertiesDialog_close} onRevisionLinkClick={notePropertiesDialogOptions.onRevisionLinkClick} />}
|
||||
{shareNoteDialogOptions.visible && <ShareNoteDialog themeId={this.props.themeId} noteIds={shareNoteDialogOptions.noteIds} onClose={this.shareNoteDialog_close} />}
|
||||
|
||||
<PromptDialog autocomplete={promptOptions && 'autocomplete' in promptOptions ? promptOptions.autocomplete : null} defaultValue={promptOptions && promptOptions.value ? promptOptions.value : ''} theme={this.props.theme} style={styles.prompt} onClose={this.promptOnClose_} label={promptOptions ? promptOptions.label : ''} description={promptOptions ? promptOptions.description : null} visible={!!this.state.promptOptions} buttons={promptOptions && 'buttons' in promptOptions ? promptOptions.buttons : null} inputType={promptOptions && 'inputType' in promptOptions ? promptOptions.inputType : null} />
|
||||
<PromptDialog autocomplete={promptOptions && 'autocomplete' in promptOptions ? promptOptions.autocomplete : null} defaultValue={promptOptions && promptOptions.value ? promptOptions.value : ''} themeId={this.props.themeId} style={styles.prompt} onClose={this.promptOnClose_} label={promptOptions ? promptOptions.label : ''} description={promptOptions ? promptOptions.description : null} visible={!!this.state.promptOptions} buttons={promptOptions && 'buttons' in promptOptions ? promptOptions.buttons : null} inputType={promptOptions && 'inputType' in promptOptions ? promptOptions.inputType : null} />
|
||||
|
||||
<Header style={styles.header} showBackButton={false} items={headerItems} />
|
||||
{messageComp}
|
||||
<SideBar style={styles.sideBar} />
|
||||
<VerticalResizer style={styles.verticalResizerSidebar} onDrag={this.sidebar_onDrag} />
|
||||
<NoteList style={styles.noteList} />
|
||||
<VerticalResizer style={styles.verticalResizerNotelist} onDrag={this.noteList_onDrag} />
|
||||
<NoteEditor bodyEditor={bodyEditor} style={styles.noteText} />
|
||||
<ResizableLayout
|
||||
width={this.state.width}
|
||||
height={styles.rowHeight}
|
||||
layout={this.state.layout}
|
||||
onResize={this.resizableLayout_resize}
|
||||
renderItem={this.resizableLayout_renderItem}
|
||||
/>
|
||||
{pluginDialog}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const mapStateToProps = (state:any) => {
|
||||
return {
|
||||
theme: state.settings.theme,
|
||||
themeId: state.settings.theme,
|
||||
settingEditorCodeView: state.settings['editor.codeView'],
|
||||
sidebarVisibility: state.sidebarVisibility,
|
||||
noteListVisibility: state.noteListVisibility,
|
||||
@ -519,6 +632,4 @@ const mapStateToProps = state => {
|
||||
};
|
||||
};
|
||||
|
||||
const MainScreen = connect(mapStateToProps)(MainScreenComponent);
|
||||
|
||||
module.exports = { MainScreen };
|
||||
export default connect(mapStateToProps)(MainScreenComponent);
|
@ -8,7 +8,7 @@ const { time } = require('lib/time-utils');
|
||||
export const declaration:CommandDeclaration = {
|
||||
name: 'editAlarm',
|
||||
label: () => _('Set alarm'),
|
||||
iconName: 'fa-clock',
|
||||
iconName: 'icon-alarm',
|
||||
};
|
||||
|
||||
export const runtime = (comp:any):CommandRuntime => {
|
||||
|
@ -4,7 +4,7 @@ const Folder = require('lib/models/Folder');
|
||||
const { bridge } = require('electron').remote.require('./bridge');
|
||||
|
||||
export const declaration:CommandDeclaration = {
|
||||
name: 'newNotebook',
|
||||
name: 'newFolder',
|
||||
label: () => _('New notebook'),
|
||||
iconName: 'fa-book',
|
||||
};
|
@ -6,13 +6,12 @@ const { uuid } = require('lib/uuid.js');
|
||||
|
||||
export const declaration:CommandDeclaration = {
|
||||
name: 'search',
|
||||
iconName: 'icon-search',
|
||||
};
|
||||
|
||||
export const runtime = (comp:any):CommandRuntime => {
|
||||
return {
|
||||
execute: async ({ query, fuzzy }:any) => {
|
||||
console.info('RUNTIME', query);
|
||||
|
||||
execute: async ({ query }:any) => {
|
||||
if (!comp.searchId_) comp.searchId_ = uuid.create();
|
||||
|
||||
comp.props.dispatch({
|
||||
@ -23,7 +22,6 @@ export const runtime = (comp:any):CommandRuntime => {
|
||||
query_pattern: query,
|
||||
query_folder_id: null,
|
||||
type_: BaseModel.TYPE_SEARCH,
|
||||
fuzzy: fuzzy,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -5,7 +5,7 @@ const { _ } = require('lib/locale');
|
||||
export const declaration:CommandDeclaration = {
|
||||
name: 'setTags',
|
||||
label: () => _('Tags'),
|
||||
iconName: 'fa-tags',
|
||||
iconName: 'icon-tags',
|
||||
};
|
||||
|
||||
export const runtime = (comp:any):CommandRuntime => {
|
||||
|
@ -4,7 +4,7 @@ const { _ } = require('lib/locale');
|
||||
export const declaration:CommandDeclaration = {
|
||||
name: 'showNoteProperties',
|
||||
label: () => _('Note properties'),
|
||||
iconName: 'fa-info-circle',
|
||||
iconName: 'icon-info',
|
||||
};
|
||||
|
||||
export const runtime = (comp:any):CommandRuntime => {
|
||||
|
32
ElectronClient/gui/MainScreen/commands/toggleEditors.ts
Normal file
32
ElectronClient/gui/MainScreen/commands/toggleEditors.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { CommandDeclaration, CommandRuntime } from '../../../lib/services/CommandService';
|
||||
const { _ } = require('lib/locale');
|
||||
const { stateUtils } = require('lib/reducer.js');
|
||||
const Setting = require('lib/models/Setting');
|
||||
|
||||
export const declaration:CommandDeclaration = {
|
||||
name: 'toggleEditors',
|
||||
label: () => _('Toggle editors'),
|
||||
iconName: 'fa-columns',
|
||||
};
|
||||
|
||||
export const runtime = ():CommandRuntime => {
|
||||
return {
|
||||
execute: async (props:any) => {
|
||||
// A bit of a hack, but for now don't allow changing code view
|
||||
// while a note is being saved as it will cause a problem with
|
||||
// TinyMCE because it won't have time to send its content before
|
||||
// being switch to Ace Editor.
|
||||
if (props.hasNotesBeingSaved) return;
|
||||
Setting.toggle('editor.codeView');
|
||||
},
|
||||
isEnabled: (props:any):boolean => {
|
||||
return !props.hasNotesBeingSaved && props.selectedNoteIds.length === 1;
|
||||
},
|
||||
mapStateToProps: (state:any):any => {
|
||||
return {
|
||||
hasNotesBeingSaved: stateUtils.hasNotesBeingSaved(state),
|
||||
selectedNoteIds: state.selectedNoteIds,
|
||||
};
|
||||
},
|
||||
};
|
||||
};
|
@ -4,7 +4,7 @@ const { _ } = require('lib/locale');
|
||||
export const declaration:CommandDeclaration = {
|
||||
name: 'toggleVisiblePanes',
|
||||
label: () => _('Toggle editor layout'),
|
||||
iconName: 'fa-columns',
|
||||
iconName: 'icon-layout ',
|
||||
};
|
||||
|
||||
export const runtime = (comp:any):CommandRuntime => {
|
||||
|
@ -5,22 +5,21 @@ const { bridge } = require('electron').remote.require('./bridge');
|
||||
const NoteListUtils = require('./utils/NoteListUtils');
|
||||
|
||||
interface MultiNoteActionsProps {
|
||||
theme: number,
|
||||
themeId: number,
|
||||
selectedNoteIds: string[],
|
||||
notes: any[],
|
||||
dispatch: Function,
|
||||
watchedNoteFiles: string[],
|
||||
style: any,
|
||||
}
|
||||
|
||||
function styles_(props:MultiNoteActionsProps) {
|
||||
return buildStyle('MultiNoteActions', props.theme, (theme:any) => {
|
||||
return buildStyle('MultiNoteActions', props.themeId, (theme:any) => {
|
||||
return {
|
||||
root: {
|
||||
...props.style,
|
||||
display: 'inline-flex',
|
||||
justifyContent: 'center',
|
||||
paddingTop: theme.marginTop,
|
||||
width: '100%',
|
||||
},
|
||||
itemList: {
|
||||
display: 'flex',
|
||||
|
@ -7,7 +7,7 @@ const Countable = require('countable');
|
||||
const markupLanguageUtils = require('lib/markupLanguageUtils');
|
||||
|
||||
interface NoteContentPropertiesDialogProps {
|
||||
theme: number,
|
||||
themeId: number,
|
||||
text: string,
|
||||
markupLanguage: number,
|
||||
onClose: Function,
|
||||
@ -46,7 +46,9 @@ function formatReadTime(readTimeMinutes: number) {
|
||||
}
|
||||
|
||||
export default function NoteContentPropertiesDialog(props:NoteContentPropertiesDialogProps) {
|
||||
const theme = themeStyle(props.theme);
|
||||
|
||||
console.info('MMMMMMMMMMMM', props.markupLanguage);
|
||||
const theme = themeStyle(props.themeId);
|
||||
const tableBodyComps: JSX.Element[] = [];
|
||||
// For the source Markdown
|
||||
const [lines, setLines] = useState<number>(0);
|
||||
@ -165,7 +167,7 @@ export default function NoteContentPropertiesDialog(props:NoteContentPropertiesD
|
||||
<div style={labelCompStyle}>
|
||||
{_('Read time: %s min', formatReadTime(strippedReadTime))}
|
||||
</div>
|
||||
<DialogButtonRow theme={props.theme} onClick={buttonRow_click} okButtonShow={false} cancelButtonLabel={_('Close')}/>
|
||||
<DialogButtonRow themeId={props.themeId} onClick={buttonRow_click} okButtonShow={false} cancelButtonLabel={_('Close')}/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -48,7 +48,6 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
|
||||
props_onChangeRef.current = props.onChange;
|
||||
const contentKeyHasChangedRef = useRef(false);
|
||||
contentKeyHasChangedRef.current = previousContentKey !== props.contentKey;
|
||||
const theme = themeStyle(props.theme);
|
||||
|
||||
const rootSize = useRootSize({ rootRef });
|
||||
|
||||
@ -351,6 +350,8 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
|
||||
}, [styles.editor.codeMirrorTheme]);
|
||||
|
||||
useEffect(() => {
|
||||
const theme = themeStyle(props.themeId);
|
||||
|
||||
const element = document.createElement('style');
|
||||
element.setAttribute('id', 'codemirrorStyle');
|
||||
document.head.appendChild(element);
|
||||
@ -420,7 +421,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
|
||||
return () => {
|
||||
document.head.removeChild(element);
|
||||
};
|
||||
}, [props.theme]);
|
||||
}, [props.themeId]);
|
||||
|
||||
const webview_domReady = useCallback(() => {
|
||||
setWebviewReady(true);
|
||||
@ -549,7 +550,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
|
||||
value={props.content}
|
||||
ref={editorRef}
|
||||
mode={props.contentMarkupLanguage === Note.MARKUP_LANGUAGE_HTML ? 'xml' : 'joplin-markdown'}
|
||||
theme={styles.editor.codeMirrorTheme}
|
||||
codeMirrorTheme={styles.editor.codeMirrorTheme}
|
||||
style={styles.editor}
|
||||
readOnly={props.visiblePanes.indexOf('editor') < 0}
|
||||
autoMatchBraces={Setting.value('editor.autoMatchingBraces')}
|
||||
@ -580,7 +581,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
|
||||
<div style={styles.root} ref={rootRef}>
|
||||
<div style={styles.rowToolbar}>
|
||||
<Toolbar
|
||||
theme={props.theme}
|
||||
themeId={props.themeId}
|
||||
dispatch={props.dispatch}
|
||||
disabled={editorReadOnly}
|
||||
/>
|
||||
|
@ -69,7 +69,7 @@ export interface EditorProps {
|
||||
value: string,
|
||||
mode: string,
|
||||
style: any,
|
||||
theme: any,
|
||||
codeMirrorTheme: any,
|
||||
readOnly: boolean,
|
||||
autoMatchBraces: boolean,
|
||||
keyMap: string,
|
||||
@ -216,7 +216,7 @@ function Editor(props: EditorProps, ref: any) {
|
||||
const cmOptions = {
|
||||
value: props.value,
|
||||
screenReaderLabel: props.value,
|
||||
theme: props.theme,
|
||||
theme: props.codeMirrorTheme,
|
||||
mode: props.mode,
|
||||
readOnly: props.readOnly,
|
||||
autoCloseBrackets: props.autoMatchBraces,
|
||||
@ -265,9 +265,9 @@ function Editor(props: EditorProps, ref: any) {
|
||||
|
||||
useEffect(() => {
|
||||
if (editor) {
|
||||
editor.setOption('theme', props.theme);
|
||||
editor.setOption('theme', props.codeMirrorTheme);
|
||||
}
|
||||
}, [props.theme]);
|
||||
}, [props.codeMirrorTheme]);
|
||||
|
||||
useEffect(() => {
|
||||
if (editor) {
|
||||
|
@ -2,22 +2,20 @@ import * as React from 'react';
|
||||
import CommandService from '../../../../lib/services/CommandService';
|
||||
|
||||
const ToolbarBase = require('../../../Toolbar.min.js');
|
||||
const { buildStyle, themeStyle } = require('lib/theme');
|
||||
const { buildStyle } = require('lib/theme');
|
||||
|
||||
interface ToolbarProps {
|
||||
theme: number,
|
||||
themeId: number,
|
||||
dispatch: Function,
|
||||
disabled: boolean,
|
||||
}
|
||||
|
||||
function styles_(props:ToolbarProps) {
|
||||
return buildStyle('CodeMirrorToolbar', props.theme, (/* theme:any*/) => {
|
||||
const theme = themeStyle(props.theme);
|
||||
return buildStyle('CodeMirrorToolbar', props.themeId, () => {
|
||||
return {
|
||||
root: {
|
||||
flex: 1,
|
||||
marginBottom: 0,
|
||||
borderTop: `1px solid ${theme.dividerColor}`,
|
||||
},
|
||||
};
|
||||
});
|
||||
@ -29,6 +27,11 @@ export default function Toolbar(props:ToolbarProps) {
|
||||
const cmdService = CommandService.instance();
|
||||
|
||||
const toolbarItems = [
|
||||
cmdService.commandToToolbarButton('historyBackward'),
|
||||
cmdService.commandToToolbarButton('historyForward'),
|
||||
cmdService.commandToToolbarButton('startExternalEditing'),
|
||||
|
||||
{ type: 'separator' },
|
||||
cmdService.commandToToolbarButton('textBold'),
|
||||
cmdService.commandToToolbarButton('textItalic'),
|
||||
{ type: 'separator' },
|
||||
@ -42,6 +45,8 @@ export default function Toolbar(props:ToolbarProps) {
|
||||
cmdService.commandToToolbarButton('textHeading'),
|
||||
cmdService.commandToToolbarButton('textHorizontalRule'),
|
||||
cmdService.commandToToolbarButton('insertDateTime'),
|
||||
|
||||
cmdService.commandToToolbarButton('toggleEditors'),
|
||||
];
|
||||
|
||||
return <ToolbarBase disabled={props.disabled} style={styles.root} items={toolbarItems} />;
|
||||
|
@ -2,7 +2,7 @@ import { NoteBodyEditorProps } from '../../../utils/types';
|
||||
const { buildStyle } = require('lib/theme');
|
||||
|
||||
export default function styles(props: NoteBodyEditorProps) {
|
||||
return buildStyle('CodeMirror', props.theme, (theme: any) => {
|
||||
return buildStyle('CodeMirror', props.themeId, (theme: any) => {
|
||||
return {
|
||||
root: {
|
||||
position: 'relative',
|
||||
|
@ -3,15 +3,18 @@ import { useState, useEffect, useCallback, useRef, forwardRef, useImperativeHand
|
||||
import { ScrollOptions, ScrollOptionTypes, EditorCommand, NoteBodyEditorProps } from '../../utils/types';
|
||||
import { resourcesStatus, commandAttachFileToBody, handlePasteEvent } from '../../utils/resourceHandling';
|
||||
import useScroll from './utils/useScroll';
|
||||
import styles_ from './styles';
|
||||
import { menuItems, ContextMenuOptions, ContextMenuItemType } from '../../utils/contextMenu';
|
||||
import CommandService from '../../../../lib/services/CommandService';
|
||||
import CommandService, { ToolbarButtonInfo } from 'lib/services/CommandService';
|
||||
import ToggleEditorsButton, { Value as ToggleEditorsButtonValue } from '../../../ToggleEditorsButton/ToggleEditorsButton';
|
||||
import ToolbarButton from '../../../../gui/ToolbarButton/ToolbarButton';
|
||||
const { MarkupToHtml } = require('lib/joplin-renderer');
|
||||
const taboverride = require('taboverride');
|
||||
const { reg } = require('lib/registry.js');
|
||||
const { _, closestSupportedLocale } = require('lib/locale');
|
||||
const BaseItem = require('lib/models/BaseItem');
|
||||
const Resource = require('lib/models/Resource');
|
||||
const { themeStyle, buildStyle } = require('lib/theme');
|
||||
const { themeStyle } = require('lib/theme');
|
||||
const { clipboard } = require('electron');
|
||||
const supportedLocales = require('./supportedLocales');
|
||||
|
||||
@ -112,31 +115,6 @@ const joplinCommandToTinyMceCommands:JoplinCommandToTinyMceCommands = {
|
||||
'search': { name: 'SearchReplace' },
|
||||
};
|
||||
|
||||
function styles_(props:NoteBodyEditorProps) {
|
||||
return buildStyle('TinyMCE', props.theme, (/* theme:any */) => {
|
||||
return {
|
||||
disabledOverlay: {
|
||||
zIndex: 10,
|
||||
position: 'absolute',
|
||||
backgroundColor: 'white',
|
||||
opacity: 0.7,
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
padding: 20,
|
||||
paddingTop: 50,
|
||||
textAlign: 'center',
|
||||
width: '100%',
|
||||
},
|
||||
rootStyle: {
|
||||
position: 'relative',
|
||||
...props.style,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
let loadedCssFiles_:string[] = [];
|
||||
let loadedJsFiles_:string[] = [];
|
||||
let dispatchDidUpdateIID_:any = null;
|
||||
@ -170,7 +148,7 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
|
||||
editorRef.current = editor;
|
||||
|
||||
const styles = styles_(props);
|
||||
const theme = themeStyle(props.theme);
|
||||
// const theme = themeStyle(props.themeId);
|
||||
|
||||
const { scrollToPercent } = useScroll({ editor, onScroll: props.onScroll });
|
||||
|
||||
@ -368,10 +346,17 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
|
||||
useEffect(() => {
|
||||
if (!editorReady) return () => {};
|
||||
|
||||
const theme = themeStyle(props.themeId);
|
||||
|
||||
const element = document.createElement('style');
|
||||
element.setAttribute('id', 'tinyMceStyle');
|
||||
document.head.appendChild(element);
|
||||
element.appendChild(document.createTextNode(`
|
||||
.joplin-tinymce .tox-editor-header {
|
||||
padding-left: ${styles.leftExtraToolbarContainer.width + styles.leftExtraToolbarContainer.padding * 2}px;
|
||||
padding-right: ${styles.rightExtraToolbarContainer.width + styles.rightExtraToolbarContainer.padding * 2}px;
|
||||
}
|
||||
|
||||
.tox .tox-toolbar,
|
||||
.tox .tox-toolbar__overflow,
|
||||
.tox .tox-toolbar__primary,
|
||||
@ -388,8 +373,7 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
|
||||
}
|
||||
|
||||
.tox .tox-editor-header {
|
||||
border-top: 1px solid ${theme.dividerColor};
|
||||
border-bottom: 1px solid ${theme.dividerColor};
|
||||
border: none;
|
||||
}
|
||||
|
||||
.tox .tox-tbtn,
|
||||
@ -401,8 +385,8 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
|
||||
.tox input,
|
||||
.tox .tox-label,
|
||||
.tox .tox-toolbar-label {
|
||||
color: ${theme.iconColor} !important;
|
||||
fill: ${theme.iconColor} !important;
|
||||
color: ${theme.color3} !important;
|
||||
fill: ${theme.color3} !important;
|
||||
}
|
||||
|
||||
.tox .tox-statusbar a,
|
||||
@ -424,32 +408,59 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
|
||||
}
|
||||
|
||||
.tox .tox-tbtn:hover {
|
||||
background-color: ${theme.backgroundHover};
|
||||
color: ${theme.colorHover};
|
||||
fill: ${theme.colorHover};
|
||||
color: ${theme.colorHover3} !important;
|
||||
fill: ${theme.colorHover3} !important;
|
||||
background-color: ${theme.backgroundColorHover3}
|
||||
}
|
||||
|
||||
.tox .tox-tbtn {
|
||||
width: ${theme.toolbarHeight}px;
|
||||
height: ${theme.toolbarHeight}px;
|
||||
min-width: ${theme.toolbarHeight}px;
|
||||
min-height: ${theme.toolbarHeight}px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
.tox .tox-tbtn[aria-haspopup=true] {
|
||||
width: ${theme.toolbarHeight + 15}px;
|
||||
min-width: ${theme.toolbarHeight + 15}px;
|
||||
}
|
||||
|
||||
.tox .tox-tbtn > span,
|
||||
.tox .tox-tbtn:active > span,
|
||||
.tox .tox-tbtn:hover > span {
|
||||
transform: scale(0.8);
|
||||
}
|
||||
|
||||
.tox .tox-toolbar__primary,
|
||||
.tox .tox-toolbar__overflow {
|
||||
background: none;
|
||||
background-color: ${theme.backgroundColor3} !important;
|
||||
}
|
||||
|
||||
.tox-tinymce,
|
||||
.tox .tox-toolbar__group,
|
||||
.tox.tox-tinymce-aux .tox-toolbar__overflow,
|
||||
.tox .tox-dialog__footer {
|
||||
border-color: ${theme.dividerColor} !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.tox-tinymce {
|
||||
border-top: none !important;
|
||||
}
|
||||
|
||||
.joplin-tinymce .tox-toolbar__group {
|
||||
background-color: ${theme.backgroundColor3};
|
||||
padding-top: ${theme.toolbarPadding}px;
|
||||
padding-bottom: ${theme.toolbarPadding}px;
|
||||
}
|
||||
`));
|
||||
|
||||
return () => {
|
||||
document.head.removeChild(element);
|
||||
};
|
||||
}, [editorReady, props.theme]);
|
||||
}, [editorReady, props.themeId]);
|
||||
|
||||
// -----------------------------------------------------------------------------------------
|
||||
// Enable or disable the editor
|
||||
@ -499,6 +510,7 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
|
||||
menubar: false,
|
||||
relative_urls: false,
|
||||
branding: false,
|
||||
statusbar: false,
|
||||
target_list: false,
|
||||
table_resize_bars: false,
|
||||
language: ['en_US', 'en_GB'].includes(language) ? undefined : language,
|
||||
@ -706,6 +718,8 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
|
||||
// The fix would be to make allAssets() return a name and a version for each asset. Then the loading
|
||||
// code would check this and either append the CSS or replace.
|
||||
|
||||
const theme = themeStyle(props.themeId);
|
||||
|
||||
let docHead_:any = null;
|
||||
|
||||
function docHead() {
|
||||
@ -1039,12 +1053,64 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
function renderExtraToolbarButton(key:string, info:ToolbarButtonInfo) {
|
||||
return <ToolbarButton
|
||||
key={key}
|
||||
themeId={props.themeId}
|
||||
toolbarButtonInfo={info}
|
||||
/>;
|
||||
}
|
||||
|
||||
const leftButtonCommandNames = ['historyBackward', 'historyForward', 'startExternalEditing'];
|
||||
|
||||
function renderLeftExtraToolbarButtons() {
|
||||
const buttons = [];
|
||||
for (const buttonName in props.noteToolbarButtonInfos) {
|
||||
if (!leftButtonCommandNames.includes(buttonName)) continue;
|
||||
const info = props.noteToolbarButtonInfos[buttonName];
|
||||
buttons.push(renderExtraToolbarButton(buttonName, info));
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={styles.leftExtraToolbarContainer}>
|
||||
{buttons}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function renderRightExtraToolbarButtons() {
|
||||
const buttons = [];
|
||||
for (const buttonName in props.noteToolbarButtonInfos) {
|
||||
if (leftButtonCommandNames.includes(buttonName)) continue;
|
||||
const info = props.noteToolbarButtonInfos[buttonName];
|
||||
|
||||
if (buttonName === 'toggleEditors') {
|
||||
buttons.push(<ToggleEditorsButton
|
||||
key={buttonName}
|
||||
value={ToggleEditorsButtonValue.RichText}
|
||||
themeId={props.themeId}
|
||||
toolbarButtonInfo={info}
|
||||
/>);
|
||||
} else {
|
||||
buttons.push(renderExtraToolbarButton(buttonName, info));
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={styles.rightExtraToolbarContainer}>
|
||||
{buttons}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Currently we don't handle resource "auto" and "manual" mode with TinyMCE
|
||||
// as it is quite complex and probably rarely used.
|
||||
function renderDisabledOverlay() {
|
||||
const status = resourcesStatus(props.resourceInfos);
|
||||
if (status === 'ready' && !draggingStarted) return null;
|
||||
|
||||
const theme = themeStyle(props.themeId);
|
||||
|
||||
const message = draggingStarted ? _('Drop notes or files here') : _('Please wait for all attachments to be downloaded and decrypted. You may also switch to %s to edit the note.', _('Code View'));
|
||||
const statusComp = draggingStarted ? null : <p style={theme.textStyleMinor}>{`Status: ${status}`}</p>;
|
||||
return (
|
||||
@ -1056,8 +1122,10 @@ const TinyMCE = (props:NoteBodyEditorProps, ref:any) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={styles.rootStyle}>
|
||||
<div style={styles.rootStyle} className="joplin-tinymce">
|
||||
{renderDisabledOverlay()}
|
||||
{renderLeftExtraToolbarButtons()}
|
||||
{renderRightExtraToolbarButtons()}
|
||||
<div style={{ width: '100%', height: '100%' }} id={rootIdRef.current}/>
|
||||
</div>
|
||||
);
|
||||
|
@ -0,0 +1,61 @@
|
||||
import { NoteBodyEditorProps } from '../../../utils/types';
|
||||
const { buildStyle } = require('lib/theme');
|
||||
|
||||
export default function styles(props:NoteBodyEditorProps) {
|
||||
return buildStyle(['TinyMCE', props.style.width, props.style.height], props.themeId, (theme:any) => {
|
||||
const extraToolbarContainer = {
|
||||
backgroundColor: theme.backgroundColor3,
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
position: 'absolute',
|
||||
height: theme.toolbarHeight,
|
||||
zIndex: 2,
|
||||
top: 0,
|
||||
padding: theme.toolbarPadding,
|
||||
};
|
||||
|
||||
return {
|
||||
disabledOverlay: {
|
||||
zIndex: 10,
|
||||
position: 'absolute',
|
||||
backgroundColor: 'white',
|
||||
opacity: 0.7,
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
padding: 20,
|
||||
paddingTop: 50,
|
||||
textAlign: 'center',
|
||||
width: '100%',
|
||||
},
|
||||
rootStyle: {
|
||||
position: 'relative',
|
||||
width: props.style.width,
|
||||
height: props.style.height,
|
||||
},
|
||||
leftExtraToolbarContainer: {
|
||||
...extraToolbarContainer,
|
||||
width: 80,
|
||||
left: 0,
|
||||
},
|
||||
rightExtraToolbarContainer: {
|
||||
...extraToolbarContainer,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
width: 70,
|
||||
right: 0,
|
||||
paddingRight: theme.mainPadding,
|
||||
},
|
||||
extraToolbarButton: {
|
||||
display: 'flex',
|
||||
border: 'none',
|
||||
background: 'none',
|
||||
},
|
||||
extraToolbarButtonIcon: {
|
||||
fontSize: theme.toolbarIconSize,
|
||||
color: theme.color3,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
@ -13,13 +13,18 @@ import useMessageHandler from './utils/useMessageHandler';
|
||||
import useWindowCommandHandler from './utils/useWindowCommandHandler';
|
||||
import useDropHandler from './utils/useDropHandler';
|
||||
import useMarkupToHtml from './utils/useMarkupToHtml';
|
||||
import useNoteToolbarButtons from './utils/useNoteToolbarButtons';
|
||||
import useFormNote, { OnLoadEvent } from './utils/useFormNote';
|
||||
import useFolder from './utils/useFolder';
|
||||
import styles_ from './styles';
|
||||
import { NoteEditorProps, FormNote, ScrollOptions, ScrollOptionTypes, OnChangeEvent, NoteBodyEditorProps } from './utils/types';
|
||||
import ResourceEditWatcher from '../../lib/services/ResourceEditWatcher/index';
|
||||
import CommandService from '../../lib/services/CommandService';
|
||||
import CommandService from 'lib/services/CommandService';
|
||||
import ToolbarButton from '../ToolbarButton/ToolbarButton';
|
||||
import Button, { ButtonLevel } from '../Button/Button';
|
||||
|
||||
const { themeStyle } = require('lib/theme');
|
||||
const { substrWithEllipsis } = require('lib/string-utils');
|
||||
const NoteSearchBar = require('../NoteSearchBar.min.js');
|
||||
const { reg } = require('lib/registry.js');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
@ -70,6 +75,8 @@ function NoteEditor(props: NoteEditorProps) {
|
||||
const formNoteRef = useRef<FormNote>();
|
||||
formNoteRef.current = { ...formNote };
|
||||
|
||||
const formNoteFolder = useFolder({ folderId: formNote.parent_id });
|
||||
|
||||
const {
|
||||
localSearch,
|
||||
onChange: localSearch_change,
|
||||
@ -133,17 +140,17 @@ function NoteEditor(props: NoteEditorProps) {
|
||||
return formNote.saveActionQueue.waitForAllDone();
|
||||
}
|
||||
|
||||
const markupToHtml = useMarkupToHtml({ themeId: props.theme, customCss: props.customCss });
|
||||
const markupToHtml = useMarkupToHtml({ themeId: props.themeId, customCss: props.customCss });
|
||||
|
||||
const allAssets = useCallback(async (markupLanguage: number): Promise<any[]> => {
|
||||
const theme = themeStyle(props.theme);
|
||||
const theme = themeStyle(props.themeId);
|
||||
|
||||
const markupToHtml = markupLanguageUtils.newMarkupToHtml({
|
||||
resourceBaseUrl: `file://${Setting.value('resourceDir')}/`,
|
||||
});
|
||||
|
||||
return markupToHtml.allAssets(markupLanguage, theme);
|
||||
}, [props.theme]);
|
||||
}, [props.themeId]);
|
||||
|
||||
const handleProvisionalFlag = useCallback(() => {
|
||||
if (props.isProvisional) {
|
||||
@ -331,25 +338,49 @@ function NoteEditor(props: NoteEditorProps) {
|
||||
}
|
||||
|
||||
function renderNoteToolbar() {
|
||||
// const theme = themeStyle(props.themeId);
|
||||
|
||||
const toolbarStyle = {
|
||||
marginBottom: 0,
|
||||
// paddingTop: theme.mainPadding,
|
||||
// paddingBottom: theme.mainPadding,
|
||||
};
|
||||
|
||||
return <NoteToolbar
|
||||
theme={props.theme}
|
||||
themeId={props.themeId}
|
||||
note={formNote}
|
||||
style={toolbarStyle}
|
||||
/>;
|
||||
}
|
||||
|
||||
function renderTagButton() {
|
||||
const info = CommandService.instance().commandToToolbarButton('setTags');
|
||||
return <ToolbarButton
|
||||
themeId={props.themeId}
|
||||
toolbarButtonInfo={info}
|
||||
/>;
|
||||
}
|
||||
|
||||
function renderTagBar() {
|
||||
return props.selectedNoteTags.length ? <TagList items={props.selectedNoteTags} /> : null;
|
||||
const theme = themeStyle(props.themeId);
|
||||
let control = null;
|
||||
if (!props.selectedNoteTags.length) {
|
||||
const noteIds = [formNote.id];
|
||||
control = <span onClick={() => { CommandService.instance().execute('setTags', { noteIds }); }} style={theme.clickableTextStyle}>Click to add some tags...</span>;
|
||||
} else {
|
||||
control = <TagList items={props.selectedNoteTags} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ paddingLeft: 8 }}>{control}</div>
|
||||
);
|
||||
}
|
||||
|
||||
function renderTitleBar() {
|
||||
const theme = themeStyle(props.themeId);
|
||||
const titleBarDate = <span style={styles.titleDate}>{time.formatMsToLocal(formNote.user_updated_time)}</span>;
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', height: theme.topRowHeight }}>
|
||||
<input
|
||||
type="text"
|
||||
ref={titleInputRef}
|
||||
@ -360,6 +391,7 @@ function NoteEditor(props: NoteEditorProps) {
|
||||
value={formNote.title}
|
||||
/>
|
||||
{titleBarDate}
|
||||
{renderNoteToolbar()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -381,7 +413,7 @@ function NoteEditor(props: NoteEditorProps) {
|
||||
markupToHtml: markupToHtml,
|
||||
allAssets: allAssets,
|
||||
disabled: false,
|
||||
theme: props.theme,
|
||||
themeId: props.themeId,
|
||||
dispatch: props.dispatch,
|
||||
noteToolbar: null,// renderNoteToolbar(),
|
||||
onScroll: onScroll,
|
||||
@ -391,6 +423,7 @@ function NoteEditor(props: NoteEditorProps) {
|
||||
keyboardMode: Setting.value('editor.keyboardMode'),
|
||||
locale: Setting.value('locale'),
|
||||
onDrop: onDrop,
|
||||
noteToolbarButtonInfos: useNoteToolbarButtons(),
|
||||
};
|
||||
|
||||
let editor = null;
|
||||
@ -414,10 +447,10 @@ function NoteEditor(props: NoteEditorProps) {
|
||||
}, []);
|
||||
|
||||
if (showRevisions) {
|
||||
const theme = themeStyle(props.theme);
|
||||
const theme = themeStyle(props.themeId);
|
||||
|
||||
const revStyle = {
|
||||
...props.style,
|
||||
const revStyle:any = {
|
||||
// ...props.style,
|
||||
display: 'inline-flex',
|
||||
padding: theme.margin,
|
||||
verticalAlign: 'top',
|
||||
@ -433,19 +466,18 @@ function NoteEditor(props: NoteEditorProps) {
|
||||
|
||||
if (props.selectedNoteIds.length > 1) {
|
||||
return <MultiNoteActions
|
||||
theme={props.theme}
|
||||
themeId={props.themeId}
|
||||
selectedNoteIds={props.selectedNoteIds}
|
||||
notes={props.notes}
|
||||
dispatch={props.dispatch}
|
||||
watchedNoteFiles={props.watchedNoteFiles}
|
||||
style={props.style}
|
||||
/>;
|
||||
}
|
||||
|
||||
function renderSearchBar() {
|
||||
if (!showLocalSearch) return false;
|
||||
|
||||
const theme = themeStyle(props.theme);
|
||||
const theme = themeStyle(props.themeId);
|
||||
|
||||
return (
|
||||
<NoteSearchBar
|
||||
@ -479,6 +511,30 @@ function NoteEditor(props: NoteEditorProps) {
|
||||
);
|
||||
}
|
||||
|
||||
function renderSearchInfo() {
|
||||
if (formNoteFolder && ['Search', 'Tag', 'SmartFilter'].includes(props.notesParentType)) {
|
||||
return (
|
||||
<div style={{ paddingTop: 10, paddingBottom: 10 }}>
|
||||
<Button
|
||||
iconName="icon-notebooks"
|
||||
level={ButtonLevel.Primary}
|
||||
title={_('In: %s', substrWithEllipsis(formNoteFolder.title, 0, 100))}
|
||||
onClick={() => {
|
||||
props.dispatch({
|
||||
type: 'FOLDER_AND_NOTE_SELECT',
|
||||
folderId: formNoteFolder.id,
|
||||
noteId: formNote.id,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<div style={{ flex: 1 }}></div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (formNote.encryption_applied || !formNote.id || !props.noteId) {
|
||||
return renderNoNotes(styles.root);
|
||||
}
|
||||
@ -488,15 +544,17 @@ function NoteEditor(props: NoteEditorProps) {
|
||||
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||
{renderResourceWatchingNotification()}
|
||||
{renderTitleBar()}
|
||||
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
|
||||
{renderNoteToolbar()}{renderTagBar()}
|
||||
</div>
|
||||
{renderSearchInfo()}
|
||||
<div style={{ display: 'flex', flex: 1 }}>
|
||||
{editor}
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
|
||||
{renderSearchBar()}
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', height: 40 }}>
|
||||
{renderTagButton()}
|
||||
{renderTagBar()}
|
||||
</div>
|
||||
{wysiwygBanner}
|
||||
</div>
|
||||
</div>
|
||||
@ -518,7 +576,7 @@ const mapStateToProps = (state: any) => {
|
||||
isProvisional: state.provisionalNoteIds.includes(noteId),
|
||||
editorNoteStatuses: state.editorNoteStatuses,
|
||||
syncStarted: state.syncStarted,
|
||||
theme: state.settings.theme,
|
||||
themeId: state.settings.theme,
|
||||
watchedNoteFiles: state.watchedNoteFiles,
|
||||
notesParentType: state.notesParentType,
|
||||
selectedNoteTags: state.selectedNoteTags,
|
||||
|
@ -28,57 +28,57 @@ const declarations:CommandDeclaration[] = [
|
||||
{
|
||||
name: 'textBold',
|
||||
label: () => _('Bold'),
|
||||
iconName: 'fa-bold',
|
||||
iconName: 'icon-bold',
|
||||
},
|
||||
{
|
||||
name: 'textItalic',
|
||||
label: () => _('Italic'),
|
||||
iconName: 'fa-italic',
|
||||
iconName: 'icon-italic',
|
||||
},
|
||||
{
|
||||
name: 'textLink',
|
||||
label: () => _('Hyperlink'),
|
||||
iconName: 'fa-link',
|
||||
iconName: 'icon-link',
|
||||
},
|
||||
{
|
||||
name: 'textCode',
|
||||
label: () => _('Code'),
|
||||
iconName: 'fa-code',
|
||||
iconName: 'icon-code',
|
||||
},
|
||||
{
|
||||
name: 'attachFile',
|
||||
label: () => _('Attach file'),
|
||||
iconName: 'fa-paperclip',
|
||||
iconName: 'icon-attachment',
|
||||
},
|
||||
{
|
||||
name: 'textNumberedList',
|
||||
label: () => _('Numbered List'),
|
||||
iconName: 'fa-list-ol',
|
||||
iconName: 'icon-numbered-list',
|
||||
},
|
||||
{
|
||||
name: 'textBulletedList',
|
||||
label: () => _('Bulleted List'),
|
||||
iconName: 'fa-list-ul',
|
||||
iconName: 'icon-bulleted-list',
|
||||
},
|
||||
{
|
||||
name: 'textCheckbox',
|
||||
label: () => _('Checkbox'),
|
||||
iconName: 'fa-check-square',
|
||||
iconName: 'icon-to-do-list',
|
||||
},
|
||||
{
|
||||
name: 'textHeading',
|
||||
label: () => _('Heading'),
|
||||
iconName: 'fa-heading',
|
||||
iconName: 'icon-heading',
|
||||
},
|
||||
{
|
||||
name: 'textHorizontalRule',
|
||||
label: () => _('Horizontal Rule'),
|
||||
iconName: 'fa-ellipsis-h',
|
||||
iconName: 'fas fa-ellipsis-h',
|
||||
},
|
||||
{
|
||||
name: 'insertDateTime',
|
||||
label: () => _('Insert Date Time'),
|
||||
iconName: 'fa-calendar-plus',
|
||||
iconName: 'icon-add-date',
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -3,16 +3,18 @@ import { NoteEditorProps } from '../utils/types';
|
||||
const { buildStyle } = require('lib/theme');
|
||||
|
||||
export default function styles(props: NoteEditorProps) {
|
||||
return buildStyle(['NoteEditor', props.style.width, props.style.height], props.theme, (theme: any) => {
|
||||
return buildStyle(['NoteEditor'], props.themeId, (theme: any) => {
|
||||
return {
|
||||
root: {
|
||||
...props.style,
|
||||
// ...props.style,
|
||||
boxSizing: 'border-box',
|
||||
paddingLeft: 10,
|
||||
paddingTop: 5,
|
||||
paddingLeft: theme.mainPadding,
|
||||
paddingTop: 0,
|
||||
borderLeftWidth: 1,
|
||||
borderLeftColor: theme.dividerColor,
|
||||
borderLeftStyle: 'solid',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
},
|
||||
titleInput: {
|
||||
flex: 1,
|
||||
@ -20,16 +22,15 @@ export default function styles(props: NoteEditorProps) {
|
||||
paddingTop: 5,
|
||||
minHeight: 35,
|
||||
boxSizing: 'border-box',
|
||||
fontWeight: 'bold',
|
||||
paddingBottom: 5,
|
||||
paddingLeft: 8,
|
||||
paddingLeft: 0,
|
||||
paddingRight: 8,
|
||||
marginLeft: 5,
|
||||
// marginRight: theme.paddingLeft,
|
||||
color: theme.textStyle.color,
|
||||
fontSize: theme.textStyle.fontSize * 1.25,
|
||||
fontSize: Math.round(theme.textStyle.fontSize * 1.5),
|
||||
backgroundColor: theme.backgroundColor,
|
||||
border: '1px solid',
|
||||
borderColor: theme.dividerColor,
|
||||
border: 'none',
|
||||
},
|
||||
warningBanner: {
|
||||
background: theme.warningBackgroundColor,
|
||||
|
@ -1,10 +1,15 @@
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import AsyncActionQueue from '../../../lib/AsyncActionQueue';
|
||||
import { ToolbarButtonInfo } from 'lib/services/CommandService';
|
||||
|
||||
export interface ToolbarButtonInfos {
|
||||
[key:string]: ToolbarButtonInfo;
|
||||
}
|
||||
|
||||
export interface NoteEditorProps {
|
||||
style: any;
|
||||
// style: any;
|
||||
noteId: string;
|
||||
theme: number;
|
||||
themeId: number;
|
||||
dispatch: Function;
|
||||
selectedNoteIds: string[];
|
||||
notes: any[];
|
||||
@ -29,7 +34,7 @@ export interface NoteEditorProps {
|
||||
export interface NoteBodyEditorProps {
|
||||
style: any;
|
||||
ref: any,
|
||||
theme: number;
|
||||
themeId: number;
|
||||
content: string,
|
||||
contentKey: string,
|
||||
contentMarkupLanguage: number,
|
||||
@ -51,6 +56,7 @@ export interface NoteBodyEditorProps {
|
||||
resourceInfos: ResourceInfos,
|
||||
locale: string,
|
||||
onDrop: Function,
|
||||
noteToolbarButtonInfos: ToolbarButtonInfos,
|
||||
}
|
||||
|
||||
export interface FormNote {
|
||||
|
29
ElectronClient/gui/NoteEditor/utils/useFolder.ts
Normal file
29
ElectronClient/gui/NoteEditor/utils/useFolder.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
const Folder = require('lib/models/Folder');
|
||||
|
||||
interface HookDependencies {
|
||||
folderId: string,
|
||||
}
|
||||
|
||||
export default function(dependencies:HookDependencies) {
|
||||
const { folderId } = dependencies;
|
||||
const [folder, setFolder] = useState(null);
|
||||
|
||||
useEffect(function() {
|
||||
let cancelled = false;
|
||||
|
||||
async function loadFolder() {
|
||||
const f = await Folder.load(folderId);
|
||||
if (cancelled) return;
|
||||
setFolder(f);
|
||||
}
|
||||
|
||||
loadFolder();
|
||||
|
||||
return function() {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [folderId]);
|
||||
|
||||
return folder;
|
||||
}
|
33
ElectronClient/gui/NoteEditor/utils/useNoteToolbarButtons.ts
Normal file
33
ElectronClient/gui/NoteEditor/utils/useNoteToolbarButtons.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import CommandService, { ToolbarButtonInfo } from 'lib/services/CommandService';
|
||||
|
||||
interface ToolbarButtonInfos {
|
||||
[key:string]: ToolbarButtonInfo;
|
||||
}
|
||||
|
||||
export default function useNoteToolbarButtons():ToolbarButtonInfos {
|
||||
const [noteToolbarButtons, setNoteToolbarButtons] = useState<ToolbarButtonInfos>({});
|
||||
|
||||
function update() {
|
||||
const buttonNames = ['historyBackward', 'historyForward', 'toggleEditors', 'startExternalEditing'];
|
||||
const output:ToolbarButtonInfos = {};
|
||||
|
||||
for (const buttonName of buttonNames) {
|
||||
output[buttonName] = CommandService.instance().commandToToolbarButton(buttonName);
|
||||
}
|
||||
|
||||
setNoteToolbarButtons(output);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
update();
|
||||
|
||||
CommandService.instance().on('commandsEnabledStateChange', update);
|
||||
|
||||
return () => {
|
||||
CommandService.instance().off('commandsEnabledStateChange', update);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return noteToolbarButtons;
|
||||
}
|
@ -12,11 +12,18 @@ const Setting = require('lib/models/Setting');
|
||||
const NoteListUtils = require('../utils/NoteListUtils');
|
||||
const NoteListItem = require('../NoteListItem').default;
|
||||
const CommandService = require('lib/services/CommandService.js').default;
|
||||
const styled = require('styled-components').default;
|
||||
|
||||
const commands = [
|
||||
require('./commands/focusElementNoteList'),
|
||||
];
|
||||
|
||||
const StyledRoot = styled.div`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: ${(props:any) => props.theme.backgroundColor3};
|
||||
`;
|
||||
|
||||
class NoteListComponent extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
@ -27,12 +34,15 @@ class NoteListComponent extends React.Component {
|
||||
|
||||
this.state = {
|
||||
dragOverTargetNoteIndex: null,
|
||||
width: 0,
|
||||
height: 0,
|
||||
};
|
||||
|
||||
this.noteListRef = React.createRef();
|
||||
this.itemListRef = React.createRef();
|
||||
this.itemAnchorRefs_ = {};
|
||||
|
||||
this.itemRenderer = this.itemRenderer.bind(this);
|
||||
this.renderItem = this.renderItem.bind(this);
|
||||
this.onKeyDown = this.onKeyDown.bind(this);
|
||||
this.noteItem_titleClick = this.noteItem_titleClick.bind(this);
|
||||
this.noteItem_noteDragOver = this.noteItem_noteDragOver.bind(this);
|
||||
@ -43,12 +53,13 @@ class NoteListComponent extends React.Component {
|
||||
this.registerGlobalDragEndEvent_ = this.registerGlobalDragEndEvent_.bind(this);
|
||||
this.unregisterGlobalDragEndEvent_ = this.unregisterGlobalDragEndEvent_.bind(this);
|
||||
this.itemContextMenu = this.itemContextMenu.bind(this);
|
||||
this.resizableLayout_resize = this.resizableLayout_resize.bind(this);
|
||||
}
|
||||
|
||||
style() {
|
||||
if (this.styleCache_ && this.styleCache_[this.props.theme]) return this.styleCache_[this.props.theme];
|
||||
if (this.styleCache_ && this.styleCache_[this.props.themeId]) return this.styleCache_[this.props.themeId];
|
||||
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
const style = {
|
||||
root: {
|
||||
@ -85,12 +96,12 @@ class NoteListComponent extends React.Component {
|
||||
};
|
||||
|
||||
this.styleCache_ = {};
|
||||
this.styleCache_[this.props.theme] = style;
|
||||
this.styleCache_[this.props.themeId] = style;
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
itemContextMenu(event) {
|
||||
itemContextMenu(event:any) {
|
||||
const currentItemId = event.currentTarget.getAttribute('data-id');
|
||||
if (!currentItemId) return;
|
||||
|
||||
@ -128,11 +139,11 @@ class NoteListComponent extends React.Component {
|
||||
document.removeEventListener('dragend', this.onGlobalDrop_);
|
||||
}
|
||||
|
||||
dragTargetNoteIndex_(event) {
|
||||
return Math.abs(Math.round((event.clientY - this.itemListRef.current.offsetTop()) / this.itemHeight));
|
||||
dragTargetNoteIndex_(event:any) {
|
||||
return Math.abs(Math.round((event.clientY - this.itemListRef.current.offsetTop() + this.itemListRef.current.offsetScroll()) / this.itemHeight));
|
||||
}
|
||||
|
||||
noteItem_noteDragOver(event) {
|
||||
noteItem_noteDragOver(event:any) {
|
||||
if (this.props.notesParentType !== 'Folder') return;
|
||||
|
||||
const dt = event.dataTransfer;
|
||||
@ -146,7 +157,7 @@ class NoteListComponent extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
async noteItem_noteDrop(event) {
|
||||
async noteItem_noteDrop(event:any) {
|
||||
if (this.props.notesParentType !== 'Folder') return;
|
||||
|
||||
if (this.props.noteSortOrder !== 'order') {
|
||||
@ -172,7 +183,7 @@ class NoteListComponent extends React.Component {
|
||||
}
|
||||
|
||||
|
||||
async noteItem_checkboxClick(event, item) {
|
||||
async noteItem_checkboxClick(event:any, item:any) {
|
||||
const checked = event.target.checked;
|
||||
const newNote = {
|
||||
id: item.id,
|
||||
@ -182,7 +193,7 @@ class NoteListComponent extends React.Component {
|
||||
eventManager.emit('todoToggle', { noteId: item.id, note: newNote });
|
||||
}
|
||||
|
||||
async noteItem_titleClick(event, item) {
|
||||
async noteItem_titleClick(event:any, item:any) {
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
event.preventDefault();
|
||||
this.props.dispatch({
|
||||
@ -203,7 +214,7 @@ class NoteListComponent extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
noteItem_dragStart(event) {
|
||||
noteItem_dragStart(event:any) {
|
||||
let noteIds = [];
|
||||
|
||||
// Here there is two cases:
|
||||
@ -223,7 +234,7 @@ class NoteListComponent extends React.Component {
|
||||
event.dataTransfer.setData('text/x-jop-note-ids', JSON.stringify(noteIds));
|
||||
}
|
||||
|
||||
itemRenderer(item, index) {
|
||||
renderItem(item:any, index:number) {
|
||||
const highlightedWords = () => {
|
||||
if (this.props.notesParentType === 'Search') {
|
||||
const query = BaseModel.byId(this.props.searches, this.props.selectedSearchId);
|
||||
@ -240,11 +251,12 @@ class NoteListComponent extends React.Component {
|
||||
return <NoteListItem
|
||||
ref={ref}
|
||||
key={item.id}
|
||||
style={this.style(this.props.theme)}
|
||||
style={this.style()}
|
||||
item={item}
|
||||
index={index}
|
||||
theme={this.props.theme}
|
||||
width={this.props.style.width}
|
||||
themeId={this.props.themeId}
|
||||
width={this.state.width}
|
||||
height={this.itemHeight}
|
||||
dragItemIndex={this.state.dragOverTargetNoteIndex}
|
||||
highlightedWords={highlightedWords()}
|
||||
isProvisional={this.props.provisionalNoteIds.includes(item.id)}
|
||||
@ -260,12 +272,12 @@ class NoteListComponent extends React.Component {
|
||||
/>;
|
||||
}
|
||||
|
||||
itemAnchorRef(itemId) {
|
||||
itemAnchorRef(itemId:string) {
|
||||
if (this.itemAnchorRefs_[itemId] && this.itemAnchorRefs_[itemId].current) return this.itemAnchorRefs_[itemId].current;
|
||||
return null;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
componentDidUpdate(prevProps:any) {
|
||||
if (prevProps.selectedNoteIds !== this.props.selectedNoteIds && this.props.selectedNoteIds.length === 1) {
|
||||
const id = this.props.selectedNoteIds[0];
|
||||
const doRefocus = this.props.notes.length < prevProps.notes.length;
|
||||
@ -281,9 +293,13 @@ class NoteListComponent extends React.Component {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (prevProps.visible !== this.props.visible) {
|
||||
this.updateSizeState();
|
||||
}
|
||||
}
|
||||
|
||||
scrollNoteIndex_(keyCode, ctrlKey, metaKey, noteIndex) {
|
||||
scrollNoteIndex_(keyCode:any, ctrlKey:any, metaKey:any, noteIndex:any) {
|
||||
|
||||
if (keyCode === 33) {
|
||||
// Page Up
|
||||
@ -314,7 +330,7 @@ class NoteListComponent extends React.Component {
|
||||
return noteIndex;
|
||||
}
|
||||
|
||||
async onKeyDown(event) {
|
||||
async onKeyDown(event:any) {
|
||||
const keyCode = event.keyCode;
|
||||
const noteIds = this.props.selectedNoteIds;
|
||||
|
||||
@ -350,7 +366,7 @@ class NoteListComponent extends React.Component {
|
||||
event.preventDefault();
|
||||
|
||||
const notes = BaseModel.modelsByIds(this.props.notes, noteIds);
|
||||
const todos = notes.filter(n => !!n.is_todo);
|
||||
const todos = notes.filter((n:any) => !!n.is_todo);
|
||||
if (!todos.length) return;
|
||||
|
||||
for (let i = 0; i < todos.length; i++) {
|
||||
@ -382,7 +398,7 @@ class NoteListComponent extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
focusNoteId_(noteId) {
|
||||
focusNoteId_(noteId:string) {
|
||||
// - We need to focus the item manually otherwise focus might be lost when the
|
||||
// list is scrolled and items within it are being rebuilt.
|
||||
// - We need to use an interval because when leaving the arrow pressed, the rendering
|
||||
@ -401,56 +417,86 @@ class NoteListComponent extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
updateSizeState() {
|
||||
this.setState({
|
||||
width: this.noteListRef.current.clientWidth,
|
||||
height: this.noteListRef.current.clientHeight,
|
||||
});
|
||||
}
|
||||
|
||||
resizableLayout_resize() {
|
||||
this.updateSizeState();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.resizableLayoutEventEmitter.on('resize', this.resizableLayout_resize);
|
||||
this.updateSizeState();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.focusItemIID_) {
|
||||
clearInterval(this.focusItemIID_);
|
||||
this.focusItemIID_ = null;
|
||||
}
|
||||
|
||||
this.props.resizableLayoutEventEmitter.off('resize', this.resizableLayout_resize);
|
||||
|
||||
CommandService.instance().componentUnregisterCommands(commands);
|
||||
}
|
||||
|
||||
renderEmptyList() {
|
||||
if (this.props.notes.length) return null;
|
||||
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
const padding = 10;
|
||||
const emptyDivStyle = {
|
||||
padding: `${padding}px`,
|
||||
fontSize: theme.fontSize,
|
||||
color: theme.color,
|
||||
backgroundColor: theme.backgroundColor,
|
||||
fontFamily: theme.fontFamily,
|
||||
};
|
||||
// emptyDivStyle.width = emptyDivStyle.width - padding * 2;
|
||||
// emptyDivStyle.height = emptyDivStyle.height - padding * 2;
|
||||
return <div style={emptyDivStyle}>{this.props.folders.length ? _('No notes in here. Create one by clicking on "New note".') : _('There is currently no notebook. Create one by clicking on "New notebook".')}</div>;
|
||||
}
|
||||
|
||||
renderItemList(style:any) {
|
||||
if (!this.props.notes.length) return null;
|
||||
|
||||
return (
|
||||
<ItemList
|
||||
ref={this.itemListRef}
|
||||
disabled={this.props.isInsertingNotes}
|
||||
itemHeight={this.style().listItem.height}
|
||||
className={'note-list'}
|
||||
items={this.props.notes}
|
||||
style={style}
|
||||
itemRenderer={this.renderItem}
|
||||
onKeyDown={this.onKeyDown}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const style = this.props.style;
|
||||
if (!this.props.size) throw new Error('props.size is required');
|
||||
|
||||
if (!this.props.notes.length) {
|
||||
const padding = 10;
|
||||
const emptyDivStyle = Object.assign(
|
||||
{
|
||||
padding: `${padding}px`,
|
||||
fontSize: theme.fontSize,
|
||||
color: theme.color,
|
||||
backgroundColor: theme.backgroundColor,
|
||||
fontFamily: theme.fontFamily,
|
||||
},
|
||||
style
|
||||
);
|
||||
emptyDivStyle.width = emptyDivStyle.width - padding * 2;
|
||||
emptyDivStyle.height = emptyDivStyle.height - padding * 2;
|
||||
return <div style={emptyDivStyle}>{this.props.folders.length ? _('No notes in here. Create one by clicking on "New note".') : _('There is currently no notebook. Create one by clicking on "New notebook".')}</div>;
|
||||
}
|
||||
|
||||
return <ItemList
|
||||
ref={this.itemListRef}
|
||||
disabled={this.props.isInsertingNotes}
|
||||
itemHeight={this.style(this.props.theme).listItem.height}
|
||||
className={'note-list'}
|
||||
items={this.props.notes}
|
||||
style={style}
|
||||
itemRenderer={this.itemRenderer}
|
||||
onKeyDown={this.onKeyDown}
|
||||
/>;
|
||||
return (
|
||||
<StyledRoot ref={this.noteListRef}>
|
||||
{this.renderEmptyList()}
|
||||
{this.renderItemList(this.props.size)}
|
||||
</StyledRoot>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const mapStateToProps = (state:any) => {
|
||||
return {
|
||||
notes: state.notes,
|
||||
folders: state.folders,
|
||||
selectedNoteIds: state.selectedNoteIds,
|
||||
selectedFolderId: state.selectedFolderId,
|
||||
theme: state.settings.theme,
|
||||
themeId: state.settings.theme,
|
||||
notesParentType: state.notesParentType,
|
||||
searches: state.searches,
|
||||
selectedSearchId: state.selectedSearchId,
|
||||
@ -462,6 +508,4 @@ const mapStateToProps = state => {
|
||||
};
|
||||
};
|
||||
|
||||
const NoteList = connect(mapStateToProps)(NoteListComponent);
|
||||
|
||||
module.exports = { NoteList };
|
||||
export default connect(mapStateToProps)(NoteListComponent);
|
58
ElectronClient/gui/NoteListControls/NoteListControls.tsx
Normal file
58
ElectronClient/gui/NoteListControls/NoteListControls.tsx
Normal file
@ -0,0 +1,58 @@
|
||||
import * as React from 'react';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import SearchBar from '../SearchBar/SearchBar';
|
||||
import Button, { ButtonLevel } from '../Button/Button';
|
||||
import CommandService from 'lib/services/CommandService';
|
||||
import { runtime as focusSearchRuntime } from './commands/focusSearch';
|
||||
const styled = require('styled-components').default;
|
||||
|
||||
const StyledRoot = styled.div`
|
||||
width: 100%;
|
||||
/*height: 100%;*/
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: ${(props:any) => props.theme.mainPadding}px;
|
||||
background-color: ${(props:any) => props.theme.backgroundColor3};
|
||||
`;
|
||||
|
||||
const StyledButton = styled(Button)`
|
||||
margin-left: 8px;
|
||||
`;
|
||||
|
||||
export default function NoteListControls() {
|
||||
const searchBarRef = useRef(null);
|
||||
|
||||
useEffect(function() {
|
||||
CommandService.instance().registerRuntime('focusSearch', focusSearchRuntime(searchBarRef));
|
||||
|
||||
return function() {
|
||||
CommandService.instance().unregisterRuntime('focusSearch');
|
||||
};
|
||||
}, []);
|
||||
|
||||
function onNewTodoButtonClick() {
|
||||
CommandService.instance().execute('newTodo');
|
||||
}
|
||||
|
||||
function onNewNoteButtonClick() {
|
||||
CommandService.instance().execute('newNote');
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledRoot>
|
||||
<SearchBar inputRef={searchBarRef}/>
|
||||
<StyledButton
|
||||
tooltip={CommandService.instance().title('newTodo')}
|
||||
iconName="far fa-check-square"
|
||||
level={ButtonLevel.Primary}
|
||||
onClick={onNewTodoButtonClick}
|
||||
/>
|
||||
<StyledButton
|
||||
tooltip={CommandService.instance().title('newNote')}
|
||||
iconName="icon-note"
|
||||
level={ButtonLevel.Primary}
|
||||
onClick={onNewNoteButtonClick}
|
||||
/>
|
||||
</StyledRoot>
|
||||
);
|
||||
}
|
@ -6,10 +6,10 @@ export const declaration:CommandDeclaration = {
|
||||
label: () => _('Search in all the notes'),
|
||||
};
|
||||
|
||||
export const runtime = (comp:any):CommandRuntime => {
|
||||
export const runtime = (searchBarRef:any):CommandRuntime => {
|
||||
return {
|
||||
execute: async () => {
|
||||
if (comp.searchElement_) comp.searchElement_.focus();
|
||||
if (searchBarRef.current) searchBarRef.current.focus();
|
||||
},
|
||||
};
|
||||
};
|
@ -5,10 +5,45 @@ const Mark = require('mark.js/dist/mark.min.js');
|
||||
const markJsUtils = require('lib/markJsUtils');
|
||||
const Note = require('lib/models/Note');
|
||||
const { replaceRegexDiacritics, pregQuote } = require('lib/string-utils');
|
||||
const styled = require('styled-components').default;
|
||||
|
||||
const StyledRoot = styled.div`
|
||||
width: ${(props:any) => props.width}px;
|
||||
height: ${(props:any) => props.height}px;
|
||||
opacity: ${(props:any) => props.isProvisional ? '0.5' : '1'};
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
position: relative;
|
||||
background-color: ${(props:any) => props.selected ? props.theme.selectedColor : 'none'};
|
||||
|
||||
border-style: solid;
|
||||
border-color: ${(props:any) => props.theme.color};
|
||||
border-top-width: ${(props:any) => props.dragItemPosition === 'top' ? 2 : 0}px;
|
||||
border-bottom-width: ${(props:any) => props.dragItemPosition === 'bottom' ? 2 : 0}px;
|
||||
border-right: none;
|
||||
border-left: none;
|
||||
|
||||
// https://stackoverflow.com/questions/50174448/css-how-to-add-white-space-before-elements-border
|
||||
&::before {
|
||||
content: '';
|
||||
border-bottom: 1px solid ${(props:any) => props.theme.dividerColor};
|
||||
width: ${(props:any) => props.width - 32}px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 16px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: ${(props:any) => props.theme.backgroundColorHover3};
|
||||
}
|
||||
`;
|
||||
|
||||
interface NoteListItemProps {
|
||||
theme: number,
|
||||
themeId: number,
|
||||
width: number,
|
||||
height: number,
|
||||
style: any,
|
||||
dragItemIndex: number,
|
||||
highlightedWords: string[],
|
||||
@ -28,8 +63,8 @@ interface NoteListItemProps {
|
||||
|
||||
function NoteListItem(props:NoteListItemProps, ref:any) {
|
||||
const item = props.item;
|
||||
const theme = themeStyle(props.theme);
|
||||
const hPadding = 10;
|
||||
const theme = themeStyle(props.themeId);
|
||||
const hPadding = 16;
|
||||
|
||||
const anchorRef = useRef(null);
|
||||
|
||||
@ -41,14 +76,11 @@ function NoteListItem(props:NoteListItemProps, ref:any) {
|
||||
};
|
||||
});
|
||||
|
||||
let rootStyle = Object.assign({ width: props.width, opacity: props.isProvisional ? 0.5 : 1 }, props.style.listItem);
|
||||
|
||||
if (props.isSelected) rootStyle = Object.assign(rootStyle, props.style.listItemSelected);
|
||||
|
||||
let dragItemPosition = '';
|
||||
if (props.dragItemIndex === props.index) {
|
||||
rootStyle.borderTop = `2px solid ${theme.color}`;
|
||||
dragItemPosition = 'top';
|
||||
} else if (props.index === props.itemCount - 1 && props.dragItemIndex >= props.itemCount) {
|
||||
rootStyle.borderBottom = `2px solid ${theme.color}`;
|
||||
dragItemPosition = 'bottom';
|
||||
}
|
||||
|
||||
const onTitleClick = useCallback((event) => {
|
||||
@ -65,7 +97,7 @@ function NoteListItem(props:NoteListItemProps, ref:any) {
|
||||
if (!item.is_todo) return null;
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', height: rootStyle.height, alignItems: 'center', paddingLeft: hPadding }}>
|
||||
<div style={{ display: 'flex', height: props.height, alignItems: 'center', paddingLeft: hPadding }}>
|
||||
<input
|
||||
style={{ margin: 0, marginBottom: 1, marginRight: 5 }}
|
||||
type="checkbox"
|
||||
@ -118,12 +150,19 @@ function NoteListItem(props:NoteListItemProps, ref:any) {
|
||||
};
|
||||
const watchedIcon = props.isWatched ? null : <i style={watchedIconStyle} className={'fa fa-share-square'}></i>;
|
||||
|
||||
// key={`${item.id}_${item.todo_completed}`}
|
||||
|
||||
// Need to include "todo_completed" in key so that checkbox is updated when
|
||||
// item is changed via sync.
|
||||
return (
|
||||
<div className="list-item-container" style={rootStyle} onDragOver={props.onNoteDragOver} onDrop={props.onNoteDrop}>
|
||||
<StyledRoot
|
||||
className="list-item-container"
|
||||
onDragOver={props.onNoteDragOver}
|
||||
onDrop={props.onNoteDrop}
|
||||
width={props.width}
|
||||
height={props.height}
|
||||
isProvisional={props.isProvisional}
|
||||
selected={props.isSelected}
|
||||
dragItemPosition={dragItemPosition}
|
||||
>
|
||||
{renderCheckbox()}
|
||||
<a
|
||||
ref={anchorRef}
|
||||
@ -138,7 +177,7 @@ function NoteListItem(props:NoteListItemProps, ref:any) {
|
||||
{watchedIcon}
|
||||
{titleComp}
|
||||
</a>
|
||||
</div>
|
||||
</StyledRoot>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -224,8 +224,8 @@ class NotePropertiesDialog extends React.Component {
|
||||
}
|
||||
|
||||
createNoteField(key, value) {
|
||||
const styles = this.styles(this.props.theme);
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const styles = this.styles(this.props.themeId);
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
const labelComp = <label style={Object.assign({}, theme.textStyle, theme.controlBoxLabel)}>{this.formatLabel(key)}</label>;
|
||||
let controlComp = null;
|
||||
let editComp = null;
|
||||
@ -356,7 +356,7 @@ class NotePropertiesDialog extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
const formNote = this.state.formNote;
|
||||
|
||||
const noteComps = [];
|
||||
@ -374,7 +374,7 @@ class NotePropertiesDialog extends React.Component {
|
||||
<div style={theme.dialogBox}>
|
||||
<div style={theme.dialogTitle}>{_('Note properties')}</div>
|
||||
<div>{noteComps}</div>
|
||||
<DialogButtonRow theme={this.props.theme} okButtonRef={this.okButton} onClick={this.buttonRow_click}/>
|
||||
<DialogButtonRow themeId={this.props.themeId} okButtonRef={this.okButton} onClick={this.buttonRow_click}/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -38,7 +38,7 @@ class NoteRevisionViewerComponent extends React.PureComponent {
|
||||
}
|
||||
|
||||
style() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
const style = {
|
||||
root: {
|
||||
@ -114,7 +114,7 @@ class NoteRevisionViewerComponent extends React.PureComponent {
|
||||
this.setState({ note: note });
|
||||
}
|
||||
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
const markupToHtml = markupLanguageUtils.newMarkupToHtml({
|
||||
resourceBaseUrl: `file://${Setting.value('resourceDir')}/`,
|
||||
@ -164,7 +164,7 @@ class NoteRevisionViewerComponent extends React.PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
const style = this.style();
|
||||
|
||||
const revisionListItems = [];
|
||||
@ -213,7 +213,7 @@ class NoteRevisionViewerComponent extends React.PureComponent {
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
theme: state.settings.theme,
|
||||
themeId: state.settings.theme,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -17,7 +17,7 @@ class NoteSearchBarComponent extends React.Component {
|
||||
}
|
||||
|
||||
style() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
const style = {
|
||||
root: Object.assign({}, theme.textStyle, {
|
||||
@ -34,7 +34,7 @@ class NoteSearchBarComponent extends React.Component {
|
||||
}
|
||||
|
||||
buttonIconComponent(iconName, clickHandler, isEnabled) {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
const searchButton = {
|
||||
paddingLeft: 4,
|
||||
@ -119,7 +119,7 @@ class NoteSearchBarComponent extends React.Component {
|
||||
// backgroundColor needs to cached to a local variable to prevent the
|
||||
// colour from blinking.
|
||||
// For more info: https://github.com/laurent22/joplin/pull/2329#issuecomment-578376835
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
if (!this.props.searching) {
|
||||
if (this.props.resultCount === 0 && query.length > 0) {
|
||||
this.backgroundColor = theme.warningBackgroundColor;
|
||||
@ -181,7 +181,7 @@ class NoteSearchBarComponent extends React.Component {
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
theme: state.settings.theme,
|
||||
themeId: state.settings.theme,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -5,7 +5,7 @@ const { themeStyle } = require('lib/theme');
|
||||
|
||||
class NoteStatusBarComponent extends React.Component {
|
||||
style() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
const style = {
|
||||
root: Object.assign({}, theme.textStyle, {
|
||||
@ -28,7 +28,7 @@ const mapStateToProps = state => {
|
||||
// notes: state.notes,
|
||||
// folders: state.folders,
|
||||
// selectedNoteIds: state.selectedNoteIds,
|
||||
theme: state.settings.theme,
|
||||
themeId: state.settings.theme,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -169,7 +169,7 @@ class NoteTextViewerComponent extends React.Component {
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
theme: state.settings.theme,
|
||||
themeId: state.settings.theme,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -1,19 +1,19 @@
|
||||
import * as React from 'react';
|
||||
import { useEffect, useCallback, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import CommandService from '../../lib/services/CommandService';
|
||||
const { connect } = require('react-redux');
|
||||
const { buildStyle } = require('lib/theme');
|
||||
const Toolbar = require('../Toolbar.min.js');
|
||||
const Folder = require('lib/models/Folder');
|
||||
const { _ } = require('lib/locale');
|
||||
const { substrWithEllipsis } = require('lib/string-utils');
|
||||
// const Folder = require('lib/models/Folder');
|
||||
// const { _ } = require('lib/locale');
|
||||
// const { substrWithEllipsis } = require('lib/string-utils');
|
||||
|
||||
interface ButtonClickEvent {
|
||||
name: string,
|
||||
}
|
||||
|
||||
interface NoteToolbarProps {
|
||||
theme: number,
|
||||
themeId: number,
|
||||
style: any,
|
||||
folders: any[],
|
||||
watchedNoteFiles: string[],
|
||||
@ -26,11 +26,12 @@ interface NoteToolbarProps {
|
||||
}
|
||||
|
||||
function styles_(props:NoteToolbarProps) {
|
||||
return buildStyle('NoteToolbar', props.theme, (/* theme:any*/) => {
|
||||
return buildStyle('NoteToolbar', props.themeId, (theme:any) => {
|
||||
return {
|
||||
root: {
|
||||
...props.style,
|
||||
borderBottom: 'none',
|
||||
backgroundColor: theme.backgroundColor,
|
||||
},
|
||||
};
|
||||
});
|
||||
@ -39,52 +40,27 @@ function styles_(props:NoteToolbarProps) {
|
||||
function NoteToolbar(props:NoteToolbarProps) {
|
||||
const styles = styles_(props);
|
||||
const [toolbarItems, setToolbarItems] = useState([]);
|
||||
const selectedNoteFolder = Folder.byId(props.folders, props.note.parent_id);
|
||||
const folderId = selectedNoteFolder ? selectedNoteFolder.id : '';
|
||||
const folderTitle = selectedNoteFolder && selectedNoteFolder.title ? selectedNoteFolder.title : '';
|
||||
// const selectedNoteFolder = Folder.byId(props.folders, props.note.parent_id);
|
||||
// const folderId = selectedNoteFolder ? selectedNoteFolder.id : '';
|
||||
// const folderTitle = selectedNoteFolder && selectedNoteFolder.title ? selectedNoteFolder.title : '';
|
||||
|
||||
const cmdService = CommandService.instance();
|
||||
|
||||
const updateToolbarItems = useCallback(() => {
|
||||
function updateToolbarItems() {
|
||||
const output = [];
|
||||
|
||||
output.push(
|
||||
cmdService.commandToToolbarButton('historyBackward')
|
||||
);
|
||||
|
||||
output.push(
|
||||
cmdService.commandToToolbarButton('historyForward')
|
||||
);
|
||||
|
||||
if (folderId && ['Search', 'Tag', 'SmartFilter'].includes(props.notesParentType)) {
|
||||
output.push({
|
||||
title: _('In: %s', substrWithEllipsis(folderTitle, 0, 16)),
|
||||
tooltip: folderTitle,
|
||||
iconName: 'fa-book',
|
||||
onClick: () => {
|
||||
props.dispatch({
|
||||
type: 'FOLDER_AND_NOTE_SELECT',
|
||||
folderId: folderId,
|
||||
noteId: props.note.id,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
output.push(cmdService.commandToToolbarButton('showNoteProperties'));
|
||||
|
||||
if (props.watchedNoteFiles.indexOf(props.note.id) >= 0) {
|
||||
output.push(cmdService.commandToToolbarButton('stopExternalEditing'));
|
||||
} else {
|
||||
output.push(cmdService.commandToToolbarButton('startExternalEditing'));
|
||||
}
|
||||
// if (props.watchedNoteFiles.indexOf(props.note.id) >= 0) {
|
||||
// output.push(cmdService.commandToToolbarButton('stopExternalEditing'));
|
||||
// } else {
|
||||
// output.push(cmdService.commandToToolbarButton('startExternalEditing'));
|
||||
// }
|
||||
|
||||
output.push(cmdService.commandToToolbarButton('editAlarm'));
|
||||
|
||||
output.push(cmdService.commandToToolbarButton('setTags'));
|
||||
output.push(cmdService.commandToToolbarButton('toggleVisiblePanes'));
|
||||
output.push(cmdService.commandToToolbarButton('showNoteProperties'));
|
||||
|
||||
setToolbarItems(output);
|
||||
}, [props.note.id, folderId, folderTitle, props.watchedNoteFiles, props.notesParentType]);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
updateToolbarItems();
|
||||
@ -92,7 +68,7 @@ function NoteToolbar(props:NoteToolbarProps) {
|
||||
return () => {
|
||||
cmdService.off('commandsEnabledStateChange', updateToolbarItems);
|
||||
};
|
||||
}, [updateToolbarItems]);
|
||||
}, []);
|
||||
|
||||
return <Toolbar style={styles.root} items={toolbarItems} />;
|
||||
}
|
||||
|
@ -1,16 +1,21 @@
|
||||
const React = require('react');
|
||||
import * as React from 'react';
|
||||
import ButtonBar from './ConfigScreen/ButtonBar';
|
||||
|
||||
const { connect } = require('react-redux');
|
||||
const { reg } = require('lib/registry.js');
|
||||
const Setting = require('lib/models/Setting');
|
||||
const { bridge } = require('electron').remote.require('./bridge');
|
||||
const { Header } = require('./Header/Header.min.js');
|
||||
const { themeStyle } = require('lib/theme');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { OneDriveApiNodeUtils } = require('lib/onedrive-api-node-utils.js');
|
||||
|
||||
class OneDriveLoginScreenComponent extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
interface Props {
|
||||
themeId: string,
|
||||
}
|
||||
|
||||
class OneDriveLoginScreenComponent extends React.Component<any, any> {
|
||||
constructor(props:Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
authLog: [],
|
||||
@ -18,8 +23,8 @@ class OneDriveLoginScreenComponent extends React.Component {
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
const log = (s) => {
|
||||
this.setState(state => {
|
||||
const log = (s:any) => {
|
||||
this.setState((state:any) => {
|
||||
const authLog = state.authLog.slice();
|
||||
authLog.push({ key: (Date.now() + Math.random()).toString(), text: s });
|
||||
return { authLog: authLog };
|
||||
@ -30,7 +35,7 @@ class OneDriveLoginScreenComponent extends React.Component {
|
||||
const syncTarget = reg.syncTarget(syncTargetId);
|
||||
const oneDriveApiUtils = new OneDriveApiNodeUtils(syncTarget.api());
|
||||
const auth = await oneDriveApiUtils.oauthDance({
|
||||
log: (s) => log(s),
|
||||
log: (s:any) => log(s),
|
||||
});
|
||||
|
||||
Setting.setValue(`sync.${syncTargetId}.auth`, auth ? JSON.stringify(auth) : null);
|
||||
@ -52,9 +57,7 @@ class OneDriveLoginScreenComponent extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const style = this.props.style;
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const headerStyle = Object.assign({}, theme.headerStyle, { width: style.width });
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
const logComps = [];
|
||||
for (const l of this.state.authLog) {
|
||||
@ -66,22 +69,23 @@ class OneDriveLoginScreenComponent extends React.Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Header style={headerStyle}/>
|
||||
<div style={{ padding: 10 }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||
<div style={{ padding: theme.configScreenPadding, flex: 1 }}>
|
||||
{logComps}
|
||||
</div>
|
||||
<ButtonBar
|
||||
onCancelClick={() => this.props.dispatch({ type: 'NAV_BACK' })}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const mapStateToProps = (state:any) => {
|
||||
return {
|
||||
theme: state.settings.theme,
|
||||
themeId: state.settings.theme,
|
||||
};
|
||||
};
|
||||
|
||||
const OneDriveLoginScreen = connect(mapStateToProps)(OneDriveLoginScreenComponent);
|
||||
export default connect(mapStateToProps)(OneDriveLoginScreenComponent);
|
||||
|
||||
module.exports = { OneDriveLoginScreen };
|
@ -164,10 +164,10 @@ class PromptDialog extends React.Component {
|
||||
|
||||
render() {
|
||||
const style = this.props.style;
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
const buttonTypes = this.props.buttons ? this.props.buttons : ['ok', 'cancel'];
|
||||
|
||||
const styles = this.styles(this.props.theme, style.width, style.height, this.state.visible);
|
||||
const styles = this.styles(this.props.themeId, style.width, style.height, this.state.visible);
|
||||
|
||||
const onClose = (accept, buttonType) => {
|
||||
if (this.props.onClose) {
|
||||
|
173
ElectronClient/gui/ResizableLayout/ResizableLayout.tsx
Normal file
173
ElectronClient/gui/ResizableLayout/ResizableLayout.tsx
Normal file
@ -0,0 +1,173 @@
|
||||
import * as React from 'react';
|
||||
import { useRef, useState } from 'react';
|
||||
import produce from 'immer';
|
||||
import useWindowResizeEvent from './hooks/useWindowResizeEvent';
|
||||
import useLayoutItemSizes, { LayoutItemSizes, itemSize } from './hooks/useLayoutItemSizes';
|
||||
const { Resizable } = require('re-resizable');
|
||||
const EventEmitter = require('events');
|
||||
|
||||
export enum LayoutItemDirection {
|
||||
Row = 'row',
|
||||
Column = 'column',
|
||||
}
|
||||
|
||||
export interface Size {
|
||||
width: number,
|
||||
height: number,
|
||||
}
|
||||
|
||||
export interface LayoutItem {
|
||||
key: string,
|
||||
width?: number,
|
||||
height?: number,
|
||||
minWidth?: number,
|
||||
minHeight?: number,
|
||||
children?: LayoutItem[]
|
||||
direction?: LayoutItemDirection,
|
||||
resizable?: boolean,
|
||||
visible?: boolean,
|
||||
}
|
||||
|
||||
interface onResizeEvent {
|
||||
layout: LayoutItem
|
||||
}
|
||||
|
||||
interface Props {
|
||||
layout: LayoutItem,
|
||||
renderItem(key:string, event:any):JSX.Element;
|
||||
onResize(event:onResizeEvent):void;
|
||||
width?: number,
|
||||
height?: number,
|
||||
}
|
||||
|
||||
export function findItemByKey(layout:LayoutItem, key:string):LayoutItem {
|
||||
function recurseFind(item:LayoutItem):LayoutItem {
|
||||
if (item.key === key) return item;
|
||||
|
||||
if (item.children) {
|
||||
for (const child of item.children) {
|
||||
const found = recurseFind(child);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const output = recurseFind(layout);
|
||||
if (!output) throw new Error(`Invalid item key: ${key}`);
|
||||
return output;
|
||||
}
|
||||
|
||||
function updateLayoutItem(layout:LayoutItem, key:string, props:any) {
|
||||
return produce(layout, (draftState:LayoutItem) => {
|
||||
function recurseFind(item:LayoutItem) {
|
||||
if (item.key === key) {
|
||||
for (const n in props) {
|
||||
(item as any)[n] = props[n];
|
||||
}
|
||||
} else {
|
||||
if (item.children) {
|
||||
for (const child of item.children) {
|
||||
recurseFind(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
recurseFind(draftState);
|
||||
});
|
||||
}
|
||||
|
||||
function renderContainer(item:LayoutItem, sizes:LayoutItemSizes, onResizeStart:Function, onResize:Function, onResizeStop:Function, children:JSX.Element[]):JSX.Element {
|
||||
const style:any = {
|
||||
display: item.visible !== false ? 'flex' : 'none',
|
||||
flexDirection: item.direction,
|
||||
};
|
||||
|
||||
const size:Size = itemSize(item, sizes);
|
||||
|
||||
const className = `resizableLayoutItem rli-${item.key}`;
|
||||
if (item.resizable) {
|
||||
const enable = { top: false, right: true, bottom: false, left: false, topRight: false, bottomRight: false, bottomLeft: false, topLeft: false };
|
||||
|
||||
return (
|
||||
<Resizable
|
||||
key={item.key}
|
||||
className={className}
|
||||
style={style}
|
||||
size={size}
|
||||
onResizeStart={onResizeStart}
|
||||
onResize={onResize}
|
||||
onResizeStop={onResizeStop}
|
||||
enable={enable}
|
||||
minWidth={item.minWidth}
|
||||
minHeight={item.minHeight}
|
||||
>
|
||||
{children}
|
||||
</Resizable>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div key={item.key} className={className} style={{ ...style, ...size }}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function ResizableLayout(props:Props) {
|
||||
const eventEmitter = useRef(new EventEmitter());
|
||||
|
||||
const [resizedItem, setResizedItem] = useState<any>(null);
|
||||
|
||||
function renderLayoutItem(item:LayoutItem, sizes:LayoutItemSizes, isVisible:boolean):JSX.Element {
|
||||
|
||||
function onResizeStart() {
|
||||
setResizedItem({
|
||||
key: item.key,
|
||||
initialWidth: sizes[item.key].width,
|
||||
initialHeight: sizes[item.key].height,
|
||||
});
|
||||
}
|
||||
|
||||
function onResize(_event:any, _direction:any, _refToElement: HTMLDivElement, delta:any) {
|
||||
const newLayout = updateLayoutItem(props.layout, item.key, {
|
||||
width: resizedItem.initialWidth + delta.width,
|
||||
height: resizedItem.initialHeight + delta.height,
|
||||
});
|
||||
|
||||
props.onResize({ layout: newLayout });
|
||||
eventEmitter.current.emit('resize');
|
||||
}
|
||||
|
||||
function onResizeStop(_event:any, _direction:any, _refToElement: HTMLDivElement, delta:any) {
|
||||
onResize(_event, _direction, _refToElement, delta);
|
||||
setResizedItem(null);
|
||||
}
|
||||
|
||||
if (!item.children) {
|
||||
const comp = props.renderItem(item.key, {
|
||||
item: item,
|
||||
eventEmitter: eventEmitter.current,
|
||||
size: sizes[item.key],
|
||||
visible: isVisible,
|
||||
});
|
||||
|
||||
return renderContainer(item, sizes, onResizeStart, onResize, onResizeStop, [comp]);
|
||||
} else {
|
||||
const childrenComponents = [];
|
||||
for (const child of item.children) {
|
||||
childrenComponents.push(renderLayoutItem(child, sizes, isVisible && child.visible !== false));
|
||||
}
|
||||
|
||||
return renderContainer(item, sizes, onResizeStart, onResize, onResizeStop, childrenComponents);
|
||||
}
|
||||
}
|
||||
|
||||
useWindowResizeEvent(eventEmitter);
|
||||
const sizes = useLayoutItemSizes(props.layout);
|
||||
|
||||
return renderLayoutItem(props.layout, sizes, props.layout.visible !== false);
|
||||
}
|
||||
|
||||
export default ResizableLayout;
|
@ -0,0 +1,79 @@
|
||||
import { useMemo } from 'react';
|
||||
import { LayoutItem, Size } from '../ResizableLayout';
|
||||
|
||||
export interface LayoutItemSizes {
|
||||
[key:string]: Size,
|
||||
}
|
||||
|
||||
export function itemSize(item:LayoutItem, sizes:LayoutItemSizes):Size {
|
||||
return {
|
||||
width: 'width' in item ? item.width : sizes[item.key].width,
|
||||
height: 'height' in item ? item.height : sizes[item.key].height,
|
||||
};
|
||||
}
|
||||
|
||||
function calculateChildrenSizes(item:LayoutItem, sizes:LayoutItemSizes):LayoutItemSizes {
|
||||
if (!item.children) return sizes;
|
||||
|
||||
const parentSize = itemSize(item, sizes);
|
||||
|
||||
const remainingSize:Size = {
|
||||
width: parentSize.width,
|
||||
height: parentSize.height,
|
||||
};
|
||||
|
||||
const noWidthChildren:LayoutItem[] = [];
|
||||
const noHeightChildren:LayoutItem[] = [];
|
||||
|
||||
for (const child of item.children) {
|
||||
let w = 'width' in child ? child.width : null;
|
||||
let h = 'height' in child ? child.height : null;
|
||||
if (child.visible === false) {
|
||||
w = 0;
|
||||
h = 0;
|
||||
}
|
||||
sizes[child.key] = { width: w, height: h };
|
||||
if (w !== null) remainingSize.width -= w;
|
||||
if (h !== null) remainingSize.height -= h;
|
||||
if (w === null) noWidthChildren.push(child);
|
||||
if (h === null) noHeightChildren.push(child);
|
||||
}
|
||||
|
||||
if (noWidthChildren.length) {
|
||||
const w = item.direction === 'row' ? remainingSize.width / noWidthChildren.length : parentSize.width;
|
||||
for (const child of noWidthChildren) {
|
||||
sizes[child.key].width = w;
|
||||
}
|
||||
}
|
||||
|
||||
if (noHeightChildren.length) {
|
||||
const h = item.direction === 'column' ? remainingSize.height / noHeightChildren.length : parentSize.height;
|
||||
for (const child of noHeightChildren) {
|
||||
sizes[child.key].height = h;
|
||||
}
|
||||
}
|
||||
|
||||
for (const child of item.children) {
|
||||
const childrenSizes = calculateChildrenSizes(child, sizes);
|
||||
sizes = { ...sizes, ...childrenSizes };
|
||||
}
|
||||
|
||||
return sizes;
|
||||
}
|
||||
|
||||
export default function useLayoutItemSizes(layout:LayoutItem) {
|
||||
return useMemo(() => {
|
||||
let sizes:LayoutItemSizes = {};
|
||||
|
||||
if (!('width' in layout) || !('height' in layout)) throw new Error('width and height are required on layout root');
|
||||
|
||||
sizes[layout.key] = {
|
||||
width: layout.width,
|
||||
height: layout.height,
|
||||
};
|
||||
|
||||
sizes = calculateChildrenSizes(layout, sizes);
|
||||
|
||||
return sizes;
|
||||
}, [layout]);
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
import { useEffect } from 'react';
|
||||
const debounce = require('debounce');
|
||||
|
||||
export default function useWindowResizeEvent(eventEmitter:any) {
|
||||
useEffect(() => {
|
||||
const window_resize = debounce(() => {
|
||||
eventEmitter.current.emit('resize');
|
||||
}, 500);
|
||||
|
||||
window.addEventListener('resize', window_resize);
|
||||
|
||||
return () => {
|
||||
window_resize.clear();
|
||||
window.removeEventListener('resize', window_resize);
|
||||
};
|
||||
}, []);
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
import * as React from 'react';
|
||||
import ButtonBar from './ConfigScreen/ButtonBar';
|
||||
|
||||
const { connect } = require('react-redux');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { themeStyle } = require('lib/theme');
|
||||
const { bridge } = require('electron').remote.require('./bridge');
|
||||
const { Header } = require('./Header/Header.min.js');
|
||||
const prettyBytes = require('pretty-bytes');
|
||||
const Resource = require('lib/models/Resource.js');
|
||||
|
||||
@ -14,8 +14,9 @@ interface Style {
|
||||
}
|
||||
|
||||
interface Props {
|
||||
theme: any;
|
||||
style: Style
|
||||
themeId: number;
|
||||
style: Style,
|
||||
dispatch: Function,
|
||||
}
|
||||
|
||||
interface Resource {
|
||||
@ -37,7 +38,7 @@ interface ResourceTable {
|
||||
onResourceClick: (resource: Resource) => any
|
||||
onResourceDelete: (resource: Resource) => any
|
||||
onToggleSorting: (order: SortingOrder) => any
|
||||
theme: any
|
||||
themeId: number
|
||||
style: Style
|
||||
}
|
||||
|
||||
@ -50,17 +51,19 @@ interface ActiveSorting {
|
||||
}
|
||||
|
||||
const ResourceTable: React.FC<ResourceTable> = (props: ResourceTable) => {
|
||||
const theme = themeStyle(props.themeId);
|
||||
|
||||
const sortOrderEngagedMarker = (s: SortingOrder) => {
|
||||
return (
|
||||
<a href="#"
|
||||
style={{ color: props.theme.urlColor }}
|
||||
style={{ color: theme.urlColor }}
|
||||
onClick={() => props.onToggleSorting(s)}>{
|
||||
(props.sorting.order === s && props.sorting.type === 'desc') ? '▾' : '▴'}</a>
|
||||
);
|
||||
};
|
||||
|
||||
const titleCellStyle = {
|
||||
...props.theme.textStyle,
|
||||
...theme.textStyle,
|
||||
textOverflow: 'ellipsis',
|
||||
overflowX: 'hidden',
|
||||
maxWidth: 1,
|
||||
@ -69,14 +72,14 @@ const ResourceTable: React.FC<ResourceTable> = (props: ResourceTable) => {
|
||||
};
|
||||
|
||||
const cellStyle = {
|
||||
...props.theme.textStyle,
|
||||
...theme.textStyle,
|
||||
whiteSpace: 'nowrap',
|
||||
color: props.theme.colorFaded,
|
||||
color: theme.colorFaded,
|
||||
width: 1,
|
||||
};
|
||||
|
||||
const headerStyle = {
|
||||
...props.theme.textStyle,
|
||||
...theme.textStyle,
|
||||
whiteSpace: 'nowrap',
|
||||
width: 1,
|
||||
fontWeight: 'bold',
|
||||
@ -97,7 +100,7 @@ const ResourceTable: React.FC<ResourceTable> = (props: ResourceTable) => {
|
||||
<tr key={index}>
|
||||
<td style={titleCellStyle} className="titleCell">
|
||||
<a
|
||||
style={{ color: props.theme.urlColor }}
|
||||
style={{ color: theme.urlColor }}
|
||||
href="#"
|
||||
onClick={() => props.onResourceClick(resource)}>{resource.title || `(${_('Untitled')})`}
|
||||
</a>
|
||||
@ -105,7 +108,7 @@ const ResourceTable: React.FC<ResourceTable> = (props: ResourceTable) => {
|
||||
<td style={cellStyle} className="dataCell">{prettyBytes(resource.size)}</td>
|
||||
<td style={cellStyle} className="dataCell">{resource.id}</td>
|
||||
<td style={cellStyle} className="dataCell">
|
||||
<button style={props.theme.buttonStyle} onClick={() => props.onResourceDelete(resource)}>{_('Delete')}</button>
|
||||
<button style={theme.buttonStyle} onClick={() => props.onResourceDelete(resource)}>{_('Delete')}</button>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
@ -202,8 +205,7 @@ class ResourceScreenComponent extends React.Component<Props, State> {
|
||||
|
||||
render() {
|
||||
const style = this.props.style;
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const headerStyle = Object.assign({}, theme.headerStyle, { width: style.width });
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
const rootStyle:any = {
|
||||
...style,
|
||||
@ -211,13 +213,16 @@ class ResourceScreenComponent extends React.Component<Props, State> {
|
||||
color: theme.color,
|
||||
padding: 20,
|
||||
boxSizing: 'border-box',
|
||||
flex: 1,
|
||||
};
|
||||
rootStyle.height = style.height - 35; // Minus the header height
|
||||
// rootStyle.height = style.height - 35; // Minus the header height
|
||||
delete rootStyle.height;
|
||||
delete rootStyle.width;
|
||||
|
||||
const containerHeight = style.height;
|
||||
|
||||
return (
|
||||
<div style={{ ...theme.containerStyle, fontFamily: theme.fontFamily }}>
|
||||
<Header style={headerStyle} />
|
||||
<div style={{ ...theme.containerStyle, fontFamily: theme.fontFamily, height: containerHeight, display: 'flex', flexDirection: 'column' }}>
|
||||
<div style={rootStyle}>
|
||||
<div style={{ ...theme.notificationBox, marginBottom: 10 }}>{
|
||||
_('This is an advanced tool to show the attachments that are linked to your notes. Please be careful when deleting one of them as they cannot be restored afterwards.')
|
||||
@ -232,7 +237,7 @@ class ResourceScreenComponent extends React.Component<Props, State> {
|
||||
<div>{_('Warning: not all resources shown for performance reasons (limit: %s).', MAX_RESOURCES)}</div>
|
||||
}
|
||||
{this.state.resources && <ResourceTable
|
||||
theme={theme}
|
||||
themeId={this.props.themeId}
|
||||
style={style}
|
||||
resources={this.state.resources}
|
||||
sorting={this.state.sorting}
|
||||
@ -243,13 +248,16 @@ class ResourceScreenComponent extends React.Component<Props, State> {
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<ButtonBar
|
||||
onCancelClick={() => this.props.dispatch({ type: 'NAV_BACK' })}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: any) => ({
|
||||
theme: state.settings.theme,
|
||||
themeId: state.settings.theme,
|
||||
});
|
||||
|
||||
const ResourceScreen = connect(mapStateToProps)(ResourceScreenComponent);
|
||||
|
@ -5,20 +5,30 @@ const { connect, Provider } = require('react-redux');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
|
||||
const { MainScreen } = require('.//MainScreen/MainScreen.min.js');
|
||||
const MainScreen = require('./MainScreen/MainScreen').default;
|
||||
const ConfigScreen = require('./ConfigScreen/ConfigScreen').default;
|
||||
const StatusScreen = require('./StatusScreen/StatusScreen').default;
|
||||
const OneDriveLoginScreen = require('./OneDriveLoginScreen').default;
|
||||
const DropboxLoginScreen = require('./DropboxLoginScreen').default;
|
||||
const ErrorBoundary = require('./ErrorBoundary').default;
|
||||
const { OneDriveLoginScreen } = require('./OneDriveLoginScreen.min.js');
|
||||
const { DropboxLoginScreen } = require('./DropboxLoginScreen.min.js');
|
||||
const { StatusScreen } = require('./StatusScreen.min.js');
|
||||
const { ImportScreen } = require('./ImportScreen.min.js');
|
||||
const { ConfigScreen } = require('./ConfigScreen.min.js');
|
||||
const { ResourceScreen } = require('./ResourceScreen.js');
|
||||
const { Navigator } = require('./Navigator.min.js');
|
||||
const WelcomeUtils = require('lib/WelcomeUtils');
|
||||
const { app } = require('../app');
|
||||
const { ThemeProvider, StyleSheetManager, createGlobalStyle } = require('styled-components');
|
||||
const { themeStyle } = require('lib/theme');
|
||||
|
||||
const { bridge } = require('electron').remote.require('./bridge');
|
||||
|
||||
const GlobalStyle = createGlobalStyle`
|
||||
div, span, a {
|
||||
color: ${(props) => props.theme.color};
|
||||
font-size: ${(props) => props.theme.fontSize}px;
|
||||
font-family: ${(props) => props.theme.fontFamily};
|
||||
}
|
||||
`;
|
||||
|
||||
async function initialize() {
|
||||
this.wcsTimeoutId_ = null;
|
||||
|
||||
@ -84,6 +94,8 @@ class RootComponent extends React.Component {
|
||||
height: this.props.size.height / this.props.zoomFactor,
|
||||
};
|
||||
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
const screens = {
|
||||
Main: { screen: MainScreen },
|
||||
OneDriveLogin: { screen: OneDriveLoginScreen, title: () => _('OneDrive Login') },
|
||||
@ -94,7 +106,14 @@ class RootComponent extends React.Component {
|
||||
Status: { screen: StatusScreen, title: () => _('Synchronisation Status') },
|
||||
};
|
||||
|
||||
return <Navigator style={navigatorStyle} screens={screens} />;
|
||||
return (
|
||||
<StyleSheetManager disableVendorPrefixes>
|
||||
<ThemeProvider theme={theme}>
|
||||
<GlobalStyle/>
|
||||
<Navigator style={navigatorStyle} screens={screens} />
|
||||
</ThemeProvider>
|
||||
</StyleSheetManager>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,6 +122,7 @@ const mapStateToProps = state => {
|
||||
size: state.windowContentSize,
|
||||
zoomFactor: state.settings.windowContentZoomFactor / 100,
|
||||
appState: state.appState,
|
||||
themeId: state.settings.theme,
|
||||
};
|
||||
};
|
||||
|
||||
|
51
ElectronClient/gui/SearchBar/SearchBar.tsx
Normal file
51
ElectronClient/gui/SearchBar/SearchBar.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
import * as React from 'react';
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
import CommandService from 'lib/services/CommandService';
|
||||
import useSearch from './hooks/useSearch';
|
||||
import { Root, SearchInput, SearchButton, SearchButtonIcon } from './styles';
|
||||
const { connect } = require('react-redux');
|
||||
|
||||
const { _ } = require('lib/locale.js');
|
||||
|
||||
interface Props {
|
||||
inputRef?: any,
|
||||
notesParentType: string,
|
||||
}
|
||||
|
||||
function SearchBar(props:Props) {
|
||||
const [query, setQuery] = useState('');
|
||||
const iconName = !query ? CommandService.instance().iconName('search') : 'fa fa-times';
|
||||
|
||||
const onChange = (event:any) => {
|
||||
setQuery(event.currentTarget.value);
|
||||
};
|
||||
|
||||
const onSearchButtonClick = useCallback(() => {
|
||||
setQuery('');
|
||||
}, []);
|
||||
|
||||
useSearch(query);
|
||||
|
||||
useEffect(() => {
|
||||
if (props.notesParentType !== 'Search') {
|
||||
setQuery('');
|
||||
}
|
||||
}, [props.notesParentType]);
|
||||
|
||||
return (
|
||||
<Root>
|
||||
<SearchInput ref={props.inputRef} value={query} type="text" placeholder={_('Search...')} onChange={onChange}/>
|
||||
<SearchButton onClick={onSearchButtonClick}>
|
||||
<SearchButtonIcon className={iconName}/>
|
||||
</SearchButton>
|
||||
</Root>
|
||||
);
|
||||
}
|
||||
|
||||
const mapStateToProps = (state:any) => {
|
||||
return {
|
||||
notesParentType: state.notesParentType,
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(SearchBar);
|
17
ElectronClient/gui/SearchBar/hooks/useSearch.ts
Normal file
17
ElectronClient/gui/SearchBar/hooks/useSearch.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { useEffect } from 'react';
|
||||
import CommandService from 'lib/services/CommandService';
|
||||
const debounce = require('debounce');
|
||||
|
||||
export default function useSearch(query:string) {
|
||||
useEffect(() => {
|
||||
const search = debounce((query:string) => {
|
||||
CommandService.instance().execute('search', { query });
|
||||
}, 500);
|
||||
|
||||
search(query);
|
||||
|
||||
return () => {
|
||||
search.clear();
|
||||
};
|
||||
}, [query]);
|
||||
}
|
28
ElectronClient/gui/SearchBar/styles/index.ts
Normal file
28
ElectronClient/gui/SearchBar/styles/index.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import StyledInput from '../../style/StyledInput';
|
||||
const styled = require('styled-components').default;
|
||||
|
||||
export const Root = styled.div`
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const SearchButton = styled.button`
|
||||
position: absolute;
|
||||
right: 0;
|
||||
background: none;
|
||||
border: none;
|
||||
height: 100%;
|
||||
opacity: ${(props:any) => props.disabled ? 0.5 : 1};
|
||||
`;
|
||||
|
||||
export const SearchButtonIcon = styled.span`
|
||||
font-size: ${(props:any) => props.theme.toolbarIconSize}px;
|
||||
color: ${(props:any) => props.theme.color4};
|
||||
`;
|
||||
|
||||
export const SearchInput = styled(StyledInput)`
|
||||
padding-right: 20px;
|
||||
flex: 1;
|
||||
width: 10px;
|
||||
`;
|
@ -12,7 +12,7 @@ const { reg } = require('lib/registry.js');
|
||||
const { clipboard } = require('electron');
|
||||
|
||||
interface ShareNoteDialogProps {
|
||||
theme: number,
|
||||
themeId: number,
|
||||
noteIds: Array<string>,
|
||||
onClose: Function,
|
||||
}
|
||||
@ -22,7 +22,7 @@ interface SharesMap {
|
||||
}
|
||||
|
||||
function styles_(props:ShareNoteDialogProps) {
|
||||
return buildStyle('ShareNoteDialog', props.theme, (theme:any) => {
|
||||
return buildStyle('ShareNoteDialog', props.themeId, (theme:any) => {
|
||||
return {
|
||||
noteList: {
|
||||
marginBottom: 10,
|
||||
@ -67,7 +67,7 @@ export default function ShareNoteDialog(props:ShareNoteDialogProps) {
|
||||
const [shares, setShares] = useState<SharesMap>({});
|
||||
|
||||
const noteCount = notes.length;
|
||||
const theme = themeStyle(props.theme);
|
||||
const theme = themeStyle(props.themeId);
|
||||
const styles = styles_(props);
|
||||
|
||||
useEffect(() => {
|
||||
@ -206,7 +206,7 @@ export default function ShareNoteDialog(props:ShareNoteDialogProps) {
|
||||
<button disabled={['creating', 'synchronizing'].indexOf(sharesState) >= 0} style={styles.copyShareLinkButton} onClick={shareLinkButton_click}>{_n('Copy Shareable Link', 'Copy Shareable Links', noteCount)}</button>
|
||||
<div style={theme.textStyle}>{statusMessage(sharesState)}</div>
|
||||
{encryptionWarningMessage}
|
||||
<DialogButtonRow theme={props.theme} onClick={buttonRow_click} okButtonShow={false} cancelButtonLabel={_('Close')}/>
|
||||
<DialogButtonRow themeId={props.themeId} onClick={buttonRow_click} okButtonShow={false} cancelButtonLabel={_('Close')}/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,766 +0,0 @@
|
||||
const React = require('react');
|
||||
const { connect } = require('react-redux');
|
||||
const shared = require('lib/components/shared/side-menu-shared.js');
|
||||
const { Synchronizer } = require('lib/synchronizer.js');
|
||||
const CommandService = require('lib/services/CommandService.js').default;
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
const Tag = require('lib/models/Tag.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { themeStyle } = require('lib/theme');
|
||||
const { bridge } = require('electron').remote.require('./bridge');
|
||||
const Menu = bridge().Menu;
|
||||
const MenuItem = bridge().MenuItem;
|
||||
const InteropServiceHelper = require('../../InteropServiceHelper.js');
|
||||
const { substrWithEllipsis } = require('lib/string-utils');
|
||||
const { ALL_NOTES_FILTER_ID } = require('lib/reserved-ids');
|
||||
|
||||
const commands = [
|
||||
require('./commands/focusElementSideBar'),
|
||||
];
|
||||
|
||||
class SideBarComponent extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
CommandService.instance().componentRegisterCommands(this, commands);
|
||||
|
||||
this.onFolderDragStart_ = event => {
|
||||
const folderId = event.currentTarget.getAttribute('folderid');
|
||||
if (!folderId) return;
|
||||
|
||||
event.dataTransfer.setDragImage(new Image(), 1, 1);
|
||||
event.dataTransfer.clearData();
|
||||
event.dataTransfer.setData('text/x-jop-folder-ids', JSON.stringify([folderId]));
|
||||
};
|
||||
|
||||
this.onFolderDragOver_ = event => {
|
||||
if (event.dataTransfer.types.indexOf('text/x-jop-note-ids') >= 0) event.preventDefault();
|
||||
if (event.dataTransfer.types.indexOf('text/x-jop-folder-ids') >= 0) event.preventDefault();
|
||||
};
|
||||
|
||||
this.onFolderDrop_ = async event => {
|
||||
const folderId = event.currentTarget.getAttribute('folderid');
|
||||
const dt = event.dataTransfer;
|
||||
if (!dt) return;
|
||||
|
||||
// folderId can be NULL when dropping on the sidebar Notebook header. In that case, it's used
|
||||
// to put the dropped folder at the root. But for notes, folderId needs to always be defined
|
||||
// since there's no such thing as a root note.
|
||||
|
||||
if (dt.types.indexOf('text/x-jop-note-ids') >= 0) {
|
||||
event.preventDefault();
|
||||
|
||||
if (!folderId) return;
|
||||
|
||||
const noteIds = JSON.parse(dt.getData('text/x-jop-note-ids'));
|
||||
for (let i = 0; i < noteIds.length; i++) {
|
||||
await Note.moveToFolder(noteIds[i], folderId);
|
||||
}
|
||||
} else if (dt.types.indexOf('text/x-jop-folder-ids') >= 0) {
|
||||
event.preventDefault();
|
||||
|
||||
const folderIds = JSON.parse(dt.getData('text/x-jop-folder-ids'));
|
||||
for (let i = 0; i < folderIds.length; i++) {
|
||||
await Folder.moveToFolder(folderIds[i], folderId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.onTagDrop_ = async event => {
|
||||
const tagId = event.currentTarget.getAttribute('tagid');
|
||||
const dt = event.dataTransfer;
|
||||
if (!dt) return;
|
||||
|
||||
if (dt.types.indexOf('text/x-jop-note-ids') >= 0) {
|
||||
event.preventDefault();
|
||||
|
||||
const noteIds = JSON.parse(dt.getData('text/x-jop-note-ids'));
|
||||
for (let i = 0; i < noteIds.length; i++) {
|
||||
await Tag.addNote(tagId, noteIds[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.onFolderToggleClick_ = async event => {
|
||||
const folderId = event.currentTarget.getAttribute('folderid');
|
||||
|
||||
this.props.dispatch({
|
||||
type: 'FOLDER_TOGGLE',
|
||||
id: folderId,
|
||||
});
|
||||
};
|
||||
|
||||
this.folderItemsOrder_ = [];
|
||||
this.tagItemsOrder_ = [];
|
||||
|
||||
this.onKeyDown = this.onKeyDown.bind(this);
|
||||
this.onAllNotesClick_ = this.onAllNotesClick_.bind(this);
|
||||
|
||||
this.rootRef = React.createRef();
|
||||
|
||||
this.anchorItemRefs = {};
|
||||
|
||||
this.state = {
|
||||
tagHeaderIsExpanded: Setting.value('tagHeaderIsExpanded'),
|
||||
folderHeaderIsExpanded: Setting.value('folderHeaderIsExpanded'),
|
||||
};
|
||||
}
|
||||
|
||||
style() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
|
||||
const itemHeight = 25;
|
||||
|
||||
const style = {
|
||||
root: {
|
||||
backgroundColor: theme.backgroundColor2,
|
||||
},
|
||||
listItemContainer: {
|
||||
boxSizing: 'border-box',
|
||||
height: itemHeight,
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
},
|
||||
listItem: {
|
||||
fontFamily: theme.fontFamily,
|
||||
fontSize: theme.fontSize,
|
||||
textDecoration: 'none',
|
||||
color: theme.color2,
|
||||
cursor: 'default',
|
||||
opacity: 0.8,
|
||||
whiteSpace: 'nowrap',
|
||||
display: 'flex',
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
userSelect: 'none',
|
||||
},
|
||||
listItemSelected: {
|
||||
backgroundColor: theme.selectedColor2,
|
||||
},
|
||||
listItemExpandIcon: {
|
||||
color: theme.color2,
|
||||
cursor: 'default',
|
||||
opacity: 0.8,
|
||||
fontSize: theme.fontSize,
|
||||
textDecoration: 'none',
|
||||
paddingRight: 5,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
width: 12,
|
||||
},
|
||||
conflictFolder: {
|
||||
color: theme.colorError2,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
header: {
|
||||
height: itemHeight * 1.8,
|
||||
fontFamily: theme.fontFamily,
|
||||
fontSize: theme.fontSize * 1.16,
|
||||
textDecoration: 'none',
|
||||
boxSizing: 'border-box',
|
||||
color: theme.color2,
|
||||
paddingLeft: 8,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
userSelect: 'none',
|
||||
},
|
||||
button: {
|
||||
padding: 6,
|
||||
fontFamily: theme.fontFamily,
|
||||
fontSize: theme.fontSize,
|
||||
textDecoration: 'none',
|
||||
boxSizing: 'border-box',
|
||||
color: theme.color2,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
border: '1px solid rgba(255,255,255,0.2)',
|
||||
marginTop: 10,
|
||||
marginLeft: 5,
|
||||
marginRight: 5,
|
||||
cursor: 'default',
|
||||
userSelect: 'none',
|
||||
},
|
||||
syncReport: {
|
||||
fontFamily: theme.fontFamily,
|
||||
fontSize: Math.round(theme.fontSize * 0.9),
|
||||
color: theme.color2,
|
||||
opacity: 0.5,
|
||||
display: 'flex',
|
||||
alignItems: 'left',
|
||||
justifyContent: 'top',
|
||||
flexDirection: 'column',
|
||||
marginTop: 10,
|
||||
marginLeft: 5,
|
||||
marginRight: 5,
|
||||
marginBottom: 10,
|
||||
wordWrap: 'break-word',
|
||||
},
|
||||
noteCount: {
|
||||
paddingLeft: 5,
|
||||
opacity: 0.5,
|
||||
userSelect: 'none',
|
||||
},
|
||||
};
|
||||
|
||||
style.tagItem = Object.assign({}, style.listItem);
|
||||
style.tagItem.paddingLeft = 23;
|
||||
style.tagItem.height = itemHeight;
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
clearForceUpdateDuringSync() {
|
||||
if (this.forceUpdateDuringSyncIID_) {
|
||||
clearInterval(this.forceUpdateDuringSyncIID_);
|
||||
this.forceUpdateDuringSyncIID_ = null;
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.clearForceUpdateDuringSync();
|
||||
|
||||
CommandService.instance().componentUnregisterCommands(commands);
|
||||
}
|
||||
|
||||
async itemContextMenu(event) {
|
||||
const itemId = event.currentTarget.getAttribute('data-id');
|
||||
if (itemId === Folder.conflictFolderId()) return;
|
||||
|
||||
const itemType = Number(event.currentTarget.getAttribute('data-type'));
|
||||
if (!itemId || !itemType) throw new Error('No data on element');
|
||||
|
||||
let deleteMessage = '';
|
||||
let buttonLabel = _('Remove');
|
||||
if (itemType === BaseModel.TYPE_FOLDER) {
|
||||
const folder = await Folder.load(itemId);
|
||||
deleteMessage = _('Delete notebook "%s"?\n\nAll notes and sub-notebooks within this notebook will also be deleted.', substrWithEllipsis(folder.title, 0, 32));
|
||||
buttonLabel = _('Delete');
|
||||
} else if (itemType === BaseModel.TYPE_TAG) {
|
||||
const tag = await Tag.load(itemId);
|
||||
deleteMessage = _('Remove tag "%s" from all notes?', substrWithEllipsis(tag.title, 0, 32));
|
||||
} else if (itemType === BaseModel.TYPE_SEARCH) {
|
||||
deleteMessage = _('Remove this search from the sidebar?');
|
||||
}
|
||||
|
||||
const menu = new Menu();
|
||||
|
||||
let item = null;
|
||||
if (itemType === BaseModel.TYPE_FOLDER) {
|
||||
item = BaseModel.byId(this.props.folders, itemId);
|
||||
}
|
||||
|
||||
if (itemType === BaseModel.TYPE_FOLDER && !item.encryption_applied) {
|
||||
menu.append(
|
||||
new MenuItem(CommandService.instance().commandToMenuItem('newNotebook', { parentId: itemId }))
|
||||
);
|
||||
}
|
||||
|
||||
menu.append(
|
||||
new MenuItem({
|
||||
label: buttonLabel,
|
||||
click: async () => {
|
||||
const ok = bridge().showConfirmMessageBox(deleteMessage, {
|
||||
buttons: [buttonLabel, _('Cancel')],
|
||||
defaultId: 1,
|
||||
});
|
||||
if (!ok) return;
|
||||
|
||||
if (itemType === BaseModel.TYPE_FOLDER) {
|
||||
await Folder.delete(itemId);
|
||||
} else if (itemType === BaseModel.TYPE_TAG) {
|
||||
await Tag.untagAll(itemId);
|
||||
} else if (itemType === BaseModel.TYPE_SEARCH) {
|
||||
this.props.dispatch({
|
||||
type: 'SEARCH_DELETE',
|
||||
id: itemId,
|
||||
});
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
if (itemType === BaseModel.TYPE_FOLDER && !item.encryption_applied) {
|
||||
menu.append(new MenuItem(CommandService.instance().commandToMenuItem('renameFolder', { folderId: itemId })));
|
||||
|
||||
menu.append(new MenuItem({ type: 'separator' }));
|
||||
|
||||
const InteropService = require('lib/services/InteropService.js');
|
||||
|
||||
const exportMenu = new Menu();
|
||||
const ioService = new InteropService();
|
||||
const ioModules = ioService.modules();
|
||||
for (let i = 0; i < ioModules.length; i++) {
|
||||
const module = ioModules[i];
|
||||
if (module.type !== 'exporter') continue;
|
||||
|
||||
exportMenu.append(
|
||||
new MenuItem({
|
||||
label: module.fullLabel(),
|
||||
click: async () => {
|
||||
await InteropServiceHelper.export(this.props.dispatch.bind(this), module, { sourceFolderIds: [itemId] });
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
menu.append(
|
||||
new MenuItem({
|
||||
label: _('Export'),
|
||||
submenu: exportMenu,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (itemType === BaseModel.TYPE_TAG) {
|
||||
menu.append(new MenuItem(
|
||||
CommandService.instance().commandToMenuItem('renameTag', { tagId: itemId })
|
||||
));
|
||||
}
|
||||
|
||||
menu.popup(bridge().window());
|
||||
}
|
||||
|
||||
folderItem_click(folder) {
|
||||
this.props.dispatch({
|
||||
type: 'FOLDER_SELECT',
|
||||
id: folder ? folder.id : null,
|
||||
});
|
||||
}
|
||||
|
||||
tagItem_click(tag) {
|
||||
this.props.dispatch({
|
||||
type: 'TAG_SELECT',
|
||||
id: tag ? tag.id : null,
|
||||
});
|
||||
}
|
||||
|
||||
// async sync_click() {
|
||||
// await shared.synchronize_press(this);
|
||||
// }
|
||||
|
||||
anchorItemRef(type, id) {
|
||||
if (!this.anchorItemRefs[type]) this.anchorItemRefs[type] = {};
|
||||
if (this.anchorItemRefs[type][id]) return this.anchorItemRefs[type][id];
|
||||
this.anchorItemRefs[type][id] = React.createRef();
|
||||
return this.anchorItemRefs[type][id];
|
||||
}
|
||||
|
||||
firstAnchorItemRef(type) {
|
||||
const refs = this.anchorItemRefs[type];
|
||||
if (!refs) return null;
|
||||
|
||||
const n = `${type}s`;
|
||||
const item = this.props[n] && this.props[n].length ? this.props[n][0] : null;
|
||||
console.info('props', this.props[n], item);
|
||||
if (!item) return null;
|
||||
|
||||
return refs[item.id];
|
||||
}
|
||||
|
||||
noteCountElement(count) {
|
||||
return <div style={this.style().noteCount}>({count})</div>;
|
||||
}
|
||||
|
||||
folderItem(folder, selected, hasChildren, depth) {
|
||||
let style = Object.assign({}, this.style().listItem);
|
||||
if (folder.id === Folder.conflictFolderId()) style = Object.assign(style, this.style().conflictFolder);
|
||||
|
||||
const itemTitle = Folder.displayTitle(folder);
|
||||
|
||||
let containerStyle = Object.assign({}, this.style().listItemContainer);
|
||||
if (selected) containerStyle = Object.assign(containerStyle, this.style().listItemSelected);
|
||||
|
||||
containerStyle.paddingLeft = 8 + depth * 15;
|
||||
|
||||
const expandLinkStyle = Object.assign({}, this.style().listItemExpandIcon);
|
||||
const expandIconStyle = {
|
||||
visibility: hasChildren ? 'visible' : 'hidden',
|
||||
};
|
||||
|
||||
const iconName = this.props.collapsedFolderIds.indexOf(folder.id) >= 0 ? 'fa-chevron-right' : 'fa-chevron-down';
|
||||
const expandIcon = <i style={expandIconStyle} className={`fas ${iconName}`}></i>;
|
||||
const expandLink = hasChildren ? (
|
||||
<a style={expandLinkStyle} href="#" folderid={folder.id} onClick={this.onFolderToggleClick_}>
|
||||
{expandIcon}
|
||||
</a>
|
||||
) : (
|
||||
<span style={expandLinkStyle}>{expandIcon}</span>
|
||||
);
|
||||
|
||||
const anchorRef = this.anchorItemRef('folder', folder.id);
|
||||
const noteCount = folder.note_count ? this.noteCountElement(folder.note_count) : '';
|
||||
|
||||
return (
|
||||
<div className={`list-item-container list-item-depth-${depth}`} style={containerStyle} key={folder.id} onDragStart={this.onFolderDragStart_} onDragOver={this.onFolderDragOver_} onDrop={this.onFolderDrop_} draggable={true} folderid={folder.id}>
|
||||
{expandLink}
|
||||
<a
|
||||
ref={anchorRef}
|
||||
className="list-item"
|
||||
href="#"
|
||||
data-id={folder.id}
|
||||
data-type={BaseModel.TYPE_FOLDER}
|
||||
onContextMenu={event => this.itemContextMenu(event)}
|
||||
style={style}
|
||||
folderid={folder.id}
|
||||
onClick={() => {
|
||||
this.folderItem_click(folder);
|
||||
}}
|
||||
onDoubleClick={this.onFolderToggleClick_}
|
||||
>
|
||||
{itemTitle} {noteCount}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
tagItem(tag, selected) {
|
||||
let style = Object.assign({}, this.style().tagItem);
|
||||
if (selected) style = Object.assign(style, this.style().listItemSelected);
|
||||
|
||||
const anchorRef = this.anchorItemRef('tag', tag.id);
|
||||
const noteCount = Setting.value('showNoteCounts') ? this.noteCountElement(tag.note_count) : '';
|
||||
|
||||
return (
|
||||
<a
|
||||
className="list-item"
|
||||
href="#"
|
||||
ref={anchorRef}
|
||||
data-id={tag.id}
|
||||
data-type={BaseModel.TYPE_TAG}
|
||||
onContextMenu={event => this.itemContextMenu(event)}
|
||||
tagid={tag.id}
|
||||
key={tag.id}
|
||||
style={style}
|
||||
onDrop={this.onTagDrop_}
|
||||
onClick={() => {
|
||||
this.tagItem_click(tag);
|
||||
}}
|
||||
>
|
||||
{Tag.displayTitle(tag)} {noteCount}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
// searchItem(search, selected) {
|
||||
// let style = Object.assign({}, this.style().listItem);
|
||||
// if (selected) style = Object.assign(style, this.style().listItemSelected);
|
||||
// return (
|
||||
// <a
|
||||
// className="list-item"
|
||||
// href="#"
|
||||
// data-id={search.id}
|
||||
// data-type={BaseModel.TYPE_SEARCH}
|
||||
// onContextMenu={event => this.itemContextMenu(event)}
|
||||
// key={search.id}
|
||||
// style={style}
|
||||
// onClick={() => {
|
||||
// this.searchItem_click(search);
|
||||
// }}
|
||||
// >
|
||||
// {search.title}
|
||||
// </a>
|
||||
// );
|
||||
// }
|
||||
|
||||
makeDivider(key) {
|
||||
return <div style={{ height: 2, backgroundColor: 'blue' }} key={key} />;
|
||||
}
|
||||
|
||||
makeHeader(key, label, iconName, extraProps = {}) {
|
||||
const style = this.style().header;
|
||||
const icon = <i style={{ fontSize: style.fontSize, marginRight: 5 }} className={`fas ${iconName}`} />;
|
||||
|
||||
if (extraProps.toggleblock || extraProps.onClick) {
|
||||
style.cursor = 'pointer';
|
||||
}
|
||||
|
||||
const headerClick = extraProps.onClick || null;
|
||||
delete extraProps.onClick;
|
||||
|
||||
// check if toggling option is set.
|
||||
let toggleIcon = null;
|
||||
const toggleKey = `${key}IsExpanded`;
|
||||
if (extraProps.toggleblock) {
|
||||
const isExpanded = this.state[toggleKey];
|
||||
toggleIcon = <i className={`fas ${isExpanded ? 'fa-chevron-down' : 'fa-chevron-right'}`} style={{ fontSize: style.fontSize * 0.75, marginRight: 12, marginLeft: 5, marginTop: style.fontSize * 0.125 }}></i>;
|
||||
}
|
||||
if (extraProps.selected) {
|
||||
style.backgroundColor = this.style().listItemSelected.backgroundColor;
|
||||
}
|
||||
|
||||
const ref = this.anchorItemRef('headers', key);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
style={style}
|
||||
key={key}
|
||||
{...extraProps}
|
||||
onClick={event => {
|
||||
// if a custom click event is attached, trigger that.
|
||||
if (headerClick) {
|
||||
headerClick(key, event);
|
||||
}
|
||||
this.onHeaderClick_(key, event);
|
||||
}}
|
||||
>
|
||||
{icon}
|
||||
<span style={{ flex: 1 }}>{label}</span>
|
||||
{toggleIcon}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
selectedItem() {
|
||||
if (this.props.notesParentType === 'Folder' && this.props.selectedFolderId) {
|
||||
return { type: 'folder', id: this.props.selectedFolderId };
|
||||
} else if (this.props.notesParentType === 'Tag' && this.props.selectedTagId) {
|
||||
return { type: 'tag', id: this.props.selectedTagId };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
onKeyDown(event) {
|
||||
const keyCode = event.keyCode;
|
||||
const selectedItem = this.selectedItem();
|
||||
|
||||
if (keyCode === 40 || keyCode === 38) {
|
||||
// DOWN / UP
|
||||
event.preventDefault();
|
||||
|
||||
const focusItems = [];
|
||||
|
||||
for (let i = 0; i < this.folderItemsOrder_.length; i++) {
|
||||
const id = this.folderItemsOrder_[i];
|
||||
focusItems.push({ id: id, ref: this.anchorItemRefs['folder'][id], type: 'folder' });
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.tagItemsOrder_.length; i++) {
|
||||
const id = this.tagItemsOrder_[i];
|
||||
focusItems.push({ id: id, ref: this.anchorItemRefs['tag'][id], type: 'tag' });
|
||||
}
|
||||
|
||||
let currentIndex = 0;
|
||||
for (let i = 0; i < focusItems.length; i++) {
|
||||
if (!selectedItem || focusItems[i].id === selectedItem.id) {
|
||||
currentIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const inc = keyCode === 38 ? -1 : +1;
|
||||
let newIndex = currentIndex + inc;
|
||||
|
||||
if (newIndex < 0) newIndex = 0;
|
||||
if (newIndex > focusItems.length - 1) newIndex = focusItems.length - 1;
|
||||
|
||||
const focusItem = focusItems[newIndex];
|
||||
|
||||
const actionName = `${focusItem.type.toUpperCase()}_SELECT`;
|
||||
|
||||
this.props.dispatch({
|
||||
type: actionName,
|
||||
id: focusItem.id,
|
||||
});
|
||||
|
||||
focusItem.ref.current.focus();
|
||||
}
|
||||
|
||||
if (keyCode === 9) {
|
||||
// TAB
|
||||
event.preventDefault();
|
||||
|
||||
if (event.shiftKey) {
|
||||
CommandService.instance().execute('focusElement', { target: 'noteBody' });
|
||||
} else {
|
||||
CommandService.instance().execute('focusElement', { target: 'noteList' });
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedItem && selectedItem.type === 'folder' && keyCode === 32) {
|
||||
// SPACE
|
||||
event.preventDefault();
|
||||
|
||||
this.props.dispatch({
|
||||
type: 'FOLDER_TOGGLE',
|
||||
id: selectedItem.id,
|
||||
});
|
||||
}
|
||||
|
||||
if (keyCode === 65 && (event.ctrlKey || event.metaKey)) {
|
||||
// Ctrl+A key
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
onHeaderClick_(key, event) {
|
||||
const currentHeader = event.currentTarget;
|
||||
const toggleBlock = +currentHeader.getAttribute('toggleblock');
|
||||
if (toggleBlock) {
|
||||
const toggleKey = `${key}IsExpanded`;
|
||||
const isExpanded = this.state[toggleKey];
|
||||
this.setState({ [toggleKey]: !isExpanded });
|
||||
Setting.setValue(toggleKey, !isExpanded);
|
||||
}
|
||||
}
|
||||
|
||||
onAllNotesClick_() {
|
||||
this.props.dispatch({
|
||||
type: 'SMART_FILTER_SELECT',
|
||||
id: ALL_NOTES_FILTER_ID,
|
||||
});
|
||||
}
|
||||
|
||||
synchronizeButton(type) {
|
||||
const style = Object.assign({}, this.style().button, { marginBottom: 5 });
|
||||
const iconName = 'fa-sync-alt';
|
||||
const label = type === 'sync' ? _('Synchronise') : _('Cancel');
|
||||
const iconStyle = { fontSize: style.fontSize, marginRight: 5 };
|
||||
|
||||
if (type !== 'sync') {
|
||||
iconStyle.animation = 'icon-infinite-rotation 1s linear infinite';
|
||||
}
|
||||
|
||||
const icon = <i style={iconStyle} className={`fas ${iconName}`} />;
|
||||
return (
|
||||
<a
|
||||
className="synchronize-button"
|
||||
style={style}
|
||||
href="#"
|
||||
key="sync_button"
|
||||
onClick={() => {
|
||||
CommandService.instance().execute('synchronize');
|
||||
// this.sync_click();
|
||||
}}
|
||||
>
|
||||
{icon}
|
||||
{label}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const style = Object.assign({}, this.style().root, this.props.style, {
|
||||
overflowX: 'hidden',
|
||||
overflowY: 'hidden',
|
||||
display: 'inline-flex',
|
||||
flexDirection: 'column',
|
||||
});
|
||||
|
||||
const items = [];
|
||||
items.push(
|
||||
this.makeHeader('allNotesHeader', _('All notes'), 'fa-clone', {
|
||||
onClick: this.onAllNotesClick_,
|
||||
selected: this.props.notesParentType === 'SmartFilter' && this.props.selectedSmartFilterId === ALL_NOTES_FILTER_ID,
|
||||
})
|
||||
);
|
||||
|
||||
items.push(
|
||||
this.makeHeader('folderHeader', _('Notebooks'), 'fa-book', {
|
||||
onDrop: this.onFolderDrop_,
|
||||
folderid: '',
|
||||
toggleblock: 1,
|
||||
})
|
||||
);
|
||||
|
||||
if (this.props.folders.length) {
|
||||
const result = shared.renderFolders(this.props, this.folderItem.bind(this));
|
||||
const folderItems = result.items;
|
||||
this.folderItemsOrder_ = result.order;
|
||||
items.push(
|
||||
<div className="folders" key="folder_items" style={{ display: this.state.folderHeaderIsExpanded ? 'block' : 'none' }}>
|
||||
{folderItems}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
items.push(
|
||||
this.makeHeader('tagHeader', _('Tags'), 'fa-tags', {
|
||||
toggleblock: 1,
|
||||
})
|
||||
);
|
||||
|
||||
if (this.props.tags.length) {
|
||||
const result = shared.renderTags(this.props, this.tagItem.bind(this));
|
||||
const tagItems = result.items;
|
||||
this.tagItemsOrder_ = result.order;
|
||||
|
||||
items.push(
|
||||
<div className="tags" key="tag_items" style={{ display: this.state.tagHeaderIsExpanded ? 'block' : 'none' }}>
|
||||
{tagItems}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let decryptionReportText = '';
|
||||
if (this.props.decryptionWorker && this.props.decryptionWorker.state !== 'idle' && this.props.decryptionWorker.itemCount) {
|
||||
decryptionReportText = _('Decrypting items: %d/%d', this.props.decryptionWorker.itemIndex + 1, this.props.decryptionWorker.itemCount);
|
||||
}
|
||||
|
||||
let resourceFetcherText = '';
|
||||
if (this.props.resourceFetcher && this.props.resourceFetcher.toFetchCount) {
|
||||
resourceFetcherText = _('Fetching resources: %d/%d', this.props.resourceFetcher.fetchingCount, this.props.resourceFetcher.toFetchCount);
|
||||
}
|
||||
|
||||
const lines = Synchronizer.reportToLines(this.props.syncReport);
|
||||
if (resourceFetcherText) lines.push(resourceFetcherText);
|
||||
if (decryptionReportText) lines.push(decryptionReportText);
|
||||
const syncReportText = [];
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
syncReportText.push(
|
||||
<div key={i} style={{ wordWrap: 'break-word', width: '100%' }}>
|
||||
{lines[i]}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const syncButton = this.synchronizeButton(this.props.syncStarted ? 'cancel' : 'sync');
|
||||
|
||||
const syncReportComp = !syncReportText.length ? null : (
|
||||
<div style={this.style().syncReport} key="sync_report">
|
||||
{syncReportText}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div ref={this.rootRef} onKeyDown={this.onKeyDown} className="side-bar" style={style}>
|
||||
<div style={{ flex: 1, overflowX: 'hidden', overflowY: 'auto' }}>{items}</div>
|
||||
<div style={{ flex: 0 }}>
|
||||
{syncReportComp}
|
||||
{syncButton}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
folders: state.folders,
|
||||
tags: state.tags,
|
||||
searches: state.searches,
|
||||
syncStarted: state.syncStarted,
|
||||
syncReport: state.syncReport,
|
||||
selectedFolderId: state.selectedFolderId,
|
||||
selectedTagId: state.selectedTagId,
|
||||
selectedSearchId: state.selectedSearchId,
|
||||
selectedSmartFilterId: state.selectedSmartFilterId,
|
||||
notesParentType: state.notesParentType,
|
||||
locale: state.settings.locale,
|
||||
theme: state.settings.theme,
|
||||
collapsedFolderIds: state.collapsedFolderIds,
|
||||
decryptionWorker: state.decryptionWorker,
|
||||
resourceFetcher: state.resourceFetcher,
|
||||
sidebarVisibility: state.sidebarVisibility,
|
||||
noteListVisibility: state.noteListVisibility,
|
||||
};
|
||||
};
|
||||
|
||||
const SideBar = connect(mapStateToProps)(SideBarComponent);
|
||||
|
||||
module.exports = { SideBar };
|
646
ElectronClient/gui/SideBar/SideBar.tsx
Normal file
646
ElectronClient/gui/SideBar/SideBar.tsx
Normal file
@ -0,0 +1,646 @@
|
||||
import * as React from 'react';
|
||||
import { StyledRoot, StyledAddButton, StyledHeader, StyledHeaderIcon, StyledHeaderLabel, StyledListItem, StyledListItemAnchor, StyledExpandLink, StyledNoteCount, StyledSyncReportText, StyledSyncReport, StyledSynchronizeButton } from './styles';
|
||||
import { ButtonLevel } from '../Button/Button';
|
||||
import CommandService from 'lib/services/CommandService';
|
||||
|
||||
const { connect } = require('react-redux');
|
||||
const shared = require('lib/components/shared/side-menu-shared.js');
|
||||
const { Synchronizer } = require('lib/synchronizer.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
const Tag = require('lib/models/Tag.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { themeStyle } = require('lib/theme');
|
||||
const { bridge } = require('electron').remote.require('./bridge');
|
||||
const Menu = bridge().Menu;
|
||||
const MenuItem = bridge().MenuItem;
|
||||
const InteropServiceHelper = require('../../InteropServiceHelper.js');
|
||||
const { substrWithEllipsis } = require('lib/string-utils');
|
||||
const { ALL_NOTES_FILTER_ID } = require('lib/reserved-ids');
|
||||
|
||||
interface Props {
|
||||
themeId: number,
|
||||
dispatch: Function,
|
||||
folders: any[],
|
||||
collapsedFolderIds: string[],
|
||||
notesParentType: string,
|
||||
selectedFolderId: string,
|
||||
selectedTagId: string,
|
||||
selectedSmartFilterId:string,
|
||||
decryptionWorker: any,
|
||||
resourceFetcher: any,
|
||||
syncReport: any,
|
||||
tags: any[],
|
||||
syncStarted: boolean,
|
||||
}
|
||||
|
||||
interface State {
|
||||
tagHeaderIsExpanded: boolean,
|
||||
folderHeaderIsExpanded: boolean,
|
||||
}
|
||||
|
||||
const commands = [
|
||||
require('./commands/focusElementSideBar'),
|
||||
];
|
||||
|
||||
class SideBarComponent extends React.Component<Props, State> {
|
||||
|
||||
private folderItemsOrder_:any[] = [];
|
||||
private tagItemsOrder_:any[] = [];
|
||||
private rootRef:any = null;
|
||||
private anchorItemRefs:any = {};
|
||||
private forceUpdateDuringSyncIID_:any = null;
|
||||
|
||||
constructor(props:any) {
|
||||
super(props);
|
||||
|
||||
CommandService.instance().componentRegisterCommands(this, commands);
|
||||
|
||||
this.state = {
|
||||
tagHeaderIsExpanded: Setting.value('tagHeaderIsExpanded'),
|
||||
folderHeaderIsExpanded: Setting.value('folderHeaderIsExpanded'),
|
||||
};
|
||||
|
||||
this.onFolderToggleClick_ = this.onFolderToggleClick_.bind(this);
|
||||
this.onKeyDown = this.onKeyDown.bind(this);
|
||||
this.onAllNotesClick_ = this.onAllNotesClick_.bind(this);
|
||||
this.header_contextMenu = this.header_contextMenu.bind(this);
|
||||
this.onAddFolderButtonClick = this.onAddFolderButtonClick.bind(this);
|
||||
}
|
||||
|
||||
onFolderDragStart_(event:any) {
|
||||
const folderId = event.currentTarget.getAttribute('data-folder-id');
|
||||
if (!folderId) return;
|
||||
|
||||
event.dataTransfer.setDragImage(new Image(), 1, 1);
|
||||
event.dataTransfer.clearData();
|
||||
event.dataTransfer.setData('text/x-jop-folder-ids', JSON.stringify([folderId]));
|
||||
}
|
||||
|
||||
onFolderDragOver_(event:any) {
|
||||
if (event.dataTransfer.types.indexOf('text/x-jop-note-ids') >= 0) event.preventDefault();
|
||||
if (event.dataTransfer.types.indexOf('text/x-jop-folder-ids') >= 0) event.preventDefault();
|
||||
}
|
||||
|
||||
async onFolderDrop_(event:any) {
|
||||
const folderId = event.currentTarget.getAttribute('data-folder-id');
|
||||
const dt = event.dataTransfer;
|
||||
if (!dt) return;
|
||||
|
||||
// folderId can be NULL when dropping on the sidebar Notebook header. In that case, it's used
|
||||
// to put the dropped folder at the root. But for notes, folderId needs to always be defined
|
||||
// since there's no such thing as a root note.
|
||||
|
||||
if (dt.types.indexOf('text/x-jop-note-ids') >= 0) {
|
||||
event.preventDefault();
|
||||
|
||||
if (!folderId) return;
|
||||
|
||||
const noteIds = JSON.parse(dt.getData('text/x-jop-note-ids'));
|
||||
for (let i = 0; i < noteIds.length; i++) {
|
||||
await Note.moveToFolder(noteIds[i], folderId);
|
||||
}
|
||||
} else if (dt.types.indexOf('text/x-jop-folder-ids') >= 0) {
|
||||
event.preventDefault();
|
||||
|
||||
const folderIds = JSON.parse(dt.getData('text/x-jop-folder-ids'));
|
||||
for (let i = 0; i < folderIds.length; i++) {
|
||||
await Folder.moveToFolder(folderIds[i], folderId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async onTagDrop_(event:any) {
|
||||
const tagId = event.currentTarget.getAttribute('data-tag-id');
|
||||
const dt = event.dataTransfer;
|
||||
if (!dt) return;
|
||||
|
||||
if (dt.types.indexOf('text/x-jop-note-ids') >= 0) {
|
||||
event.preventDefault();
|
||||
|
||||
const noteIds = JSON.parse(dt.getData('text/x-jop-note-ids'));
|
||||
for (let i = 0; i < noteIds.length; i++) {
|
||||
await Tag.addNote(tagId, noteIds[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async onFolderToggleClick_(event:any) {
|
||||
const folderId = event.currentTarget.getAttribute('data-folder-id');
|
||||
|
||||
this.props.dispatch({
|
||||
type: 'FOLDER_TOGGLE',
|
||||
id: folderId,
|
||||
});
|
||||
}
|
||||
|
||||
clearForceUpdateDuringSync() {
|
||||
if (this.forceUpdateDuringSyncIID_) {
|
||||
clearInterval(this.forceUpdateDuringSyncIID_);
|
||||
this.forceUpdateDuringSyncIID_ = null;
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.clearForceUpdateDuringSync();
|
||||
|
||||
CommandService.instance().componentUnregisterCommands(commands);
|
||||
}
|
||||
|
||||
async header_contextMenu() {
|
||||
const menu = new Menu();
|
||||
|
||||
menu.append(
|
||||
new MenuItem(CommandService.instance().commandToMenuItem('newFolder'))
|
||||
);
|
||||
|
||||
menu.popup(bridge().window());
|
||||
}
|
||||
|
||||
async itemContextMenu(event:any) {
|
||||
const itemId = event.currentTarget.getAttribute('data-id');
|
||||
if (itemId === Folder.conflictFolderId()) return;
|
||||
|
||||
const itemType = Number(event.currentTarget.getAttribute('data-type'));
|
||||
if (!itemId || !itemType) throw new Error('No data on element');
|
||||
|
||||
let deleteMessage = '';
|
||||
let buttonLabel = _('Remove');
|
||||
if (itemType === BaseModel.TYPE_FOLDER) {
|
||||
const folder = await Folder.load(itemId);
|
||||
deleteMessage = _('Delete notebook "%s"?\n\nAll notes and sub-notebooks within this notebook will also be deleted.', substrWithEllipsis(folder.title, 0, 32));
|
||||
buttonLabel = _('Delete');
|
||||
} else if (itemType === BaseModel.TYPE_TAG) {
|
||||
const tag = await Tag.load(itemId);
|
||||
deleteMessage = _('Remove tag "%s" from all notes?', substrWithEllipsis(tag.title, 0, 32));
|
||||
} else if (itemType === BaseModel.TYPE_SEARCH) {
|
||||
deleteMessage = _('Remove this search from the sidebar?');
|
||||
}
|
||||
|
||||
const menu = new Menu();
|
||||
|
||||
let item = null;
|
||||
if (itemType === BaseModel.TYPE_FOLDER) {
|
||||
item = BaseModel.byId(this.props.folders, itemId);
|
||||
}
|
||||
|
||||
if (itemType === BaseModel.TYPE_FOLDER && !item.encryption_applied) {
|
||||
menu.append(
|
||||
new MenuItem(CommandService.instance().commandToMenuItem('newFolder', { parentId: itemId }))
|
||||
);
|
||||
}
|
||||
|
||||
menu.append(
|
||||
new MenuItem({
|
||||
label: buttonLabel,
|
||||
click: async () => {
|
||||
const ok = bridge().showConfirmMessageBox(deleteMessage, {
|
||||
buttons: [buttonLabel, _('Cancel')],
|
||||
defaultId: 1,
|
||||
});
|
||||
if (!ok) return;
|
||||
|
||||
if (itemType === BaseModel.TYPE_FOLDER) {
|
||||
await Folder.delete(itemId);
|
||||
} else if (itemType === BaseModel.TYPE_TAG) {
|
||||
await Tag.untagAll(itemId);
|
||||
} else if (itemType === BaseModel.TYPE_SEARCH) {
|
||||
this.props.dispatch({
|
||||
type: 'SEARCH_DELETE',
|
||||
id: itemId,
|
||||
});
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
if (itemType === BaseModel.TYPE_FOLDER && !item.encryption_applied) {
|
||||
menu.append(new MenuItem(CommandService.instance().commandToMenuItem('renameFolder', { folderId: itemId })));
|
||||
|
||||
menu.append(new MenuItem({ type: 'separator' }));
|
||||
|
||||
const InteropService = require('lib/services/InteropService.js');
|
||||
|
||||
const exportMenu = new Menu();
|
||||
const ioService = new InteropService();
|
||||
const ioModules = ioService.modules();
|
||||
for (let i = 0; i < ioModules.length; i++) {
|
||||
const module = ioModules[i];
|
||||
if (module.type !== 'exporter') continue;
|
||||
|
||||
exportMenu.append(
|
||||
new MenuItem({
|
||||
label: module.fullLabel(),
|
||||
click: async () => {
|
||||
await InteropServiceHelper.export(this.props.dispatch.bind(this), module, { sourceFolderIds: [itemId] });
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
menu.append(
|
||||
new MenuItem({
|
||||
label: _('Export'),
|
||||
submenu: exportMenu,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (itemType === BaseModel.TYPE_TAG) {
|
||||
menu.append(new MenuItem(
|
||||
CommandService.instance().commandToMenuItem('renameTag', { tagId: itemId })
|
||||
));
|
||||
}
|
||||
|
||||
menu.popup(bridge().window());
|
||||
}
|
||||
|
||||
folderItem_click(folder:any) {
|
||||
this.props.dispatch({
|
||||
type: 'FOLDER_SELECT',
|
||||
id: folder ? folder.id : null,
|
||||
});
|
||||
}
|
||||
|
||||
tagItem_click(tag:any) {
|
||||
this.props.dispatch({
|
||||
type: 'TAG_SELECT',
|
||||
id: tag ? tag.id : null,
|
||||
});
|
||||
}
|
||||
|
||||
anchorItemRef(type:string, id:string) {
|
||||
if (!this.anchorItemRefs[type]) this.anchorItemRefs[type] = {};
|
||||
if (this.anchorItemRefs[type][id]) return this.anchorItemRefs[type][id];
|
||||
this.anchorItemRefs[type][id] = React.createRef();
|
||||
return this.anchorItemRefs[type][id];
|
||||
}
|
||||
|
||||
firstAnchorItemRef(type:string) {
|
||||
const refs = this.anchorItemRefs[type];
|
||||
if (!refs) return null;
|
||||
|
||||
const n = `${type}s`;
|
||||
const p = this.props as any;
|
||||
const item = p[n] && p[n].length ? p[n][0] : null;
|
||||
if (!item) return null;
|
||||
|
||||
return refs[item.id];
|
||||
}
|
||||
|
||||
renderNoteCount(count:number) {
|
||||
return <StyledNoteCount>{count}</StyledNoteCount>;
|
||||
}
|
||||
|
||||
renderExpandIcon(isExpanded:boolean, isVisible:boolean = true) {
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
const style:any = { width: 16, maxWidth: 16, opacity: 0.5, fontSize: Math.round(theme.toolbarIconSize * 0.8), display: 'flex', justifyContent: 'center' };
|
||||
if (!isVisible) style.visibility = 'hidden';
|
||||
return <i className={isExpanded ? 'fas fa-caret-down' : 'fas fa-caret-right'} style={style}></i>;
|
||||
}
|
||||
|
||||
renderAllNotesItem(selected:boolean) {
|
||||
return (
|
||||
<StyledListItem key="allNotesHeader" selected={selected} className={'list-item-container list-item-depth-0'} isSpecialItem={true}>
|
||||
<StyledExpandLink>{this.renderExpandIcon(false, false)}</StyledExpandLink>
|
||||
<StyledListItemAnchor
|
||||
className="list-item"
|
||||
isSpecialItem={true}
|
||||
href="#"
|
||||
selected={selected}
|
||||
onClick={() => {
|
||||
this.onAllNotesClick_();
|
||||
}}
|
||||
>
|
||||
({_('All notes')})
|
||||
</StyledListItemAnchor>
|
||||
</StyledListItem>
|
||||
);
|
||||
}
|
||||
|
||||
renderFolderItem(folder:any, selected:boolean, hasChildren:boolean, depth:number) {
|
||||
const isExpanded = this.props.collapsedFolderIds.indexOf(folder.id) < 0;
|
||||
const expandIcon = this.renderExpandIcon(isExpanded, hasChildren);
|
||||
const expandLink = hasChildren ? (
|
||||
<StyledExpandLink href="#" data-folder-id={folder.id} onClick={this.onFolderToggleClick_}>
|
||||
{expandIcon}
|
||||
</StyledExpandLink>
|
||||
) : (
|
||||
<StyledExpandLink>{expandIcon}</StyledExpandLink>
|
||||
);
|
||||
|
||||
const anchorRef = this.anchorItemRef('folder', folder.id);
|
||||
const noteCount = folder.note_count ? this.renderNoteCount(folder.note_count) : '';
|
||||
|
||||
return (
|
||||
<StyledListItem depth={depth} selected={selected} className={`list-item-container list-item-depth-${depth}`} key={folder.id} onDragStart={this.onFolderDragStart_} onDragOver={this.onFolderDragOver_} onDrop={this.onFolderDrop_} draggable={true} data-folder-id={folder.id}>
|
||||
{expandLink}
|
||||
<StyledListItemAnchor
|
||||
ref={anchorRef}
|
||||
className="list-item"
|
||||
isConflictFolder={folder.id === Folder.conflictFolderId()}
|
||||
href="#"
|
||||
selected={selected}
|
||||
data-id={folder.id}
|
||||
data-type={BaseModel.TYPE_FOLDER}
|
||||
onContextMenu={(event:any) => this.itemContextMenu(event)}
|
||||
data-folder-id={folder.id}
|
||||
onClick={() => {
|
||||
this.folderItem_click(folder);
|
||||
}}
|
||||
onDoubleClick={this.onFolderToggleClick_}
|
||||
>
|
||||
{Folder.displayTitle(folder)} {noteCount}
|
||||
</StyledListItemAnchor>
|
||||
</StyledListItem>
|
||||
);
|
||||
}
|
||||
|
||||
renderTag(tag:any, selected:boolean) {
|
||||
const anchorRef = this.anchorItemRef('tag', tag.id);
|
||||
const noteCount = Setting.value('showNoteCounts') ? this.renderNoteCount(tag.note_count) : '';
|
||||
|
||||
return (
|
||||
<StyledListItem selected={selected} className={'list-item-container'} key={tag.id} onDrop={this.onTagDrop_} data-tag-id={tag.id}>
|
||||
<StyledExpandLink>{this.renderExpandIcon(false, false)}</StyledExpandLink>
|
||||
<StyledListItemAnchor
|
||||
ref={anchorRef}
|
||||
className="list-item"
|
||||
href="#"
|
||||
selected={selected}
|
||||
data-id={tag.id}
|
||||
data-type={BaseModel.TYPE_TAG}
|
||||
onContextMenu={(event:any) => this.itemContextMenu(event)}
|
||||
onClick={() => {
|
||||
this.tagItem_click(tag);
|
||||
}}
|
||||
>
|
||||
{Tag.displayTitle(tag)} {noteCount}
|
||||
</StyledListItemAnchor>
|
||||
</StyledListItem>
|
||||
);
|
||||
}
|
||||
|
||||
makeDivider(key:string) {
|
||||
return <div style={{ height: 2, backgroundColor: 'blue' }} key={key} />;
|
||||
}
|
||||
|
||||
renderHeader(key:string, label:string, iconName:string, contextMenuHandler:Function = null, onPlusButtonClick:Function = null, extraProps:any = {}) {
|
||||
const headerClick = extraProps.onClick || null;
|
||||
delete extraProps.onClick;
|
||||
const ref = this.anchorItemRef('headers', key);
|
||||
|
||||
return (
|
||||
<div key={key} style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
|
||||
<StyledHeader
|
||||
ref={ref}
|
||||
{...extraProps}
|
||||
onContextMenu={contextMenuHandler}
|
||||
onClick={(event:any) => {
|
||||
// if a custom click event is attached, trigger that.
|
||||
if (headerClick) {
|
||||
headerClick(key, event);
|
||||
}
|
||||
this.onHeaderClick_(key);
|
||||
}}
|
||||
>
|
||||
<StyledHeaderIcon className={iconName}/>
|
||||
<StyledHeaderLabel>{label}</StyledHeaderLabel>
|
||||
</StyledHeader>
|
||||
{ onPlusButtonClick && <StyledAddButton onClick={onPlusButtonClick} iconName="fas fa-plus" level={ButtonLevel.SideBarSecondary}/> }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
selectedItem() {
|
||||
if (this.props.notesParentType === 'Folder' && this.props.selectedFolderId) {
|
||||
return { type: 'folder', id: this.props.selectedFolderId };
|
||||
} else if (this.props.notesParentType === 'Tag' && this.props.selectedTagId) {
|
||||
return { type: 'tag', id: this.props.selectedTagId };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
onKeyDown(event:any) {
|
||||
const keyCode = event.keyCode;
|
||||
const selectedItem = this.selectedItem();
|
||||
|
||||
if (keyCode === 40 || keyCode === 38) {
|
||||
// DOWN / UP
|
||||
event.preventDefault();
|
||||
|
||||
const focusItems = [];
|
||||
|
||||
for (let i = 0; i < this.folderItemsOrder_.length; i++) {
|
||||
const id = this.folderItemsOrder_[i];
|
||||
focusItems.push({ id: id, ref: this.anchorItemRefs['folder'][id], type: 'folder' });
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.tagItemsOrder_.length; i++) {
|
||||
const id = this.tagItemsOrder_[i];
|
||||
focusItems.push({ id: id, ref: this.anchorItemRefs['tag'][id], type: 'tag' });
|
||||
}
|
||||
|
||||
let currentIndex = 0;
|
||||
for (let i = 0; i < focusItems.length; i++) {
|
||||
if (!selectedItem || focusItems[i].id === selectedItem.id) {
|
||||
currentIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const inc = keyCode === 38 ? -1 : +1;
|
||||
let newIndex = currentIndex + inc;
|
||||
|
||||
if (newIndex < 0) newIndex = 0;
|
||||
if (newIndex > focusItems.length - 1) newIndex = focusItems.length - 1;
|
||||
|
||||
const focusItem = focusItems[newIndex];
|
||||
|
||||
const actionName = `${focusItem.type.toUpperCase()}_SELECT`;
|
||||
|
||||
this.props.dispatch({
|
||||
type: actionName,
|
||||
id: focusItem.id,
|
||||
});
|
||||
|
||||
focusItem.ref.current.focus();
|
||||
}
|
||||
|
||||
if (keyCode === 9) {
|
||||
// TAB
|
||||
event.preventDefault();
|
||||
|
||||
if (event.shiftKey) {
|
||||
CommandService.instance().execute('focusElement', { target: 'noteBody' });
|
||||
} else {
|
||||
CommandService.instance().execute('focusElement', { target: 'noteList' });
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedItem && selectedItem.type === 'folder' && keyCode === 32) {
|
||||
// SPACE
|
||||
event.preventDefault();
|
||||
|
||||
this.props.dispatch({
|
||||
type: 'FOLDER_TOGGLE',
|
||||
id: selectedItem.id,
|
||||
});
|
||||
}
|
||||
|
||||
if (keyCode === 65 && (event.ctrlKey || event.metaKey)) {
|
||||
// Ctrl+A key
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
onHeaderClick_(key:string) {
|
||||
const toggleKey = `${key}IsExpanded`;
|
||||
const isExpanded = (this.state as any)[toggleKey];
|
||||
const newState:any = { [toggleKey]: !isExpanded };
|
||||
this.setState(newState);
|
||||
Setting.setValue(toggleKey, !isExpanded);
|
||||
}
|
||||
|
||||
onAllNotesClick_() {
|
||||
this.props.dispatch({
|
||||
type: 'SMART_FILTER_SELECT',
|
||||
id: ALL_NOTES_FILTER_ID,
|
||||
});
|
||||
}
|
||||
|
||||
renderSynchronizeButton(type:string) {
|
||||
const label = type === 'sync' ? _('Synchronise') : _('Cancel');
|
||||
const iconAnimation = type !== 'sync' ? 'icon-infinite-rotation 1s linear infinite' : '';
|
||||
|
||||
return (
|
||||
<StyledSynchronizeButton
|
||||
level={ButtonLevel.SideBarSecondary}
|
||||
iconName="icon-sync"
|
||||
key="sync_button"
|
||||
iconAnimation={iconAnimation}
|
||||
title={label}
|
||||
onClick={() => {
|
||||
CommandService.instance().execute('synchronize', { syncStarted: type !== 'sync' });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
onAddFolderButtonClick() {
|
||||
CommandService.instance().execute('newFolder');
|
||||
}
|
||||
|
||||
render() {
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
const items = [];
|
||||
|
||||
items.push(
|
||||
this.renderHeader('folderHeader', _('Notebooks'), 'icon-notebooks', this.header_contextMenu, this.onAddFolderButtonClick, {
|
||||
onDrop: this.onFolderDrop_,
|
||||
['data-folder-id']: '',
|
||||
toggleblock: 1,
|
||||
})
|
||||
);
|
||||
|
||||
if (this.props.folders.length) {
|
||||
const allNotesSelected = this.props.notesParentType === 'SmartFilter' && this.props.selectedSmartFilterId === ALL_NOTES_FILTER_ID;
|
||||
const result = shared.renderFolders(this.props, this.renderFolderItem.bind(this));
|
||||
const folderItems = [this.renderAllNotesItem(allNotesSelected)].concat(result.items);
|
||||
this.folderItemsOrder_ = result.order;
|
||||
items.push(
|
||||
<div className="folders" key="folder_items" style={{ display: this.state.folderHeaderIsExpanded ? 'block' : 'none', paddingBottom: 10 }}>
|
||||
{folderItems}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
items.push(
|
||||
this.renderHeader('tagHeader', _('Tags'), 'icon-tags', null, null, {
|
||||
toggleblock: 1,
|
||||
})
|
||||
);
|
||||
|
||||
if (this.props.tags.length) {
|
||||
const result = shared.renderTags(this.props, this.renderTag.bind(this));
|
||||
const tagItems = result.items;
|
||||
this.tagItemsOrder_ = result.order;
|
||||
|
||||
items.push(
|
||||
<div className="tags" key="tag_items" style={{ display: this.state.tagHeaderIsExpanded ? 'block' : 'none' }}>
|
||||
{tagItems}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let decryptionReportText = '';
|
||||
if (this.props.decryptionWorker && this.props.decryptionWorker.state !== 'idle' && this.props.decryptionWorker.itemCount) {
|
||||
decryptionReportText = _('Decrypting items: %d/%d', this.props.decryptionWorker.itemIndex + 1, this.props.decryptionWorker.itemCount);
|
||||
}
|
||||
|
||||
let resourceFetcherText = '';
|
||||
if (this.props.resourceFetcher && this.props.resourceFetcher.toFetchCount) {
|
||||
resourceFetcherText = _('Fetching resources: %d/%d', this.props.resourceFetcher.fetchingCount, this.props.resourceFetcher.toFetchCount);
|
||||
}
|
||||
|
||||
const lines = Synchronizer.reportToLines(this.props.syncReport);
|
||||
if (resourceFetcherText) lines.push(resourceFetcherText);
|
||||
if (decryptionReportText) lines.push(decryptionReportText);
|
||||
const syncReportText = [];
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
syncReportText.push(
|
||||
<StyledSyncReportText key={i}>
|
||||
{lines[i]}
|
||||
</StyledSyncReportText>
|
||||
);
|
||||
}
|
||||
|
||||
const syncButton = this.renderSynchronizeButton(this.props.syncStarted ? 'cancel' : 'sync');
|
||||
|
||||
const syncReportComp = !syncReportText.length ? null : (
|
||||
<StyledSyncReport key="sync_report">
|
||||
{syncReportText}
|
||||
</StyledSyncReport>
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledRoot ref={this.rootRef} onKeyDown={this.onKeyDown} className="side-bar">
|
||||
<div style={{ flex: 1, overflowX: 'hidden', overflowY: 'auto' }}>{items}</div>
|
||||
<div style={{ flex: 0, padding: theme.mainPadding }}>
|
||||
{syncReportComp}
|
||||
{syncButton}
|
||||
</div>
|
||||
</StyledRoot>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state:any) => {
|
||||
return {
|
||||
folders: state.folders,
|
||||
tags: state.tags,
|
||||
searches: state.searches,
|
||||
syncStarted: state.syncStarted,
|
||||
syncReport: state.syncReport,
|
||||
selectedFolderId: state.selectedFolderId,
|
||||
selectedTagId: state.selectedTagId,
|
||||
selectedSearchId: state.selectedSearchId,
|
||||
selectedSmartFilterId: state.selectedSmartFilterId,
|
||||
notesParentType: state.notesParentType,
|
||||
locale: state.settings.locale,
|
||||
themeId: state.settings.theme,
|
||||
collapsedFolderIds: state.collapsedFolderIds,
|
||||
decryptionWorker: state.decryptionWorker,
|
||||
resourceFetcher: state.resourceFetcher,
|
||||
sidebarVisibility: state.sidebarVisibility,
|
||||
noteListVisibility: state.noteListVisibility,
|
||||
};
|
||||
};
|
||||
|
||||
const SideBar = connect(mapStateToProps)(SideBarComponent);
|
||||
|
||||
module.exports = { SideBar };
|
122
ElectronClient/gui/SideBar/styles/index.ts
Normal file
122
ElectronClient/gui/SideBar/styles/index.ts
Normal file
@ -0,0 +1,122 @@
|
||||
import Button from '../../Button/Button';
|
||||
const styled = require('styled-components').default;
|
||||
|
||||
export const StyledRoot = styled.div`
|
||||
background-color: ${(props:any) => props.theme.backgroundColor2};
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-x: hidden;
|
||||
overflow-y: hidden;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
export const StyledHeader = styled.div`
|
||||
//height: ${(props:any) => props.theme.topRowHeight}px;
|
||||
//text-decoration: none;
|
||||
flex: 1;
|
||||
box-sizing: border-box;
|
||||
padding: ${(props:any) => props.theme.mainPadding}px;
|
||||
padding-bottom: ${(props:any) => props.theme.mainPadding / 2}px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
user-select: none;
|
||||
text-transform: uppercase;
|
||||
//cursor: pointer;
|
||||
`;
|
||||
|
||||
export const StyledHeaderIcon = styled.i`
|
||||
font-size: ${(props:any) => props.theme.toolbarIconSize}px;
|
||||
color: ${(props:any) => props.theme.color2};
|
||||
margin-right: 8px;
|
||||
`;
|
||||
|
||||
export const StyledHeaderLabel = styled.span`
|
||||
flex: 1;
|
||||
color: ${(props:any) => props.theme.color2};
|
||||
font-size: ${(props:any) => Math.round(props.theme.fontSize * 1.1)}px;
|
||||
font-weight: bold;
|
||||
`;
|
||||
|
||||
export const StyledListItem = styled.div`
|
||||
box-sizing: border-box;
|
||||
height: 25px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding-left: ${(props:any) => props.theme.mainPadding + ('depth' in props ? props.depth : 0) * 16}px;
|
||||
background: ${(props:any) => props.selected ? props.theme.selectedColor2 : 'none'};
|
||||
text-transform: ${(props:any) => props.isSpecialItem ? 'uppercase' : 'none'};
|
||||
transition: 0.1s;
|
||||
|
||||
&:hover {
|
||||
background-color: ${(props:any) => props.theme.backgroundColorHover2};
|
||||
}
|
||||
`;
|
||||
|
||||
function listItemTextColor(props:any) {
|
||||
if (props.isConflictFolder) return props.theme.colorError2;
|
||||
if (props.isSpecialItem) return props.theme.colorFaded2;
|
||||
return props.theme.color2;
|
||||
}
|
||||
|
||||
export const StyledListItemAnchor = styled.a`
|
||||
font-size: ${(props:any) => Math.round(props.theme.fontSize * 1.0833333)}px;
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
color: ${(props:any) => listItemTextColor(props)};
|
||||
cursor: default;
|
||||
opacity: ${(props:any) => props.selected ? 1 : 0.8};
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
user-select: none;
|
||||
`;
|
||||
|
||||
export const StyledExpandLink = styled.a`
|
||||
color: ${(props:any) => props.theme.color2};
|
||||
cursor: default;
|
||||
opacity: 0.8;
|
||||
text-decoration: none;
|
||||
padding-right: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 16px;
|
||||
max-width: 16px;
|
||||
min-width: 16px;
|
||||
`;
|
||||
|
||||
export const StyledNoteCount = styled.div`
|
||||
color: ${(props:any) => props.theme.color2};
|
||||
padding-left: 8px;
|
||||
opacity: 0.5;
|
||||
user-select: none;
|
||||
`;
|
||||
|
||||
export const StyledSynchronizeButton = styled(Button)`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const StyledAddButton = styled(Button)`
|
||||
border: none;
|
||||
padding-right: 15px;
|
||||
padding-top: 4px;
|
||||
`;
|
||||
|
||||
export const StyledSyncReport = styled.div`
|
||||
font-size: ${(props:any) => Math.round(props.theme.fontSize * 0.9)}px;
|
||||
color: ${(props:any) => props.theme.color2};
|
||||
opacity: 0.5;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
margin-bottom: 10px;
|
||||
word-wrap: break-word;
|
||||
`;
|
||||
|
||||
export const StyledSyncReportText = styled.div`
|
||||
color: ${(props:any) => props.theme.color2};
|
||||
word-wrap: break-word;
|
||||
width: 100%;
|
||||
`;
|
@ -1,159 +0,0 @@
|
||||
const React = require('react');
|
||||
const { connect } = require('react-redux');
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
const { bridge } = require('electron').remote.require('./bridge');
|
||||
const { Header } = require('./Header/Header.min.js');
|
||||
const { themeStyle } = require('lib/theme');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { ReportService } = require('lib/services/report.js');
|
||||
const fs = require('fs-extra');
|
||||
|
||||
class StatusScreenComponent extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
report: [],
|
||||
};
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
this.resfreshScreen();
|
||||
}
|
||||
|
||||
async resfreshScreen() {
|
||||
const service = new ReportService();
|
||||
const report = await service.status(Setting.value('sync.target'));
|
||||
this.setState({ report: report });
|
||||
}
|
||||
|
||||
async exportDebugReportClick() {
|
||||
const filename = `syncReport-${new Date().getTime()}.csv`;
|
||||
|
||||
const filePath = bridge().showSaveDialog({
|
||||
title: _('Please select where the sync status should be exported to'),
|
||||
defaultPath: filename,
|
||||
});
|
||||
|
||||
if (!filePath) return;
|
||||
|
||||
const service = new ReportService();
|
||||
const csv = await service.basicItemList({ format: 'csv' });
|
||||
await fs.writeFileSync(filePath, csv);
|
||||
}
|
||||
|
||||
render() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const style = this.props.style;
|
||||
|
||||
const headerStyle = Object.assign({}, theme.headerStyle, { width: style.width });
|
||||
const retryStyle = Object.assign({}, theme.urlStyle, { marginLeft: 5 });
|
||||
const retryAllStyle = Object.assign({}, theme.urlStyle, { marginTop: 5, display: 'inline-block' });
|
||||
|
||||
const containerPadding = 10;
|
||||
|
||||
const containerStyle = Object.assign({}, theme.containerStyle, {
|
||||
padding: containerPadding,
|
||||
height: style.height - theme.headerHeight - containerPadding * 2,
|
||||
});
|
||||
|
||||
function renderSectionTitleHtml(key, title) {
|
||||
return (
|
||||
<h2 key={`section_${key}`} style={theme.h2Style}>
|
||||
{title}
|
||||
</h2>
|
||||
);
|
||||
}
|
||||
|
||||
function renderSectionRetryAllHtml(key, retryAllHandler) {
|
||||
return (
|
||||
<a key={`retry_all_${key}`} href="#" onClick={retryAllHandler} style={retryAllStyle}>
|
||||
{_('Retry All')}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
const renderSectionHtml = (key, section) => {
|
||||
const itemsHtml = [];
|
||||
|
||||
itemsHtml.push(renderSectionTitleHtml(section.title, section.title));
|
||||
|
||||
for (const n in section.body) {
|
||||
if (!section.body.hasOwnProperty(n)) continue;
|
||||
const item = section.body[n];
|
||||
let text = '';
|
||||
|
||||
let retryLink = null;
|
||||
if (typeof item === 'object') {
|
||||
if (item.canRetry) {
|
||||
const onClick = async () => {
|
||||
await item.retryHandler();
|
||||
this.resfreshScreen();
|
||||
};
|
||||
|
||||
retryLink = (
|
||||
<a href="#" onClick={onClick} style={retryStyle}>
|
||||
{_('Retry')}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
text = item.text;
|
||||
} else {
|
||||
text = item;
|
||||
}
|
||||
|
||||
if (!text) text = '\xa0';
|
||||
|
||||
itemsHtml.push(
|
||||
<div style={theme.textStyle} key={`item_${n}`}>
|
||||
<span>{text}</span>
|
||||
{retryLink}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (section.canRetryAll) {
|
||||
itemsHtml.push(renderSectionRetryAllHtml(section.title, section.retryAllHandler));
|
||||
}
|
||||
|
||||
return <div key={key}>{itemsHtml}</div>;
|
||||
};
|
||||
|
||||
function renderBodyHtml(report) {
|
||||
const sectionsHtml = [];
|
||||
|
||||
for (let i = 0; i < report.length; i++) {
|
||||
const section = report[i];
|
||||
if (!section.body.length) continue;
|
||||
sectionsHtml.push(renderSectionHtml(i, section));
|
||||
}
|
||||
|
||||
return <div>{sectionsHtml}</div>;
|
||||
}
|
||||
|
||||
const body = renderBodyHtml(this.state.report);
|
||||
|
||||
return (
|
||||
<div style={style}>
|
||||
<Header style={headerStyle} />
|
||||
<div style={containerStyle}>
|
||||
<a style={theme.textStyle} onClick={() => this.exportDebugReportClick()} href="#">
|
||||
Export debug report
|
||||
</a>
|
||||
{body}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
theme: state.settings.theme,
|
||||
settings: state.settings,
|
||||
locale: state.settings.locale,
|
||||
};
|
||||
};
|
||||
|
||||
const StatusScreen = connect(mapStateToProps)(StatusScreenComponent);
|
||||
|
||||
module.exports = { StatusScreen };
|
163
ElectronClient/gui/StatusScreen/StatusScreen.tsx
Normal file
163
ElectronClient/gui/StatusScreen/StatusScreen.tsx
Normal file
@ -0,0 +1,163 @@
|
||||
import * as React from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import ButtonBar from '../ConfigScreen/ButtonBar';
|
||||
|
||||
const { connect } = require('react-redux');
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
const { bridge } = require('electron').remote.require('./bridge');
|
||||
const { themeStyle } = require('lib/theme');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { ReportService } = require('lib/services/report.js');
|
||||
const fs = require('fs-extra');
|
||||
|
||||
interface Props {
|
||||
themeId: string,
|
||||
style: any,
|
||||
dispatch: Function,
|
||||
}
|
||||
|
||||
async function exportDebugReportClick() {
|
||||
const filename = `syncReport-${new Date().getTime()}.csv`;
|
||||
|
||||
const filePath = bridge().showSaveDialog({
|
||||
title: _('Please select where the sync status should be exported to'),
|
||||
defaultPath: filename,
|
||||
});
|
||||
|
||||
if (!filePath) return;
|
||||
|
||||
const service = new ReportService();
|
||||
const csv = await service.basicItemList({ format: 'csv' });
|
||||
await fs.writeFileSync(filePath, csv);
|
||||
}
|
||||
|
||||
function StatusScreen(props:Props) {
|
||||
const [report, setReport] = useState<any[]>([]);
|
||||
|
||||
async function resfreshScreen() {
|
||||
const service = new ReportService();
|
||||
const r = await service.status(Setting.value('sync.target'));
|
||||
setReport(r);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
resfreshScreen();
|
||||
}, []);
|
||||
|
||||
const theme = themeStyle(props.themeId);
|
||||
const style = { ...props.style,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
};
|
||||
|
||||
const retryStyle = Object.assign({}, theme.urlStyle, { marginLeft: 5 });
|
||||
const retryAllStyle = Object.assign({}, theme.urlStyle, { marginTop: 5, display: 'inline-block' });
|
||||
|
||||
const containerPadding = theme.configScreenPadding;
|
||||
|
||||
const containerStyle = Object.assign({}, theme.containerStyle, {
|
||||
padding: containerPadding,
|
||||
flex: 1,
|
||||
});
|
||||
|
||||
function renderSectionTitleHtml(key:string, title:string) {
|
||||
return (
|
||||
<h2 key={`section_${key}`} style={theme.h2Style}>
|
||||
{title}
|
||||
</h2>
|
||||
);
|
||||
}
|
||||
|
||||
function renderSectionRetryAllHtml(key:string, retryAllHandler:any) {
|
||||
return (
|
||||
<a key={`retry_all_${key}`} href="#" onClick={retryAllHandler} style={retryAllStyle}>
|
||||
{_('Retry All')}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
const renderSectionHtml = (key:string, section:any) => {
|
||||
const itemsHtml = [];
|
||||
|
||||
itemsHtml.push(renderSectionTitleHtml(section.title, section.title));
|
||||
|
||||
for (const n in section.body) {
|
||||
if (!section.body.hasOwnProperty(n)) continue;
|
||||
const item = section.body[n];
|
||||
let text = '';
|
||||
|
||||
let retryLink = null;
|
||||
if (typeof item === 'object') {
|
||||
if (item.canRetry) {
|
||||
const onClick = async () => {
|
||||
await item.retryHandler();
|
||||
resfreshScreen();
|
||||
};
|
||||
|
||||
retryLink = (
|
||||
<a href="#" onClick={onClick} style={retryStyle}>
|
||||
{_('Retry')}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
text = item.text;
|
||||
} else {
|
||||
text = item;
|
||||
}
|
||||
|
||||
if (!text) text = '\xa0';
|
||||
|
||||
itemsHtml.push(
|
||||
<div style={theme.textStyle} key={`item_${n}`}>
|
||||
<span>{text}</span>
|
||||
{retryLink}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (section.canRetryAll) {
|
||||
itemsHtml.push(renderSectionRetryAllHtml(section.title, section.retryAllHandler));
|
||||
}
|
||||
|
||||
return <div key={key}>{itemsHtml}</div>;
|
||||
};
|
||||
|
||||
function renderBodyHtml(report:any) {
|
||||
const sectionsHtml = [];
|
||||
|
||||
for (let i = 0; i < report.length; i++) {
|
||||
const section = report[i];
|
||||
if (!section.body.length) continue;
|
||||
sectionsHtml.push(renderSectionHtml(`${i}`, section));
|
||||
}
|
||||
|
||||
return <div>{sectionsHtml}</div>;
|
||||
}
|
||||
|
||||
const body = renderBodyHtml(report);
|
||||
|
||||
return (
|
||||
<div style={style}>
|
||||
<div style={containerStyle}>
|
||||
<a style={theme.textStyle} onClick={() => exportDebugReportClick()} href="#">
|
||||
Export debug report
|
||||
</a>
|
||||
{body}
|
||||
</div>
|
||||
<ButtonBar
|
||||
onCancelClick={() => props.dispatch({ type: 'NAV_BACK' })}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const mapStateToProps = (state:any) => {
|
||||
return {
|
||||
themeId: state.settings.theme,
|
||||
settings: state.settings,
|
||||
locale: state.settings.locale,
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(StatusScreen);
|
||||
|
@ -4,7 +4,7 @@ const { themeStyle } = require('lib/theme');
|
||||
|
||||
class TagItemComponent extends React.Component {
|
||||
render() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
const style = Object.assign({}, theme.tagStyle);
|
||||
const title = this.props.title;
|
||||
|
||||
@ -13,7 +13,7 @@ class TagItemComponent extends React.Component {
|
||||
}
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return { theme: state.settings.theme };
|
||||
return { themeId: state.settings.theme };
|
||||
};
|
||||
|
||||
const TagItem = connect(mapStateToProps)(TagItemComponent);
|
||||
|
@ -6,7 +6,7 @@ const TagItem = require('./TagItem.min.js');
|
||||
class TagListComponent extends React.Component {
|
||||
render() {
|
||||
const style = Object.assign({}, this.props.style);
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
const tags = this.props.items;
|
||||
|
||||
style.display = 'flex';
|
||||
@ -15,11 +15,13 @@ class TagListComponent extends React.Component {
|
||||
style.boxSizing = 'border-box';
|
||||
style.fontSize = theme.fontSize;
|
||||
style.whiteSpace = 'nowrap';
|
||||
style.height = 25;
|
||||
// style.height = 40;
|
||||
style.paddingTop = 8;
|
||||
style.paddingBottom = 8;
|
||||
|
||||
const tagItems = [];
|
||||
if (tags && tags.length > 0) {
|
||||
// Sort by id for now, but probably needs to be changed in the future.
|
||||
|
||||
tags.sort((a, b) => {
|
||||
return a.title < b.title ? -1 : +1;
|
||||
});
|
||||
@ -42,7 +44,7 @@ class TagListComponent extends React.Component {
|
||||
}
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return { theme: state.settings.theme };
|
||||
return { themeId: state.settings.theme };
|
||||
};
|
||||
|
||||
const TagList = connect(mapStateToProps)(TagListComponent);
|
||||
|
@ -0,0 +1,29 @@
|
||||
import * as React from 'react';
|
||||
import styles_ from './styles';
|
||||
import { ToolbarButtonInfo } from 'lib/services/CommandService';
|
||||
|
||||
export enum Value {
|
||||
Markdown = 'markdown',
|
||||
RichText = 'richText',
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
themeId: number,
|
||||
value: Value,
|
||||
toolbarButtonInfo: ToolbarButtonInfo,
|
||||
}
|
||||
|
||||
export default function ToggleEditorsButton(props:Props) {
|
||||
const style = styles_(props);
|
||||
|
||||
return (
|
||||
<button style={style.button} disabled={!props.toolbarButtonInfo.enabled} aria-label={props.toolbarButtonInfo.title} title={props.toolbarButtonInfo.title} type="button" className="tox-tbtn" aria-pressed="false" onClick={props.toolbarButtonInfo.onClick}>
|
||||
<div style={style.leftInnerButton}>
|
||||
<i style={style.leftIcon} className="fab fa-markdown"></i>
|
||||
</div>
|
||||
<div style={style.rightInnerButton}>
|
||||
<i style={style.rightIcon} className="fas fa-edit"></i>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
}
|
68
ElectronClient/gui/ToggleEditorsButton/styles/index.ts
Normal file
68
ElectronClient/gui/ToggleEditorsButton/styles/index.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import { Props, Value } from '../ToggleEditorsButton';
|
||||
const { buildStyle } = require('lib/theme');
|
||||
|
||||
export default function styles(props:Props) {
|
||||
return buildStyle(['ToggleEditorsButton', props.value], props.themeId, (theme: any) => {
|
||||
const iconSize = 15;
|
||||
const mdIconWidth = iconSize * 1.25;
|
||||
const buttonHeight = theme.toolbarHeight - 8;
|
||||
const mdIconPadding = Math.round((buttonHeight - iconSize) / 2) + 3;
|
||||
|
||||
const innerButton:any = {
|
||||
borderStyle: 'solid',
|
||||
borderColor: theme.color3,
|
||||
borderWidth: 1,
|
||||
borderRadius: 0,
|
||||
width: mdIconWidth + mdIconPadding * 2,
|
||||
height: buttonHeight,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
};
|
||||
|
||||
const output:any = {
|
||||
button: {
|
||||
border: 'none',
|
||||
padding: 0,
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
background: 'none',
|
||||
},
|
||||
leftInnerButton: {
|
||||
...innerButton,
|
||||
borderTopLeftRadius: 4,
|
||||
borderBottomLeftRadius: 4,
|
||||
},
|
||||
rightInnerButton: {
|
||||
...innerButton,
|
||||
borderTopRightRadius: 4,
|
||||
borderBottomRightRadius: 4,
|
||||
},
|
||||
leftIcon: {
|
||||
fontSize: iconSize,
|
||||
position: 'relative',
|
||||
top: 1,
|
||||
color: theme.color3,
|
||||
},
|
||||
rightIcon: {
|
||||
fontSize: iconSize - 1,
|
||||
borderLeft: 'none',
|
||||
position: 'relative',
|
||||
top: 1,
|
||||
color: theme.color3,
|
||||
},
|
||||
};
|
||||
|
||||
if (props.value === Value.Markdown) {
|
||||
output.leftInnerButton.backgroundColor = theme.color3;
|
||||
output.leftIcon.color = theme.backgroundColor3;
|
||||
output.rightInnerButton.opacity = 0.5;
|
||||
} else if (props.value === Value.RichText) {
|
||||
output.rightInnerButton.backgroundColor = theme.color3;
|
||||
output.rightIcon.color = theme.backgroundColor3;
|
||||
output.leftInnerButton.opacity = 0.5;
|
||||
}
|
||||
|
||||
return output;
|
||||
});
|
||||
}
|
@ -1,22 +1,32 @@
|
||||
const React = require('react');
|
||||
const { connect } = require('react-redux');
|
||||
const { themeStyle } = require('lib/theme');
|
||||
const ToolbarButton = require('./ToolbarButton.min.js');
|
||||
const ToolbarButton = require('./ToolbarButton/ToolbarButton.js').default;
|
||||
const ToolbarSpace = require('./ToolbarSpace.min.js');
|
||||
const ToggleEditorsButton = require('./ToggleEditorsButton/ToggleEditorsButton.js').default;
|
||||
|
||||
class ToolbarComponent extends React.Component {
|
||||
render() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
const style = Object.assign({
|
||||
// height: theme.toolbarHeight,
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
borderBottom: `1px solid ${theme.dividerColor}`,
|
||||
boxSizing: 'border-box',
|
||||
backgroundColor: theme.backgroundColor3,
|
||||
padding: theme.toolbarPadding,
|
||||
paddingRight: theme.mainPadding,
|
||||
}, this.props.style);
|
||||
|
||||
const itemComps = [];
|
||||
const groupStyle = {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
boxSizing: 'border-box',
|
||||
};
|
||||
|
||||
const leftItemComps = [];
|
||||
const centerItemComps = [];
|
||||
const rightItemComps = [];
|
||||
|
||||
if (this.props.items) {
|
||||
for (let i = 0; i < this.props.items.length; i++) {
|
||||
@ -30,31 +40,47 @@ class ToolbarComponent extends React.Component {
|
||||
const props = Object.assign(
|
||||
{
|
||||
key: key,
|
||||
theme: this.props.theme,
|
||||
themeId: this.props.themeId,
|
||||
},
|
||||
o
|
||||
);
|
||||
|
||||
if (this.props.disabled) props.disabled = true;
|
||||
|
||||
if (itemType === 'button') {
|
||||
itemComps.push(<ToolbarButton {...props} />);
|
||||
if (o.name === 'toggleEditors') {
|
||||
rightItemComps.push(<ToggleEditorsButton
|
||||
key={o.name}
|
||||
value={'markdown'}
|
||||
themeId={this.props.themeId}
|
||||
toolbarButtonInfo={o}
|
||||
/>);
|
||||
} else if (itemType === 'button') {
|
||||
const target = ['historyForward', 'historyBackward', 'startExternalEditing'].includes(o.name) ? leftItemComps : centerItemComps;
|
||||
target.push(<ToolbarButton {...props} />);
|
||||
} else if (itemType === 'separator') {
|
||||
itemComps.push(<ToolbarSpace {...props} />);
|
||||
centerItemComps.push(<ToolbarSpace {...props} />);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="editor-toolbar" style={style}>
|
||||
{itemComps}
|
||||
<div style={groupStyle}>
|
||||
{leftItemComps}
|
||||
</div>
|
||||
<div style={groupStyle}>
|
||||
{centerItemComps}
|
||||
</div>
|
||||
<div style={Object.assign({}, groupStyle, { flex: 1, justifyContent: 'flex-end' })}>
|
||||
{rightItemComps}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return { theme: state.settings.theme };
|
||||
return { themeId: state.settings.theme };
|
||||
};
|
||||
|
||||
const Toolbar = connect(mapStateToProps)(ToolbarComponent);
|
||||
|
@ -1,51 +0,0 @@
|
||||
const React = require('react');
|
||||
const { themeStyle } = require('lib/theme');
|
||||
|
||||
class ToolbarButton extends React.Component {
|
||||
render() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
|
||||
const style = Object.assign({}, theme.toolbarStyle);
|
||||
|
||||
const title = this.props.title ? this.props.title : '';
|
||||
const tooltip = this.props.tooltip ? this.props.tooltip : title;
|
||||
|
||||
let icon = null;
|
||||
if (this.props.iconName) {
|
||||
const iconStyle = {
|
||||
fontSize: Math.round(theme.fontSize * 1.5),
|
||||
color: theme.iconColor,
|
||||
};
|
||||
if (title) iconStyle.marginRight = 5;
|
||||
icon = <i style={iconStyle} className={`fas ${this.props.iconName}`}></i>;
|
||||
}
|
||||
|
||||
// Keep this for legacy compatibility but for consistency we should use "disabled" prop
|
||||
let isEnabled = !('enabled' in this.props) || this.props.enabled === true;
|
||||
if (this.props.disabled) isEnabled = false;
|
||||
|
||||
const classes = ['button'];
|
||||
if (!isEnabled) classes.push('disabled');
|
||||
|
||||
const finalStyle = Object.assign({}, style, {
|
||||
opacity: isEnabled ? 1 : 0.4,
|
||||
});
|
||||
|
||||
return (
|
||||
<a
|
||||
className={classes.join(' ')}
|
||||
style={finalStyle}
|
||||
title={tooltip}
|
||||
href="#"
|
||||
onClick={() => {
|
||||
if (isEnabled && this.props.onClick) this.props.onClick();
|
||||
}}
|
||||
>
|
||||
{icon}
|
||||
{title}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ToolbarButton;
|
63
ElectronClient/gui/ToolbarButton/ToolbarButton.tsx
Normal file
63
ElectronClient/gui/ToolbarButton/ToolbarButton.tsx
Normal file
@ -0,0 +1,63 @@
|
||||
import * as React from 'react';
|
||||
import { ToolbarButtonInfo } from 'lib/services/CommandService';
|
||||
import { StyledRoot, StyledIconSpan, StyledIconI } from './styles';
|
||||
|
||||
interface Props {
|
||||
readonly themeId: number,
|
||||
readonly toolbarButtonInfo?: ToolbarButtonInfo,
|
||||
readonly title?: string,
|
||||
readonly tooltip?: string,
|
||||
readonly iconName?: string,
|
||||
readonly disabled?: boolean,
|
||||
readonly backgroundHover?: boolean,
|
||||
}
|
||||
|
||||
function isFontAwesomeIcon(iconName:string) {
|
||||
const s = iconName.split(' ');
|
||||
return s.length === 2 && ['fa', 'fas'].includes(s[0]);
|
||||
}
|
||||
|
||||
function getProp(props:Props, name:string, defaultValue:any = null) {
|
||||
if (props.toolbarButtonInfo && (name in props.toolbarButtonInfo)) return (props.toolbarButtonInfo as any)[name];
|
||||
if (!(name in props)) return defaultValue;
|
||||
return (props as any)[name];
|
||||
}
|
||||
|
||||
export default function ToolbarButton(props:Props) {
|
||||
const title = getProp(props, 'title', '');
|
||||
const tooltip = getProp(props, 'tooltip', title);
|
||||
|
||||
let icon = null;
|
||||
const iconName = getProp(props, 'iconName');
|
||||
if (iconName) {
|
||||
const IconClass = isFontAwesomeIcon(iconName) ? StyledIconI : StyledIconSpan;
|
||||
icon = <IconClass className={iconName} title={title}/>;
|
||||
}
|
||||
|
||||
// Keep this for legacy compatibility but for consistency we should use "disabled" prop
|
||||
let isEnabled = getProp(props, 'enabled', null);
|
||||
if (isEnabled === null) isEnabled = true;
|
||||
if (props.disabled) isEnabled = false;
|
||||
|
||||
const classes = ['button'];
|
||||
if (!isEnabled) classes.push('disabled');
|
||||
|
||||
const onClick = getProp(props, 'onClick');
|
||||
|
||||
return (
|
||||
<StyledRoot
|
||||
className={classes.join(' ')}
|
||||
disabled={!isEnabled}
|
||||
title={tooltip}
|
||||
href="#"
|
||||
hasTitle={!!title}
|
||||
onClick={() => {
|
||||
if (isEnabled && onClick) onClick();
|
||||
}}
|
||||
>
|
||||
{icon}
|
||||
{title}
|
||||
</StyledRoot>
|
||||
);
|
||||
}
|
||||
|
40
ElectronClient/gui/ToolbarButton/styles/index.ts
Normal file
40
ElectronClient/gui/ToolbarButton/styles/index.ts
Normal file
@ -0,0 +1,40 @@
|
||||
const styled = require('styled-components').default;
|
||||
const { css } = require('styled-components');
|
||||
|
||||
interface RootProps {
|
||||
readonly theme: any;
|
||||
readonly disabled: boolean;
|
||||
readonly hasTitle: boolean;
|
||||
}
|
||||
|
||||
export const StyledRoot = styled.a<RootProps>`
|
||||
opacity: ${(props:RootProps) => props.disabled ? 0.3 : 1};
|
||||
height: ${(props:RootProps) => props.theme.toolbarHeight}px;
|
||||
min-height: ${(props:RootProps) => props.theme.toolbarHeight}px;
|
||||
width: ${(props:RootProps) => props.hasTitle ? 'auto' : `${props.theme.toolbarHeight}px`};
|
||||
max-width: ${(props:RootProps) => props.hasTitle ? 'auto' : `${props.theme.toolbarHeight}px`};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: default;
|
||||
border-radius: 3px;
|
||||
box-sizing: border-box;
|
||||
|
||||
&:hover {
|
||||
background-color: ${(props:RootProps) => props.disabled ? 'none' : props.theme.backgroundColorHover3};
|
||||
}
|
||||
`;
|
||||
|
||||
interface IconProps {
|
||||
readonly theme: any;
|
||||
readonly title: string;
|
||||
}
|
||||
|
||||
const iconStyle = css<IconProps>`
|
||||
font-size: ${(props:IconProps) => props.theme.toolbarIconSize}px;
|
||||
color: ${(props:IconProps) => props.theme.color3};
|
||||
margin-right: ${(props:IconProps) => props.title ? 5 : 0}px;
|
||||
`;
|
||||
|
||||
export const StyledIconI = styled.i`${iconStyle}`;
|
||||
export const StyledIconSpan = styled.span`${iconStyle}`;
|
@ -3,7 +3,7 @@ const { themeStyle } = require('lib/theme');
|
||||
|
||||
class ToolbarSpace extends React.Component {
|
||||
render() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
const style = Object.assign({}, theme.toolbarStyle);
|
||||
style.minWidth = style.height / 2;
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
const { createSelector } = require('reselect');
|
||||
const { themeStyle } = require('lib/theme');
|
||||
|
||||
const themeSelector = (state, props) => themeStyle(props.theme);
|
||||
const themeSelector = (state, props) => themeStyle(props.themeId);
|
||||
|
||||
const style = createSelector(
|
||||
themeSelector,
|
||||
|
@ -1,7 +1,7 @@
|
||||
const { createSelector } = require('reselect');
|
||||
const { themeStyle } = require('lib/theme');
|
||||
|
||||
const themeSelector = (state, props) => themeStyle(props.theme);
|
||||
const themeSelector = (state, props) => themeStyle(props.themeId);
|
||||
|
||||
const style = createSelector(
|
||||
themeSelector,
|
||||
|
25
ElectronClient/gui/style/StyledInput.tsx
Normal file
25
ElectronClient/gui/style/StyledInput.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
const styled = require('styled-components').default;
|
||||
const Color = require('color');
|
||||
|
||||
const StyledInput = styled.input`
|
||||
border: 1px solid ${(props:any) => Color(props.theme.color3).alpha(0.6)};
|
||||
border-radius: 3px;
|
||||
font-size: ${(props:any) => props.theme.fontSize}px;
|
||||
color: ${(props:any) => props.theme.color};
|
||||
padding: 0 8px;
|
||||
height: ${(props:any) => `${props.theme.toolbarHeight}px`};
|
||||
max-height: ${(props:any) => `${props.theme.toolbarHeight}px`};
|
||||
box-sizing: border-box;
|
||||
background-color: ${(props:any) => Color(props.theme.backgroundColor4).alpha(0.5)};
|
||||
|
||||
&::placeholder {
|
||||
color: ${(props:any) => props.theme.colorFaded};
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background-color: ${(props:any) => props.theme.backgroundColor4};
|
||||
border: 1px solid ${(props:any) => props.theme.color3};
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledInput;
|
7
ElectronClient/gui/style/StyledTextInput.tsx
Normal file
7
ElectronClient/gui/style/StyledTextInput.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
const styled = require('styled-components').default;
|
||||
|
||||
const StyledInput = styled.input`
|
||||
|
||||
`;
|
||||
|
||||
export default StyledInput;
|
@ -9,9 +9,12 @@
|
||||
-->
|
||||
<title>Joplin</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<link rel="stylesheet" href="style/icons/style.css">
|
||||
<!-- TODO: Remove once all icons have been swapped -->
|
||||
<link rel="stylesheet" href="node_modules/@fortawesome/fontawesome-free/css/all.min.css">
|
||||
<link rel="stylesheet" href="node_modules/react-datetime/css/react-datetime.css">
|
||||
<link rel="stylesheet" href="node_modules/smalltalk/css/smalltalk.css">
|
||||
<link rel="stylesheet" href="node_modules/roboto-fontface/css/roboto/roboto-fontface.css">
|
||||
<link rel="stylesheet" href="node_modules/codemirror/lib/codemirror.css">
|
||||
|
||||
<style>
|
||||
|
550
ElectronClient/package-lock.json
generated
550
ElectronClient/package-lock.json
generated
@ -28,6 +28,123 @@
|
||||
"integrity": "sha512-uPHXapEmUtlUKTBx4asWMlxtFUWXzEY0KVEgU7QKhgO2LJzzM3kYxM6yOyUZTtYE6mhK4dDn3FDut9SCQWHzgg==",
|
||||
"optional": true
|
||||
},
|
||||
"@babel/code-frame": {
|
||||
"version": "7.10.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
|
||||
"integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==",
|
||||
"requires": {
|
||||
"@babel/highlight": "^7.10.4"
|
||||
}
|
||||
},
|
||||
"@babel/generator": {
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.0.tgz",
|
||||
"integrity": "sha512-fEm3Uzw7Mc9Xi//qU20cBKatTfs2aOtKqmvy/Vm7RkJEGFQ4xc9myCfbXxqK//ZS8MR/ciOHw6meGASJuKmDfQ==",
|
||||
"requires": {
|
||||
"@babel/types": "^7.11.0",
|
||||
"jsesc": "^2.5.1",
|
||||
"source-map": "^0.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/types": {
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.0.tgz",
|
||||
"integrity": "sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA==",
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.10.4",
|
||||
"lodash": "^4.17.19",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"jsesc": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
|
||||
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA=="
|
||||
},
|
||||
"to-fast-properties": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/helper-annotate-as-pure": {
|
||||
"version": "7.10.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz",
|
||||
"integrity": "sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA==",
|
||||
"requires": {
|
||||
"@babel/types": "^7.10.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/types": {
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.0.tgz",
|
||||
"integrity": "sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA==",
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.10.4",
|
||||
"lodash": "^4.17.19",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"to-fast-properties": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/helper-function-name": {
|
||||
"version": "7.10.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz",
|
||||
"integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==",
|
||||
"requires": {
|
||||
"@babel/helper-get-function-arity": "^7.10.4",
|
||||
"@babel/template": "^7.10.4",
|
||||
"@babel/types": "^7.10.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/types": {
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.0.tgz",
|
||||
"integrity": "sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA==",
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.10.4",
|
||||
"lodash": "^4.17.19",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"to-fast-properties": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/helper-get-function-arity": {
|
||||
"version": "7.10.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz",
|
||||
"integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==",
|
||||
"requires": {
|
||||
"@babel/types": "^7.10.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/types": {
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.0.tgz",
|
||||
"integrity": "sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA==",
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.10.4",
|
||||
"lodash": "^4.17.19",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"to-fast-properties": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/helper-module-imports": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz",
|
||||
@ -36,6 +153,84 @@
|
||||
"@babel/types": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"@babel/helper-split-export-declaration": {
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz",
|
||||
"integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==",
|
||||
"requires": {
|
||||
"@babel/types": "^7.11.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/types": {
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.0.tgz",
|
||||
"integrity": "sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA==",
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.10.4",
|
||||
"lodash": "^4.17.19",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"to-fast-properties": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/helper-validator-identifier": {
|
||||
"version": "7.10.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz",
|
||||
"integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw=="
|
||||
},
|
||||
"@babel/highlight": {
|
||||
"version": "7.10.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz",
|
||||
"integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==",
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.10.4",
|
||||
"chalk": "^2.0.0",
|
||||
"js-tokens": "^4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"requires": {
|
||||
"color-convert": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"requires": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
}
|
||||
},
|
||||
"js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"requires": {
|
||||
"has-flag": "^3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.0.tgz",
|
||||
"integrity": "sha512-qvRvi4oI8xii8NllyEc4MDJjuZiNaRzyb7Y7lup1NqJV8TZHF4O27CcP+72WPn/k1zkgJ6WJfnIbk4jTsVAZHw=="
|
||||
},
|
||||
"@babel/runtime": {
|
||||
"version": "7.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.5.tgz",
|
||||
@ -51,6 +246,84 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/template": {
|
||||
"version": "7.10.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz",
|
||||
"integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==",
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.10.4",
|
||||
"@babel/parser": "^7.10.4",
|
||||
"@babel/types": "^7.10.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/types": {
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.0.tgz",
|
||||
"integrity": "sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA==",
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.10.4",
|
||||
"lodash": "^4.17.19",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"to-fast-properties": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/traverse": {
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.0.tgz",
|
||||
"integrity": "sha512-ZB2V+LskoWKNpMq6E5UUCrjtDUh5IOTAyIl0dTjIEoXum/iKWkoIEKIRDnUucO6f+2FzNkE0oD4RLKoPIufDtg==",
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.10.4",
|
||||
"@babel/generator": "^7.11.0",
|
||||
"@babel/helper-function-name": "^7.10.4",
|
||||
"@babel/helper-split-export-declaration": "^7.11.0",
|
||||
"@babel/parser": "^7.11.0",
|
||||
"@babel/types": "^7.11.0",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0",
|
||||
"lodash": "^4.17.19"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/types": {
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.0.tgz",
|
||||
"integrity": "sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA==",
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.10.4",
|
||||
"lodash": "^4.17.19",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"globals": {
|
||||
"version": "11.12.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
|
||||
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"to-fast-properties": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.4.tgz",
|
||||
@ -172,6 +445,21 @@
|
||||
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.6.6.tgz",
|
||||
"integrity": "sha512-ojhgxzUHZ7am3D2jHkMzPpsBAiB005GF5YU4ea+8DNPybMk01JJUM9V9YRlF/GE95tcOm8DxQvWA2jq19bGalQ=="
|
||||
},
|
||||
"@emotion/is-prop-valid": {
|
||||
"version": "0.8.8",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz",
|
||||
"integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==",
|
||||
"requires": {
|
||||
"@emotion/memoize": "0.7.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/memoize": {
|
||||
"version": "0.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz",
|
||||
"integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@emotion/memoize": {
|
||||
"version": "0.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.6.6.tgz",
|
||||
@ -214,6 +502,108 @@
|
||||
"integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@styled-system/background": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@styled-system/background/-/background-5.1.2.tgz",
|
||||
"integrity": "sha512-jtwH2C/U6ssuGSvwTN3ri/IyjdHb8W9X/g8Y0JLcrH02G+BW3OS8kZdHphF1/YyRklnrKrBT2ngwGUK6aqqV3A==",
|
||||
"requires": {
|
||||
"@styled-system/core": "^5.1.2"
|
||||
}
|
||||
},
|
||||
"@styled-system/border": {
|
||||
"version": "5.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@styled-system/border/-/border-5.1.5.tgz",
|
||||
"integrity": "sha512-JvddhNrnhGigtzWRCVuAHepniyVi6hBlimxWDVAdcTuk7aRn9BYJUwfHslURtwYFsF5FoEs8Zmr1oZq2M1AP0A==",
|
||||
"requires": {
|
||||
"@styled-system/core": "^5.1.2"
|
||||
}
|
||||
},
|
||||
"@styled-system/color": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@styled-system/color/-/color-5.1.2.tgz",
|
||||
"integrity": "sha512-1kCkeKDZkt4GYkuFNKc7vJQMcOmTl3bJY3YBUs7fCNM6mMYJeT1pViQ2LwBSBJytj3AB0o4IdLBoepgSgGl5MA==",
|
||||
"requires": {
|
||||
"@styled-system/core": "^5.1.2"
|
||||
}
|
||||
},
|
||||
"@styled-system/core": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@styled-system/core/-/core-5.1.2.tgz",
|
||||
"integrity": "sha512-XclBDdNIy7OPOsN4HBsawG2eiWfCcuFt6gxKn1x4QfMIgeO6TOlA2pZZ5GWZtIhCUqEPTgIBta6JXsGyCkLBYw==",
|
||||
"requires": {
|
||||
"object-assign": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"@styled-system/css": {
|
||||
"version": "5.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@styled-system/css/-/css-5.1.5.tgz",
|
||||
"integrity": "sha512-XkORZdS5kypzcBotAMPBoeckDs9aSZVkvrAlq5K3xP8IMAUek+x2O4NtwoSgkYkWWzVBu6DGdFZLR790QWGG+A=="
|
||||
},
|
||||
"@styled-system/flexbox": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@styled-system/flexbox/-/flexbox-5.1.2.tgz",
|
||||
"integrity": "sha512-6hHV52+eUk654Y1J2v77B8iLeBNtc+SA3R4necsu2VVinSD7+XY5PCCEzBFaWs42dtOEDIa2lMrgL0YBC01mDQ==",
|
||||
"requires": {
|
||||
"@styled-system/core": "^5.1.2"
|
||||
}
|
||||
},
|
||||
"@styled-system/grid": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@styled-system/grid/-/grid-5.1.2.tgz",
|
||||
"integrity": "sha512-K3YiV1KyHHzgdNuNlaw8oW2ktMuGga99o1e/NAfTEi5Zsa7JXxzwEnVSDSBdJC+z6R8WYTCYRQC6bkVFcvdTeg==",
|
||||
"requires": {
|
||||
"@styled-system/core": "^5.1.2"
|
||||
}
|
||||
},
|
||||
"@styled-system/layout": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@styled-system/layout/-/layout-5.1.2.tgz",
|
||||
"integrity": "sha512-wUhkMBqSeacPFhoE9S6UF3fsMEKFv91gF4AdDWp0Aym1yeMPpqz9l9qS/6vjSsDPF7zOb5cOKC3tcKKOMuDCPw==",
|
||||
"requires": {
|
||||
"@styled-system/core": "^5.1.2"
|
||||
}
|
||||
},
|
||||
"@styled-system/position": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@styled-system/position/-/position-5.1.2.tgz",
|
||||
"integrity": "sha512-60IZfMXEOOZe3l1mCu6sj/2NAyUmES2kR9Kzp7s2D3P4qKsZWxD1Se1+wJvevb+1TP+ZMkGPEYYXRyU8M1aF5A==",
|
||||
"requires": {
|
||||
"@styled-system/core": "^5.1.2"
|
||||
}
|
||||
},
|
||||
"@styled-system/shadow": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@styled-system/shadow/-/shadow-5.1.2.tgz",
|
||||
"integrity": "sha512-wqniqYb7XuZM7K7C0d1Euxc4eGtqEe/lvM0WjuAFsQVImiq6KGT7s7is+0bNI8O4Dwg27jyu4Lfqo/oIQXNzAg==",
|
||||
"requires": {
|
||||
"@styled-system/core": "^5.1.2"
|
||||
}
|
||||
},
|
||||
"@styled-system/space": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@styled-system/space/-/space-5.1.2.tgz",
|
||||
"integrity": "sha512-+zzYpR8uvfhcAbaPXhH8QgDAV//flxqxSjHiS9cDFQQUSznXMQmxJegbhcdEF7/eNnJgHeIXv1jmny78kipgBA==",
|
||||
"requires": {
|
||||
"@styled-system/core": "^5.1.2"
|
||||
}
|
||||
},
|
||||
"@styled-system/typography": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@styled-system/typography/-/typography-5.1.2.tgz",
|
||||
"integrity": "sha512-BxbVUnN8N7hJ4aaPOd7wEsudeT7CxarR+2hns8XCX1zp0DFfbWw4xYa/olA0oQaqx7F1hzDg+eRaGzAJbF+jOg==",
|
||||
"requires": {
|
||||
"@styled-system/core": "^5.1.2"
|
||||
}
|
||||
},
|
||||
"@styled-system/variant": {
|
||||
"version": "5.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@styled-system/variant/-/variant-5.1.5.tgz",
|
||||
"integrity": "sha512-Yn8hXAFoWIro8+Q5J8YJd/mP85Teiut3fsGVR9CAxwgNfIAiqlYxsk5iHU7VHJks/0KjL4ATSjmbtCDC/4l1qw==",
|
||||
"requires": {
|
||||
"@styled-system/core": "^5.1.2",
|
||||
"@styled-system/css": "^5.1.5"
|
||||
}
|
||||
},
|
||||
"@szmarczak/http-timer": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz",
|
||||
@ -1145,7 +1535,8 @@
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
@ -1310,7 +1701,8 @@
|
||||
"dependencies": {
|
||||
"minimist": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
}
|
||||
@ -1425,7 +1817,8 @@
|
||||
},
|
||||
"tar": {
|
||||
"version": "4.4.8",
|
||||
"resolved": "",
|
||||
"resolved": false,
|
||||
"integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
@ -1652,6 +2045,17 @@
|
||||
"resolve": "^1.10.0"
|
||||
}
|
||||
},
|
||||
"babel-plugin-styled-components": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-1.11.1.tgz",
|
||||
"integrity": "sha512-YwrInHyKUk1PU3avIRdiLyCpM++18Rs1NgyMXEAQC33rIXs/vro0A+stf4sT0Gf22Got+xRWB8Cm0tw+qkRzBA==",
|
||||
"requires": {
|
||||
"@babel/helper-annotate-as-pure": "^7.0.0",
|
||||
"@babel/helper-module-imports": "^7.0.0",
|
||||
"babel-plugin-syntax-jsx": "^6.18.0",
|
||||
"lodash": "^4.17.11"
|
||||
}
|
||||
},
|
||||
"babel-plugin-syntax-flow": {
|
||||
"version": "6.18.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz",
|
||||
@ -2432,6 +2836,11 @@
|
||||
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
|
||||
"dev": true
|
||||
},
|
||||
"camelize": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz",
|
||||
"integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs="
|
||||
},
|
||||
"caseless": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
|
||||
@ -3123,6 +3532,21 @@
|
||||
"resolved": "https://registry.npmjs.org/css-b64-images/-/css-b64-images-0.2.5.tgz",
|
||||
"integrity": "sha1-QgBdgyBLK0pdk7axpWRBM7WSegI="
|
||||
},
|
||||
"css-color-keywords": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
|
||||
"integrity": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU="
|
||||
},
|
||||
"css-to-react-native": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.0.0.tgz",
|
||||
"integrity": "sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==",
|
||||
"requires": {
|
||||
"camelize": "^1.0.0",
|
||||
"css-color-keywords": "^1.0.0",
|
||||
"postcss-value-parser": "^4.0.2"
|
||||
}
|
||||
},
|
||||
"cssom": {
|
||||
"version": "0.4.4",
|
||||
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz",
|
||||
@ -3465,6 +3889,11 @@
|
||||
"whatwg-url": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"debounce": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.0.tgz",
|
||||
"integrity": "sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg=="
|
||||
},
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
@ -4312,8 +4741,7 @@
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
|
||||
"dev": true
|
||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
|
||||
},
|
||||
"escaper": {
|
||||
"version": "2.5.3",
|
||||
@ -4652,6 +5080,11 @@
|
||||
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
|
||||
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc="
|
||||
},
|
||||
"fast-memoize": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-memoize/-/fast-memoize-2.5.2.tgz",
|
||||
"integrity": "sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw=="
|
||||
},
|
||||
"fbjs": {
|
||||
"version": "0.8.16",
|
||||
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz",
|
||||
@ -5440,7 +5873,9 @@
|
||||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
|
||||
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"minipass": {
|
||||
"version": "2.9.0",
|
||||
@ -5464,9 +5899,14 @@
|
||||
}
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.5",
|
||||
"version": "0.5.1",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"minimist": "0.0.8"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
@ -5636,7 +6076,8 @@
|
||||
"dependencies": {
|
||||
"minimist": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "",
|
||||
"resolved": false,
|
||||
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
}
|
||||
@ -6194,8 +6635,7 @@
|
||||
"has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
|
||||
"dev": true
|
||||
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
|
||||
},
|
||||
"has-symbols": {
|
||||
"version": "1.0.1",
|
||||
@ -9021,6 +9461,11 @@
|
||||
"integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=",
|
||||
"dev": true
|
||||
},
|
||||
"postcss-value-parser": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz",
|
||||
"integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ=="
|
||||
},
|
||||
"prebuild-install": {
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.3.tgz",
|
||||
@ -9260,6 +9705,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"re-resizable": {
|
||||
"version": "6.5.4",
|
||||
"resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-6.5.4.tgz",
|
||||
"integrity": "sha512-7T3L1lexB2zkZIDmzRJbwdq+xGFuRkrEVQIf5hBPnh7JuS9kG9Yc8XgIaxTWic1kU7jVlDgqzfId/gvmpBCjpA==",
|
||||
"requires": {
|
||||
"fast-memoize": "^2.5.1"
|
||||
}
|
||||
},
|
||||
"react": {
|
||||
"version": "16.9.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-16.9.0.tgz",
|
||||
@ -9906,6 +10359,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"roboto-fontface": {
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/roboto-fontface/-/roboto-fontface-0.10.0.tgz",
|
||||
"integrity": "sha512-OlwfYEgA2RdboZohpldlvJ1xngOins5d7ejqnIBWr9KaMxsnBqotpptRXTyfNRLnFpqzX6sTDt+X+a+6udnU8g=="
|
||||
},
|
||||
"rw": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
|
||||
@ -10078,6 +10536,11 @@
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
|
||||
"integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ=="
|
||||
},
|
||||
"shallowequal": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
|
||||
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
|
||||
},
|
||||
"shebang-command": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
|
||||
@ -10559,6 +11022,71 @@
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
|
||||
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo="
|
||||
},
|
||||
"styled-components": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.1.1.tgz",
|
||||
"integrity": "sha512-1ps8ZAYu2Husx+Vz8D+MvXwEwvMwFv+hqqUwhNlDN5ybg6A+3xyW1ECrAgywhvXapNfXiz79jJyU0x22z0FFTg==",
|
||||
"requires": {
|
||||
"@babel/helper-module-imports": "^7.0.0",
|
||||
"@babel/traverse": "^7.4.5",
|
||||
"@emotion/is-prop-valid": "^0.8.8",
|
||||
"@emotion/stylis": "^0.8.4",
|
||||
"@emotion/unitless": "^0.7.4",
|
||||
"babel-plugin-styled-components": ">= 1",
|
||||
"css-to-react-native": "^3.0.0",
|
||||
"hoist-non-react-statics": "^3.0.0",
|
||||
"shallowequal": "^1.1.0",
|
||||
"supports-color": "^5.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/stylis": {
|
||||
"version": "0.8.5",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz",
|
||||
"integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ=="
|
||||
},
|
||||
"@emotion/unitless": {
|
||||
"version": "0.7.5",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
|
||||
"integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="
|
||||
},
|
||||
"hoist-non-react-statics": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
|
||||
"requires": {
|
||||
"react-is": "^16.7.0"
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"requires": {
|
||||
"has-flag": "^3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"styled-system": {
|
||||
"version": "5.1.5",
|
||||
"resolved": "https://registry.npmjs.org/styled-system/-/styled-system-5.1.5.tgz",
|
||||
"integrity": "sha512-7VoD0o2R3RKzOzPK0jYrVnS8iJdfkKsQJNiLRDjikOpQVqQHns/DXWaPZOH4tIKkhAT7I6wIsy9FWTWh2X3q+A==",
|
||||
"requires": {
|
||||
"@styled-system/background": "^5.1.2",
|
||||
"@styled-system/border": "^5.1.5",
|
||||
"@styled-system/color": "^5.1.2",
|
||||
"@styled-system/core": "^5.1.2",
|
||||
"@styled-system/flexbox": "^5.1.2",
|
||||
"@styled-system/grid": "^5.1.2",
|
||||
"@styled-system/layout": "^5.1.2",
|
||||
"@styled-system/position": "^5.1.2",
|
||||
"@styled-system/shadow": "^5.1.2",
|
||||
"@styled-system/space": "^5.1.2",
|
||||
"@styled-system/typography": "^5.1.2",
|
||||
"@styled-system/variant": "^5.1.5",
|
||||
"object-assign": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"stylis": {
|
||||
"version": "3.5.4",
|
||||
"resolved": "https://registry.npmjs.org/stylis/-/stylis-3.5.4.tgz",
|
||||
|
@ -125,6 +125,7 @@
|
||||
"color": "^3.1.2",
|
||||
"compare-versions": "^3.2.1",
|
||||
"countable": "^3.0.1",
|
||||
"debounce": "^1.2.0",
|
||||
"diacritics": "^1.3.0",
|
||||
"diff-match-patch": "^1.0.4",
|
||||
"electron-context-menu": "^0.15.0",
|
||||
@ -177,6 +178,7 @@
|
||||
"pretty-bytes": "^5.3.0",
|
||||
"promise": "^8.0.1",
|
||||
"query-string": "^5.1.1",
|
||||
"re-resizable": "^6.5.4",
|
||||
"react": "^16.9.0",
|
||||
"react-datetime": "^2.14.0",
|
||||
"react-dom": "^16.9.0",
|
||||
@ -188,6 +190,7 @@
|
||||
"redux": "^3.7.2",
|
||||
"relative": "^3.0.2",
|
||||
"reselect": "^4.0.0",
|
||||
"roboto-fontface": "^0.10.0",
|
||||
"sax": "^1.2.4",
|
||||
"server-destroy": "^1.0.1",
|
||||
"smalltalk": "^2.5.1",
|
||||
@ -195,6 +198,8 @@
|
||||
"sqlite3": "^4.1.1",
|
||||
"string-padding": "^1.0.2",
|
||||
"string-to-stream": "^1.1.1",
|
||||
"styled-components": "^5.1.1",
|
||||
"styled-system": "^5.1.5",
|
||||
"syswide-cas": "^5.1.0",
|
||||
"taboverride": "^4.0.3",
|
||||
"tar": "^4.4.4",
|
||||
|
@ -33,6 +33,8 @@ class Dialog extends React.PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.fuzzy_ = false;
|
||||
|
||||
this.state = {
|
||||
query: '',
|
||||
results: [],
|
||||
@ -58,11 +60,11 @@ class Dialog extends React.PureComponent {
|
||||
}
|
||||
|
||||
style() {
|
||||
const styleKey = [this.props.theme, this.state.resultsInBody ? '1' : '0'].join('-');
|
||||
const styleKey = [this.props.themeId, this.state.resultsInBody ? '1' : '0'].join('-');
|
||||
|
||||
if (this.styles_[styleKey]) return this.styles_[styleKey];
|
||||
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
const itemHeight = this.state.resultsInBody ? 84 : 64;
|
||||
|
||||
@ -178,7 +180,7 @@ class Dialog extends React.PureComponent {
|
||||
}
|
||||
|
||||
async keywords(searchQuery) {
|
||||
const parsedQuery = await SearchEngine.instance().parseQuery(searchQuery, false);
|
||||
const parsedQuery = await SearchEngine.instance().parseQuery(searchQuery, this.fuzzy_);
|
||||
return SearchEngine.instance().allParsedQueryTerms(parsedQuery);
|
||||
}
|
||||
|
||||
@ -215,7 +217,7 @@ class Dialog extends React.PureComponent {
|
||||
} else { // Note TITLE or BODY
|
||||
listType = BaseModel.TYPE_NOTE;
|
||||
searchQuery = this.makeSearchQuery(this.state.query);
|
||||
results = await SearchEngine.instance().search(searchQuery);
|
||||
results = await SearchEngine.instance().search(searchQuery, { fuzzy: this.fuzzy_ });
|
||||
|
||||
resultsInBody = !!results.find(row => row.fields.includes('body'));
|
||||
|
||||
@ -341,7 +343,7 @@ class Dialog extends React.PureComponent {
|
||||
}
|
||||
|
||||
listItemRenderer(item) {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
const style = this.style();
|
||||
const rowStyle = item.id === this.state.selectedItemId ? style.rowSelected : style.row;
|
||||
const titleHtml = item.fragments
|
||||
@ -430,7 +432,7 @@ class Dialog extends React.PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
const style = this.style();
|
||||
const helpComp = !this.state.showHelp ? null : <div style={style.help}>{_('Type a note title or part of its content to jump to it. Or type # followed by a tag name, or @ followed by a notebook name.')}</div>;
|
||||
|
||||
@ -453,7 +455,7 @@ class Dialog extends React.PureComponent {
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
folders: state.folders,
|
||||
theme: state.settings.theme,
|
||||
themeId: state.settings.theme,
|
||||
showCompletedTodos: state.settings.showCompletedTodos,
|
||||
highlightedWords: state.highlightedWords,
|
||||
};
|
||||
|
@ -38,18 +38,18 @@ a {
|
||||
::-webkit-scrollbar-track {
|
||||
border: none;
|
||||
}
|
||||
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(100, 100, 100, 0.3);
|
||||
background: rgba(100, 100, 100, 0.3);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track:hover {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(100, 100, 100, 0.7);
|
||||
background: rgba(100, 100, 100, 0.7);
|
||||
}
|
||||
|
||||
.fade_out {
|
||||
@ -64,20 +64,13 @@ a {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/*
|
||||
.note-list .list-item-container:hover {
|
||||
background-color: rgba(0,160,255,0.1) !important;
|
||||
}
|
||||
*/
|
||||
|
||||
/*.side-bar .list-item:hover,
|
||||
.side-bar .synchronize-button:hover {
|
||||
background-color: #01427B;
|
||||
}
|
||||
|
||||
.side-bar .list-item:active,
|
||||
.side-bar .synchronize-button:active {
|
||||
background-color: #0465BB;
|
||||
}*/
|
||||
|
||||
/*
|
||||
.editor-toolbar .button:not(.disabled):hover,
|
||||
.header .button:not(.disabled):hover {
|
||||
background-color: rgba(0,160,255,0.1);
|
||||
@ -91,6 +84,7 @@ a {
|
||||
border: 1px solid rgba(0,160,255,0.7);
|
||||
box-sizing: 'border-box';
|
||||
}
|
||||
*/
|
||||
|
||||
.editor-toolbar .button,
|
||||
.header .button {
|
||||
@ -163,3 +157,12 @@ a {
|
||||
from {transform: rotate(0deg);}
|
||||
to {transform: rotate(360deg);}
|
||||
}
|
||||
|
||||
/* .joplin-tinymce .tox-editor-header {
|
||||
padding-left: 88px;
|
||||
padding-right: 150px;
|
||||
} */
|
||||
|
||||
*:focus {
|
||||
outline: none;
|
||||
}
|
BIN
ElectronClient/style/icons/fonts/icomoon.eot
Executable file
BIN
ElectronClient/style/icons/fonts/icomoon.eot
Executable file
Binary file not shown.
55
ElectronClient/style/icons/fonts/icomoon.svg
Executable file
55
ElectronClient/style/icons/fonts/icomoon.svg
Executable file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 41 KiB |
BIN
ElectronClient/style/icons/fonts/icomoon.ttf
Executable file
BIN
ElectronClient/style/icons/fonts/icomoon.ttf
Executable file
Binary file not shown.
BIN
ElectronClient/style/icons/fonts/icomoon.woff
Executable file
BIN
ElectronClient/style/icons/fonts/icomoon.woff
Executable file
Binary file not shown.
162
ElectronClient/style/icons/style.css
Executable file
162
ElectronClient/style/icons/style.css
Executable file
@ -0,0 +1,162 @@
|
||||
@font-face {
|
||||
font-family: 'icomoon';
|
||||
src: url('fonts/icomoon.eot?f410v3');
|
||||
src: url('fonts/icomoon.eot?f410v3#iefix') format('embedded-opentype'),
|
||||
url('fonts/icomoon.ttf?f410v3') format('truetype'),
|
||||
url('fonts/icomoon.woff?f410v3') format('woff'),
|
||||
url('fonts/icomoon.svg?f410v3#icomoon') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
}
|
||||
|
||||
[class^="icon-"], [class*=" icon-"] {
|
||||
/* use !important to prevent issues with browser extensions that change fonts */
|
||||
font-family: 'icomoon' !important;
|
||||
speak: never;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
line-height: 1;
|
||||
|
||||
/* Better Font Rendering =========== */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-error:before {
|
||||
content: "\e900";
|
||||
}
|
||||
.icon-attention:before {
|
||||
content: "\e901";
|
||||
}
|
||||
.icon-collapse:before {
|
||||
content: "\e902";
|
||||
}
|
||||
.icon-cancel:before {
|
||||
content: "\e903";
|
||||
}
|
||||
.icon-expand:before {
|
||||
content: "\e904";
|
||||
}
|
||||
.icon-notebooks:before {
|
||||
content: "\e905";
|
||||
}
|
||||
.icon-plus:before {
|
||||
content: "\e906";
|
||||
}
|
||||
.icon-update:before {
|
||||
content: "\e907";
|
||||
}
|
||||
.icon-tags:before {
|
||||
content: "\e908";
|
||||
}
|
||||
.icon-search:before {
|
||||
content: "\e909";
|
||||
}
|
||||
.icon-notes:before {
|
||||
content: "\e90a";
|
||||
}
|
||||
.icon-sync:before {
|
||||
content: "\e90b";
|
||||
}
|
||||
.icon-general:before {
|
||||
content: "\e90c";
|
||||
}
|
||||
.icon-note:before {
|
||||
content: "\e90d";
|
||||
}
|
||||
.icon-note-history:before {
|
||||
content: "\e90e";
|
||||
}
|
||||
.icon-application:before {
|
||||
content: "\e90f";
|
||||
}
|
||||
.icon-encryption:before {
|
||||
content: "\e910";
|
||||
}
|
||||
.icon-plugins:before {
|
||||
content: "\e911";
|
||||
}
|
||||
.icon-web-clipper:before {
|
||||
content: "\e912";
|
||||
}
|
||||
.icon-appearance:before {
|
||||
content: "\e913";
|
||||
}
|
||||
.icon-code:before {
|
||||
content: "\e914";
|
||||
}
|
||||
.icon-asterisks:before {
|
||||
content: "\e915";
|
||||
}
|
||||
.icon-bold:before {
|
||||
content: "\e916";
|
||||
}
|
||||
.icon-bulleted-list:before {
|
||||
content: "\e917";
|
||||
}
|
||||
.icon-embed-code:before {
|
||||
content: "\e918";
|
||||
}
|
||||
.icon-back:before {
|
||||
content: "\e919";
|
||||
}
|
||||
.icon-forward:before {
|
||||
content: "\e91a";
|
||||
}
|
||||
.icon-h1:before {
|
||||
content: "\e91b";
|
||||
}
|
||||
.icon-h2:before {
|
||||
content: "\e91c";
|
||||
}
|
||||
.icon-h3:before {
|
||||
content: "\e91d";
|
||||
}
|
||||
.icon-heading:before {
|
||||
content: "\e91e";
|
||||
}
|
||||
.icon-info:before {
|
||||
content: "\e91f";
|
||||
}
|
||||
.icon-italic:before {
|
||||
content: "\e920";
|
||||
}
|
||||
.icon-layout:before {
|
||||
content: "\e921";
|
||||
}
|
||||
.icon-line:before {
|
||||
content: "\e922";
|
||||
}
|
||||
.icon-link:before {
|
||||
content: "\e923";
|
||||
}
|
||||
.icon-more:before {
|
||||
content: "\e924";
|
||||
}
|
||||
.icon-numbered-list:before {
|
||||
content: "\e925";
|
||||
}
|
||||
.icon-quote:before {
|
||||
content: "\e926";
|
||||
}
|
||||
.icon-alarm:before {
|
||||
content: "\e927";
|
||||
}
|
||||
.icon-share:before {
|
||||
content: "\e928";
|
||||
}
|
||||
.icon-table:before {
|
||||
content: "\e929";
|
||||
}
|
||||
.icon-to-do-list:before {
|
||||
content: "\e92a";
|
||||
}
|
||||
.icon-add-date:before {
|
||||
content: "\e92b";
|
||||
}
|
||||
.icon-attachment:before {
|
||||
content: "\e92c";
|
||||
}
|
@ -43,9 +43,7 @@ function convertJsx(path) {
|
||||
|
||||
module.exports = function() {
|
||||
convertJsx(`${__dirname}/../gui`);
|
||||
convertJsx(`${__dirname}/../gui/SideBar`);
|
||||
convertJsx(`${__dirname}/../gui/MainScreen`);
|
||||
convertJsx(`${__dirname}/../gui/Header`);
|
||||
convertJsx(`${__dirname}/../gui/NoteList`);
|
||||
convertJsx(`${__dirname}/../plugins`);
|
||||
|
||||
|
@ -292,8 +292,8 @@ class BaseApplication {
|
||||
notes = await Tag.notes(parentId, options);
|
||||
} else if (parentType === BaseModel.TYPE_SEARCH) {
|
||||
const search = BaseModel.byId(state.searches, parentId);
|
||||
notes = await SearchEngineUtils.notesForQuery(search.query_pattern, { fuzzy: search.fuzzy });
|
||||
const parsedQuery = await SearchEngine.instance().parseQuery(search.query_pattern, search.fuzzy);
|
||||
notes = await SearchEngineUtils.notesForQuery(search.query_pattern);
|
||||
const parsedQuery = await SearchEngine.instance().parseQuery(search.query_pattern);
|
||||
highlightedWords = SearchEngine.instance().allParsedQueryTerms(parsedQuery);
|
||||
} else if (parentType === BaseModel.TYPE_SMART_FILTER) {
|
||||
notes = await Note.previews(parentId, options);
|
||||
|
@ -4,7 +4,8 @@ const { _ } = require('lib/locale');
|
||||
export const declaration:CommandDeclaration = {
|
||||
name: 'historyBackward',
|
||||
label: () => _('Back'),
|
||||
iconName: 'fa-arrow-left',
|
||||
// iconName: 'fa-arrow-left',
|
||||
iconName: 'icon-back',
|
||||
};
|
||||
|
||||
interface Props {
|
||||
|
@ -4,7 +4,7 @@ const { _ } = require('lib/locale');
|
||||
export const declaration:CommandDeclaration = {
|
||||
name: 'historyForward',
|
||||
label: () => _('Forward'),
|
||||
iconName: 'fa-arrow-right',
|
||||
iconName: 'icon-forward',
|
||||
};
|
||||
|
||||
interface Props {
|
||||
|
@ -10,7 +10,7 @@ class ModalDialog extends React.Component {
|
||||
}
|
||||
|
||||
styles() {
|
||||
const themeId = this.props.theme;
|
||||
const themeId = this.props.themeId;
|
||||
const theme = themeStyle(themeId);
|
||||
|
||||
if (this.styles_[themeId]) return this.styles_[themeId];
|
||||
|
@ -63,7 +63,7 @@ class AppNavComponent extends Component {
|
||||
|
||||
this.previousRouteName_ = route.routeName;
|
||||
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
const style = { flex: 1, backgroundColor: theme.backgroundColor };
|
||||
|
||||
@ -81,7 +81,7 @@ class AppNavComponent extends Component {
|
||||
const AppNav = connect(state => {
|
||||
return {
|
||||
route: state.route,
|
||||
theme: state.settings.theme,
|
||||
themeId: state.settings.theme,
|
||||
};
|
||||
})(AppNavComponent);
|
||||
|
||||
|
@ -44,7 +44,7 @@ class NoteBodyViewer extends Component {
|
||||
this.forceUpdate_ = false;
|
||||
|
||||
const note = this.props.note;
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
const bodyToRender = note ? note.body : '';
|
||||
|
||||
|
@ -22,9 +22,9 @@ class NoteItemComponent extends Component {
|
||||
}
|
||||
|
||||
styles() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
if (this.styles_[this.props.theme]) return this.styles_[this.props.theme];
|
||||
if (this.styles_[this.props.themeId]) return this.styles_[this.props.themeId];
|
||||
this.styles_ = {};
|
||||
|
||||
const styles = {
|
||||
@ -62,8 +62,8 @@ class NoteItemComponent extends Component {
|
||||
styles.selectionWrapperSelected = Object.assign({}, styles.selectionWrapper);
|
||||
styles.selectionWrapperSelected.backgroundColor = theme.selectedColor;
|
||||
|
||||
this.styles_[this.props.theme] = StyleSheet.create(styles);
|
||||
return this.styles_[this.props.theme];
|
||||
this.styles_[this.props.themeId] = StyleSheet.create(styles);
|
||||
return this.styles_[this.props.themeId];
|
||||
}
|
||||
|
||||
async todoCheckbox_change(checked) {
|
||||
@ -107,7 +107,7 @@ class NoteItemComponent extends Component {
|
||||
const note = this.props.note ? this.props.note : {};
|
||||
const isTodo = !!Number(note.is_todo);
|
||||
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const theme = themeStyle(this.props.themeId);
|
||||
|
||||
// IOS: display: none crashes the app
|
||||
const checkboxStyle = !isTodo ? { display: 'none' } : { color: theme.color };
|
||||
@ -145,7 +145,7 @@ class NoteItemComponent extends Component {
|
||||
|
||||
const NoteItem = connect(state => {
|
||||
return {
|
||||
theme: state.settings.theme,
|
||||
themeId: state.settings.theme,
|
||||
noteSelectionEnabled: state.noteSelectionEnabled,
|
||||
selectedNoteIds: state.selectedNoteIds,
|
||||
};
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user