Error!
Please click button
diff --git a/src/mobile/components/Navigator/ControlButton.tsx b/src/mobile/components/Navigator/ControlButton.tsx
deleted file mode 100644
index 6ca3740aef..0000000000
--- a/src/mobile/components/Navigator/ControlButton.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import React from 'react'
-import styled from '../../../lib/styled'
-
-const StyledButton = styled.button`
- position: relative;
- right: 9px;
- width: 30px;
- padding: 0;
- border: none;
- background-color: transparent;
- border-radius: 2px;
- font-size: 20px;
- line-height: 20px;
- cursor: pointer;
- vertical-align: middle;
- color: ${({ theme }) => theme.sideNavLabelColor};
- &:hover,
- &:active,
- &:focus {
- box-shadow: none;
- }
- + button {
- top: -1px;
- }
-`
-
-interface ControlButtonProps {
- icon: React.ReactNode
- onClick?: (event: React.MouseEvent) => void
-}
-
-const ControlButton = ({ icon, onClick }: ControlButtonProps) => {
- return
{icon}
-}
-
-export default ControlButton
diff --git a/src/mobile/components/Navigator/Navigator.tsx b/src/mobile/components/Navigator/Navigator.tsx
deleted file mode 100644
index e18dfc9497..0000000000
--- a/src/mobile/components/Navigator/Navigator.tsx
+++ /dev/null
@@ -1,423 +0,0 @@
-import React, { useMemo, useCallback } from 'react'
-import { useRouter, usePathnameWithoutNoteId } from '../../lib/router'
-import { useDb } from '../../lib/db'
-import { entries } from '../../../lib/db/utils'
-import styled from '../../../lib/styled'
-import { useDialog, DialogIconTypes } from '../../../lib/dialog'
-import { useContextMenu, MenuTypes } from '../../../lib/contextMenu'
-import { usePreferences } from '../../../lib/preferences'
-import NavigatorItem from './NavigatorItem'
-import { useGeneralStatus } from '../../lib/generalStatus'
-import ControlButton from './ControlButton'
-import FolderListFragment from './FolderListFragment'
-import TagListFragment from './TagListFragment'
-import { useUsers } from '../../../lib/accounts'
-import { useToast } from '../../../lib/toast'
-import { useTranslation } from 'react-i18next'
-import {
- IconAddRound,
- IconAdjustVertical,
- IconArrowAgain,
- IconTrash,
- IconSetting,
- IconBook,
-} from '../../../components/icons'
-import Icon from '../../../components/atoms/Icon'
-import { mdiClose } from '@mdi/js'
-
-const Description = styled.nav`
- margin-left: 15px;
- margin-bottom: 10px;
- font-size: 18px;
-`
-
-const StyledSideNavContainer = styled.nav`
- display: flex;
- background-color: ${({ theme }) => theme.sideNavBackgroundColor};
- flex-direction: column;
- height: 100%;
- .topControl {
- height: 50px;
- display: flex;
- -webkit-app-region: drag;
- .spacer {
- flex: 1;
- }
- .button {
- width: 50px;
- height: 50px;
- background-color: transparent;
- border: none;
- cursor: pointer;
- font-size: 24px;
-
- transition: color 200ms ease-in-out;
- color: ${({ theme }) => theme.sideNavButtonColor};
- &:hover {
- color: ${({ theme }) => theme.sideNavButtonHoverColor};
- }
-
- &:active,
- .active {
- color: ${({ theme }) => theme.sideNavButtonActiveColor};
- }
- }
- }
-
- .storageList {
- list-style: none;
- padding: 0;
- margin: 0;
- flex: 1;
- overflow: auto;
- display: flex;
- flex-direction: column;
- }
-
- .empty {
- padding: 4px;
- padding-left: 26px;
- margin-bottom: 4px;
- color: ${({ theme }) => theme.sideNavLabelColor};
- user-select: none;
- }
-
- .bottomControl {
- height: 35px;
- display: flex;
- border-top: 1px solid ${({ theme }) => theme.colors.border};
- button {
- height: 35px;
- border: none;
- background-color: transparent;
- display: flex;
- align-items: center;
- }
- .addFolderButton {
- flex: 1;
- border-right: 1px solid ${({ theme }) => theme.colors.border};
- }
- .addFolderButtonIcon {
- margin-right: 4px;
- }
- .moreButton {
- width: 30px;
- display: flex;
- justify-content: center;
- }
- }
-`
-
-const CreateStorageButton = styled.button`
- position: absolute;
- right: 8px;
- border: none;
- background-color: transparent;
- cursor: pointer;
- transition: color 200ms ease-in-out;
- color: ${({ theme }) => theme.sideNavButtonColor};
- &:hover {
- color: ${({ theme }) => theme.sideNavButtonHoverColor};
- }
-
- &:active,
- .active {
- color: ${({ theme }) => theme.sideNavButtonActiveColor};
- }
-`
-
-const Spacer = styled.div`
- flex: 1;
-`
-
-interface NavigatorProps {
- toggle: () => void
-}
-
-export default ({ toggle }: NavigatorProps) => {
- const {
- createStorage,
- createFolder,
- renameFolder,
- renameStorage,
- removeStorage,
- storageMap,
- syncStorage,
- } = useDb()
- const { popup } = useContextMenu()
- const { prompt, messageBox } = useDialog()
- const { push } = useRouter()
- const [[user]] = useUsers()
- const { pushMessage } = useToast()
-
- const storageEntries = useMemo(() => {
- return entries(storageMap)
- }, [storageMap])
-
- const openSideNavContextMenu = useCallback(
- (event: React.MouseEvent) => {
- event.preventDefault()
- popup(event, [
- {
- type: MenuTypes.Normal,
- label: 'New Storage',
- onClick: async () => {
- prompt({
- title: 'Create a Storage',
- message: 'Enter name of a storage to create',
- iconType: DialogIconTypes.Question,
- submitButtonLabel: 'Create Storage',
- onClose: async (value: string | null) => {
- if (value == null) return
- const storage = await createStorage(value)
- push(`/m/storages/${storage.id}/notes`)
- },
- })
- },
- },
- ])
- },
- [popup, prompt, createStorage, push]
- )
-
- const { toggleClosed } = usePreferences()
- const {
- toggleSideNavOpenedItem,
- sideNavOpenedItemSet,
- openSideNavFolderItemRecursively,
- toggleNav,
- } = useGeneralStatus()
-
- const currentPathname = usePathnameWithoutNoteId()
-
- const { t } = useTranslation()
-
- return (
-
-
-
-
- Storages
- {
- push('/m/storages')
- toggleNav()
- }}
- >
-
-
-
-
-
- {storageEntries.map(([, storage]) => {
- const itemId = `storage:${storage.id}`
- const storageIsFolded = !sideNavOpenedItemSet.has(itemId)
- const showPromptToCreateFolder = (folderPathname: string) => {
- prompt({
- title: 'Create a Folder',
- message: 'Enter the path where do you want to create a folder',
- iconType: DialogIconTypes.Question,
- defaultValue: folderPathname === '/' ? '/' : `${folderPathname}/`,
- submitButtonLabel: 'Create Folder',
- onClose: async (value: string | null) => {
- if (value == null) {
- return
- }
- if (value.endsWith('/')) {
- value = value.slice(0, value.length - 1)
- }
- await createFolder(storage.id, value)
-
- push(`/m/storages/${storage.id}/notes${value}`)
-
- // Open folder item
- openSideNavFolderItemRecursively(storage.id, value)
- },
- })
- }
- const showPromptToRenameFolder = (folderPathname: string) => {
- prompt({
- title: t('folder.rename'),
- message: t('folder.renameMessage'),
- iconType: DialogIconTypes.Question,
- defaultValue: folderPathname.split('/').pop(),
- submitButtonLabel: t('folder.rename'),
- onClose: async (value: string | null) => {
- const folderPathSplit = folderPathname.split('/')
- if (
- value == null ||
- value === '' ||
- value === folderPathSplit.pop()
- ) {
- return
- }
- const newPathname = folderPathSplit.join('/') + '/' + value
- try {
- await renameFolder(storage.id, folderPathname, newPathname)
- push(`/m/storages/${storage.id}/notes${newPathname}`)
- openSideNavFolderItemRecursively(storage.id, newPathname)
- } catch (error) {
- pushMessage({
- title: t('general.error'),
- description: t('folder.renameErrorMessage'),
- })
- }
- },
- })
- }
-
- const allNotesPagePathname = `/m/storages/${storage.id}/notes`
- const allNotesPageIsActive = currentPathname === allNotesPagePathname
-
- const trashcanPagePathname = `/m/storages/${storage.id}/trashcan`
- const trashcanPageIsActive = currentPathname === trashcanPagePathname
-
- const controlComponents = [
-
showPromptToCreateFolder('/')}
- icon={}
- />,
- ]
-
- if (storage.cloudStorage != null && user != null) {
- const cloudSync = () => {
- if (user == null) {
- pushMessage({
- title: 'No User Error',
- description: 'Please login first to sync the storage.',
- })
- }
- syncStorage(storage.id)
- }
-
- controlComponents.unshift(
- }
- />
- )
- }
-
- controlComponents.unshift(
- {
- push(`/m/storages/${storage.id}/settings`)
- toggleNav()
- }}
- icon={}
- />
- )
-
- return (
-
- {
- toggleSideNavOpenedItem(itemId)
- }}
- onClick={() => {
- toggleSideNavOpenedItem(itemId)
- }}
- onContextMenu={(event) => {
- event.preventDefault()
- popup(event, [
- {
- type: MenuTypes.Normal,
- label: t('storage.rename'),
- onClick: async () => {
- prompt({
- title: `Rename "${storage.name}" storage`,
- message: t('storage.renameMessage'),
- iconType: DialogIconTypes.Question,
- defaultValue: storage.name,
- submitButtonLabel: t('storage.rename'),
- onClose: async (value: string | null) => {
- if (value == null) return
- await renameStorage(storage.id, value)
- },
- })
- },
- },
- {
- type: MenuTypes.Normal,
- label: t('storage.remove'),
- onClick: async () => {
- messageBox({
- title: `Remove "${storage.name}" storage`,
- message: t('storage.removeMessage'),
- iconType: DialogIconTypes.Warning,
- buttons: [t('storage.remove'), t('general.cancel')],
- defaultButtonIndex: 0,
- cancelButtonIndex: 1,
- onClose: (value: number | null) => {
- if (value === 0) {
- removeStorage(storage.id)
- }
- },
- })
- },
- },
- ])
- }}
- controlComponents={controlComponents}
- />
- {!storageIsFolded && (
- <>
- }
- active={allNotesPageIsActive}
- onClick={() => {
- push(allNotesPagePathname)
- toggleNav()
- }}
- />
-
-
-
- : }
- active={trashcanPageIsActive}
- onClick={() => {
- push(trashcanPagePathname)
- toggleNav()
- }}
- onContextMenu={(event) => {
- event.preventDefault()
- // TODO: Implement context menu(restore all notes)
- }}
- />
- >
- )}
-
- )
- })}
- {storageEntries.length === 0 && (
- {t('storage.noStorage')}
- )}
-
-
-
-
- )
-}
diff --git a/src/mobile/components/Navigator/NavigatorItem.tsx b/src/mobile/components/Navigator/NavigatorItem.tsx
deleted file mode 100644
index 028f989e38..0000000000
--- a/src/mobile/components/Navigator/NavigatorItem.tsx
+++ /dev/null
@@ -1,219 +0,0 @@
-import React from 'react'
-import cc from 'classcat'
-import styled from '../../../lib/styled'
-import { activeBackgroundColor } from '../../../lib/styled/styleFunctions'
-import Icon from '../../../components/atoms/Icon'
-import { mdiChevronRight, mdiChevronDown } from '@mdi/js'
-
-const Container = styled.div`
- position: relative;
- user-select: none;
- height: 34px;
- display: flex;
- justify-content: space-between;
-
- .sideNavWrapper {
- min-width: 0;
- flex: 1 1 auto;
-
- button > span {
- width: 30px;
- transition: color 200ms ease-in-out;
- color: ${({ theme }) => theme.sideNavButtonColor};
- &:hover {
- color: ${({ theme }) => theme.sideNavButtonHoverColor};
- }
-
- &:active,
- .active {
- color: ${({ theme }) => theme.sideNavButtonActiveColor};
- }
- }
- }
-
- transition: 200ms background-color;
- &:hover,
- &:focus,
- &:active,
- &.active {
- ${activeBackgroundColor}
-
- button > span {
- color: ${({ theme }) => theme.sideNavButtonColor};
- }
- }
- &:hover {
- cursor: pointer;
- }
-
- &.allnotes-sidenav {
- padding-left: 4px !important;
- button {
- padding-left: 6px !important;
- }
- }
- &.bookmark-sidenav {
- padding-left: 4px !important;
- margin-bottom: 25px;
- button {
- padding-left: 6px !important;
- }
- }
-`
-
-const FoldButton = styled.button`
- position: absolute;
- width: 26px;
- height: 26px;
- padding-left: 10px;
- border: none;
- background-color: transparent;
- margin-right: 3px;
- border-radius: 2px;
- flex: 0 0 26px;
- top: 5px;
- color: ${({ theme }) => theme.sideNavButtonColor};
- &:focus {
- box-shadow: none;
- }
-`
-
-const ClickableContainer = styled.button`
- background-color: transparent;
- border: none;
- height: 35px;
- display: flex;
- align-items: center;
- min-width: 0;
- width: 100%;
- flex: 1 1 auto;
-
- color: ${({ theme }) => theme.sideNavLabelColor};
-
- .icon {
- flex: 0 0 auto;
- margin-right: 4px;
- color: ${({ theme }) => theme.sideNavButtonColor};
- &:hover {
- color: ${({ theme }) => theme.sideNavButtonHoverColor};
- }
-
- &:active,
- .active {
- color: ${({ theme }) => theme.sideNavButtonActiveColor};
- }
- }
-`
-
-const Label = styled.div`
- min-width: 0;
- overflow: hidden;
- white-space: nowrap;
- flex: 1 1 auto;
- text-align: left;
-`
-
-const ControlContainer = styled.div`
- position: absolute;
- right: 0;
- top: 4px;
- padding-left: 10px;
- display: flex;
- flex: 2 0 auto;
- justify-content: flex-end;
- button {
- transition: color 200ms ease-in-out;
- color: ${({ theme }) => theme.sideNavButtonColor};
- &:hover {
- color: ${({ theme }) => theme.sideNavButtonHoverColor};
- }
-
- &:active,
- .active {
- color: ${({ theme }) => theme.sideNavButtonActiveColor};
- }
- }
-`
-
-const SideNavigatorItemIconContainer = styled.span`
- padding-right: 6px;
-`
-
-interface NavigatorItemProps {
- label: string
- icon?: React.ReactNode
- depth: number
- controlComponents?: any[]
- className?: string
- folded?: boolean
- active?: boolean
- onFoldButtonClick?: (event: React.MouseEvent) => void
- onClick?: (event: React.MouseEvent) => void
- onContextMenu?: (event: React.MouseEvent) => void
- onDrop?: (event: React.DragEvent) => void
- onDragOver?: (event: React.DragEvent) => void
- onDragEnd?: (event: React.DragEvent) => void
- onDoubleClick?: (event: React.MouseEvent) => void
-}
-
-const NavigatorItem = ({
- label,
- icon,
- depth,
- controlComponents,
- className,
- folded,
- active,
- onFoldButtonClick,
- onClick,
- onDoubleClick,
- onContextMenu,
- onDrop,
- onDragOver,
- onDragEnd,
-}: NavigatorItemProps) => {
- return (
-
-
- {folded != null && (
-
-
-
- )}
-
- {icon != null && (
-
- {icon}
-
- )}
-
-
-
- {controlComponents && (
-
- {controlComponents}
-
- )}
-
- )
-}
-
-export default NavigatorItem
diff --git a/src/mobile/components/Navigator/index.ts b/src/mobile/components/Navigator/index.ts
deleted file mode 100644
index 49e364d97a..0000000000
--- a/src/mobile/components/Navigator/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import SideNavigator from './Navigator'
-
-export default SideNavigator
diff --git a/src/mobile/components/atoms/ControlButton.tsx b/src/mobile/components/atoms/ControlButton.tsx
new file mode 100644
index 0000000000..e3c67e89ae
--- /dev/null
+++ b/src/mobile/components/atoms/ControlButton.tsx
@@ -0,0 +1,43 @@
+import React from 'react'
+import styled from '../../../lib/styled'
+import Icon from '../../../components/atoms/Icon'
+
+const StyledButton = styled.button`
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 34px;
+ width: 29px;
+ padding: 0;
+ border: none;
+ background-color: transparent;
+ font-size: 18px;
+ cursor: pointer;
+ color: ${({ theme }) => theme.sideNavButtonColor};
+
+ &.active {
+ color: ${({ theme }) => theme.sideNavButtonActiveColor};
+ }
+`
+
+interface ControlButtonProps {
+ iconPath: string
+ onClick?: (event: React.MouseEvent) => void
+ spin?: boolean
+ active?: boolean
+}
+
+const ControlButton = ({
+ iconPath,
+ spin,
+ onClick,
+ active = false,
+}: ControlButtonProps) => {
+ return (
+
+
+
+ )
+}
+
+export default ControlButton
diff --git a/src/mobile/components/atoms/NavigatorItem.tsx b/src/mobile/components/atoms/NavigatorItem.tsx
new file mode 100644
index 0000000000..a5221fe55e
--- /dev/null
+++ b/src/mobile/components/atoms/NavigatorItem.tsx
@@ -0,0 +1,108 @@
+import React from 'react'
+import cc from 'classcat'
+import styled from '../../../lib/styled'
+import { textOverflow } from '../../../lib/styled/styleFunctions'
+import Icon from '../../../components/atoms/Icon'
+import { mdiChevronRight, mdiChevronDown } from '@mdi/js'
+
+const Container = styled.div`
+ position: relative;
+ user-select: none;
+ height: 34px;
+ display: flex;
+ &.active {
+ background-color: ${({ theme }) => theme.sideNavItemActiveBackgroundColor};
+ }
+`
+
+const FoldButton = styled.button`
+ position: absolute;
+ width: 24px;
+ height: 34px;
+ left: 0;
+ border: none;
+ background-color: transparent;
+ color: ${({ theme }) => theme.sideNavButtonColor};
+`
+
+const ClickableContainer = styled.button`
+ height: 34px;
+ display: flex;
+ align-items: center;
+ flex: 1;
+
+ background-color: transparent;
+ border: none;
+ color: ${({ theme }) => theme.sideNavLabelColor};
+`
+
+const Label = styled.div`
+ min-width: 0;
+ margin-left: 0.25em;
+ text-align: left;
+ ${textOverflow}
+`
+
+const Control = styled.div`
+ position: absolute;
+ right: 0;
+ height: 34px;
+ display: flex;
+ align-items: center;
+`
+
+interface NavigatorItemProps {
+ label: string
+ iconPath?: string
+ depth: number
+ control?: React.ReactNode
+ className?: string
+ folded?: boolean
+ active?: boolean
+ onFoldButtonClick?: (event: React.MouseEvent) => void
+ onClick?: (event: React.MouseEvent) => void
+ onContextMenu?: (event: React.MouseEvent) => void
+ onDrop?: (event: React.DragEvent) => void
+ onDragOver?: (event: React.DragEvent) => void
+ onDragEnd?: (event: React.DragEvent) => void
+ onDoubleClick?: (event: React.MouseEvent) => void
+}
+
+const NavigatorItem = ({
+ label,
+ iconPath,
+ depth,
+ control,
+ className,
+ folded,
+ active,
+ onFoldButtonClick,
+ onClick,
+}: NavigatorItemProps) => {
+ return (
+
+ {folded != null && (
+
+
+
+ )}
+
+ {iconPath != null && }
+
+
+ {control && {control}}
+
+ )
+}
+
+export default NavigatorItem
diff --git a/src/mobile/components/layouts/TopBarLayout.tsx b/src/mobile/components/layouts/TopBarLayout.tsx
index b6992c9daf..3efd8b110d 100644
--- a/src/mobile/components/layouts/TopBarLayout.tsx
+++ b/src/mobile/components/layouts/TopBarLayout.tsx
@@ -1,6 +1,11 @@
import React, { CSSProperties } from 'react'
import styled from '../../../lib/styled'
-import { borderBottom } from '../../../lib/styled/styleFunctions'
+import {
+ borderBottom,
+ textOverflow,
+ flexCenter,
+} from '../../../lib/styled/styleFunctions'
+import Icon from '../../../components/atoms/Icon'
const TopBarLayoutContainer = styled.div`
display: flex;
@@ -15,9 +20,7 @@ const TopBar = styled.div`
height: 44px;
position: relative;
${borderBottom}
- display: flex;
- align-items: center;
- justify-content: center;
+ ${flexCenter}
`
const TopBarLeftControl = styled.div`
@@ -37,7 +40,19 @@ const TopBarRightControl = styled.div`
const TopBarTitle = styled.div`
height: 44px;
- line-height: 44px;
+ flex: 1;
+ padding: 0 88px;
+ overflow: hidden;
+ ${flexCenter}
+`
+
+const TopBarTitleIcon = styled.div`
+ padding-right: 0.25em;
+ ${flexCenter}
+`
+
+const TopBarTitleLabel = styled.div`
+ ${textOverflow}
`
const ContentContainer = styled.div`
@@ -46,14 +61,16 @@ const ContentContainer = styled.div`
`
interface TopBarLayoutProps {
- title?: React.ReactNode
+ titleLabel?: React.ReactNode
+ titleIconPath?: string
style?: CSSProperties
leftControl?: React.ReactNode
rightControl?: React.ReactNode
}
const TopBarLayout: React.FC
= ({
- title,
+ titleLabel,
+ titleIconPath,
children,
style,
leftControl,
@@ -62,7 +79,14 @@ const TopBarLayout: React.FC = ({
{leftControl}
- {title}
+
+ {titleIconPath != null && (
+
+
+
+ )}
+ {titleLabel}
+
{rightControl}
{children}
diff --git a/src/mobile/components/Navigator/FolderListFragment.tsx b/src/mobile/components/molecules/FolderListFragment.tsx
similarity index 87%
rename from src/mobile/components/Navigator/FolderListFragment.tsx
rename to src/mobile/components/molecules/FolderListFragment.tsx
index d1a72839cd..d3d1810ed0 100644
--- a/src/mobile/components/Navigator/FolderListFragment.tsx
+++ b/src/mobile/components/molecules/FolderListFragment.tsx
@@ -2,30 +2,32 @@ import React, { useMemo } from 'react'
import { useDb } from '../../lib/db'
import { useDialog, DialogIconTypes } from '../../../lib/dialog'
import { useContextMenu, MenuTypes } from '../../../lib/contextMenu'
-import NavigatorItem from './NavigatorItem'
+import NavigatorItem from '../atoms/NavigatorItem'
import { NoteStorage } from '../../../lib/db/types'
import { usePathnameWithoutNoteId, useRouter } from '../../lib/router'
import { useGeneralStatus } from '../../lib/generalStatus'
-import ControlButton from './ControlButton'
+import ControlButton from '../atoms/ControlButton'
import { getFolderItemId } from '../../../lib/nav'
-import { IconAddRound, IconFile, IconFileOpen } from '../../../components/icons'
import { useTranslation } from 'react-i18next'
+import { mdiFolder, mdiFolderOpen, mdiDotsVertical } from '@mdi/js'
interface FolderListFragmentProps {
storage: NoteStorage
+ createNoteToFolder: (folderPathname: string) => void
showPromptToCreateFolder: (folderPathname: string) => void
showPromptToRenameFolder: (folderPathname: string) => void
}
const FolderListFragment = ({
storage,
+ createNoteToFolder,
showPromptToCreateFolder,
showPromptToRenameFolder,
}: FolderListFragmentProps) => {
const { removeFolder } = useDb()
const { push } = useRouter()
const { messageBox } = useDialog()
- const { popup } = useContextMenu()
+ const { popupWithPosition } = useContextMenu()
const { t } = useTranslation()
const { folderMap, id: storageId } = storage
@@ -57,9 +59,15 @@ const FolderListFragment = ({
storageId: string,
folderPathname: string
) => {
- return (event: React.MouseEvent) => {
- event.preventDefault()
- popup(event, [
+ return () => {
+ popupWithPosition({ x: 0, y: 0 }, [
+ {
+ type: MenuTypes.Normal,
+ label: 'New Note',
+ onClick: () => {
+ createNoteToFolder(folderPathname)
+ },
+ },
{
type: MenuTypes.Normal,
label: t('folder.create'),
@@ -67,6 +75,8 @@ const FolderListFragment = ({
showPromptToCreateFolder(folderPathname)
},
},
+ { type: MenuTypes.Separator },
+
{
type: MenuTypes.Normal,
label: t('folder.rename'),
@@ -141,22 +151,16 @@ const FolderListFragment = ({
folded={folded}
depth={depth}
active={folderIsActive}
- icon={folderIsActive ? : }
+ iconPath={folderIsActive ? mdiFolderOpen : mdiFolder}
label={folderName}
onClick={createOnFolderItemClickHandler(folderPathname)}
- onDoubleClick={() => showPromptToRenameFolder(folderPathname)}
- onContextMenu={createOnContextMenuHandler(
- storage.id,
- folderPathname
- )}
onFoldButtonClick={() => toggleSideNavOpenedItem(itemId)}
- controlComponents={[
+ control={
showPromptToCreateFolder(folderPathname)}
- icon={}
- />,
- ]}
+ onClick={createOnContextMenuHandler(storageId, folderPathname)}
+ iconPath={mdiDotsVertical}
+ />
+ }
/>
)
})}
diff --git a/src/mobile/components/molecules/StorageNavigatorFragment.tsx b/src/mobile/components/molecules/StorageNavigatorFragment.tsx
new file mode 100644
index 0000000000..a85f2e2a5d
--- /dev/null
+++ b/src/mobile/components/molecules/StorageNavigatorFragment.tsx
@@ -0,0 +1,303 @@
+import React, { useCallback, useMemo } from 'react'
+import { useRouter, usePathnameWithoutNoteId } from '../../lib/router'
+import { useDb } from '../../lib/db'
+import { useDialog, DialogIconTypes } from '../../../lib/dialog'
+import { useContextMenu, MenuTypes } from '../../../lib/contextMenu'
+import { useFirstUser } from '../../../lib/preferences'
+import NavigatorItem from '../atoms/NavigatorItem'
+import { useGeneralStatus } from '../../lib/generalStatus'
+import ControlButton from '../atoms/ControlButton'
+import FolderListFragment from '../molecules/FolderListFragment'
+import TagListFragment from '../molecules/TagListFragment'
+import { useToast } from '../../../lib/toast'
+import { useTranslation } from 'react-i18next'
+import { mdiTrashCan, mdiBookOpen, mdiSync, mdiDotsVertical } from '@mdi/js'
+import { NoteStorage } from '../../../lib/db/types'
+import { dispatchNoteDetailFocusTitleInputEvent } from '../../../lib/events'
+
+interface StorageNavigatorFragmentProps {
+ storage: NoteStorage
+}
+
+const StorageNavigatorFragment = ({
+ storage,
+}: StorageNavigatorFragmentProps) => {
+ const {
+ createFolder,
+ renameFolder,
+ renameStorage,
+ removeStorage,
+ syncStorage,
+ createNote,
+ } = useDb()
+ const currentPathname = usePathnameWithoutNoteId()
+
+ const {
+ toggleSideNavOpenedItem,
+ sideNavOpenedItemSet,
+ openSideNavFolderItemRecursively,
+ toggleNav,
+ } = useGeneralStatus()
+
+ const { t } = useTranslation()
+ const { popupWithPosition } = useContextMenu()
+ const { prompt, messageBox } = useDialog()
+ const { push } = useRouter()
+ const user = useFirstUser()
+ const { pushMessage } = useToast()
+ const itemId = `storage:${storage.id}`
+ const storageIsFolded = !sideNavOpenedItemSet.has(itemId)
+
+ const showPromptToCreateFolder = useCallback(
+ (folderPathname: string) => {
+ prompt({
+ title: 'Create a Folder',
+ message: 'Enter the path where do you want to create a folder',
+ iconType: DialogIconTypes.Question,
+ defaultValue: folderPathname === '/' ? '/' : `${folderPathname}/`,
+ submitButtonLabel: 'Create Folder',
+ onClose: async (value: string | null) => {
+ if (value == null) {
+ return
+ }
+ if (value.endsWith('/')) {
+ value = value.slice(0, value.length - 1)
+ }
+ await createFolder(storage.id, value)
+
+ push(`/m/storages/${storage.id}/notes${value}`)
+
+ // Open folder item
+ openSideNavFolderItemRecursively(storage.id, value)
+ },
+ })
+ },
+ [createFolder, prompt, push, storage.id, openSideNavFolderItemRecursively]
+ )
+
+ const showPromptToRenameFolder = (folderPathname: string) => {
+ prompt({
+ title: t('folder.rename'),
+ message: t('folder.renameMessage'),
+ iconType: DialogIconTypes.Question,
+ defaultValue: folderPathname.split('/').pop(),
+ submitButtonLabel: t('folder.rename'),
+ onClose: async (value: string | null) => {
+ const folderPathSplit = folderPathname.split('/')
+ if (value == null || value === '' || value === folderPathSplit.pop()) {
+ return
+ }
+ const newPathname = folderPathSplit.join('/') + '/' + value
+ try {
+ await renameFolder(storage.id, folderPathname, newPathname)
+ push(`/m/storages/${storage.id}/notes${newPathname}`)
+ openSideNavFolderItemRecursively(storage.id, newPathname)
+ } catch (error) {
+ pushMessage({
+ title: t('general.error'),
+ description: t('folder.renameErrorMessage'),
+ })
+ }
+ },
+ })
+ }
+
+ const allNotesPagePathname = `/m/storages/${storage.id}/notes`
+ const allNotesPageIsActive = currentPathname === allNotesPagePathname
+
+ const trashcanPagePathname = `/m/storages/${storage.id}/trashcan`
+ const trashcanPageIsActive = currentPathname === trashcanPagePathname
+
+ const sync = useCallback(() => {
+ if (user == null) {
+ pushMessage({
+ title: 'No User Error',
+ description: 'Please login first to sync the storage.',
+ })
+ }
+ syncStorage(storage.id)
+ }, [user, pushMessage, syncStorage, storage.id])
+
+ const createNoteToFolder = useCallback(
+ async (folderPathname: string) => {
+ const newNote = await createNote(storage.id, {
+ folderPathname,
+ })
+ if (newNote == null) {
+ return
+ }
+ console.log(folderPathname)
+
+ const notePathname =
+ newNote.folderPathname === '/'
+ ? `/m/storages/${storage.id}/notes/${newNote._id}`
+ : `/m/storages/${storage.id}/notes${newNote.folderPathname}/${newNote._id}`
+ push(notePathname)
+ toggleNav()
+ dispatchNoteDetailFocusTitleInputEvent()
+ },
+ [createNote, push, toggleNav, storage.id]
+ )
+
+ const openStorageContextMenu = useCallback(() => {
+ popupWithPosition({ x: 0, y: 0 }, [
+ {
+ type: MenuTypes.Normal,
+ label: 'New Note',
+ onClick: async () => {
+ createNoteToFolder('/')
+ },
+ },
+ {
+ type: MenuTypes.Normal,
+ label: 'New Folder',
+ onClick: async () => {
+ showPromptToCreateFolder('/')
+ },
+ },
+ { type: MenuTypes.Separator },
+ {
+ type: MenuTypes.Normal,
+ label: 'Sync Storage',
+ onClick: sync,
+ },
+ {
+ type: MenuTypes.Normal,
+ label: t('storage.rename'),
+ onClick: async () => {
+ prompt({
+ title: `Rename "${storage.name}" storage`,
+ message: t('storage.renameMessage'),
+ iconType: DialogIconTypes.Question,
+ defaultValue: storage.name,
+ submitButtonLabel: t('storage.rename'),
+ onClose: async (value: string | null) => {
+ if (value == null) {
+ return
+ }
+ renameStorage(storage.id, value)
+ },
+ })
+ },
+ },
+ {
+ type: MenuTypes.Normal,
+ label: 'Configure Storage',
+ onClick: async () => {
+ toggleNav()
+ push(`/m/storages/${storage.id}/settings`)
+ },
+ },
+ { type: MenuTypes.Separator },
+ {
+ type: MenuTypes.Normal,
+ label: t('storage.remove'),
+ onClick: async () => {
+ messageBox({
+ title: `Remove "${storage.name}" storage`,
+ message: t('storage.removeMessage'),
+ iconType: DialogIconTypes.Warning,
+ buttons: [t('storage.remove'), t('general.cancel')],
+ defaultButtonIndex: 0,
+ cancelButtonIndex: 1,
+ onClose: (value: number | null) => {
+ if (value === 0) {
+ removeStorage(storage.id)
+ }
+ },
+ })
+ },
+ },
+ ])
+ }, [
+ popupWithPosition,
+ showPromptToCreateFolder,
+ sync,
+ push,
+ prompt,
+ messageBox,
+ createNoteToFolder,
+ renameStorage,
+ toggleNav,
+ removeStorage,
+ storage.id,
+ storage.name,
+ t,
+ ])
+
+ const syncing = storage.sync != null
+
+ const trashed = useMemo(
+ () => Object.values(storage.noteMap).filter((note) => note!.trashed),
+ [storage.noteMap]
+ )
+ return (
+ <>
+ {
+ toggleSideNavOpenedItem(itemId)
+ }}
+ onClick={() => {
+ toggleSideNavOpenedItem(itemId)
+ }}
+ control={
+ <>
+
+
+ >
+ }
+ />
+ {!storageIsFolded && (
+ <>
+ {
+ push(allNotesPagePathname)
+ toggleNav()
+ }}
+ />
+
+
+
+ {trashed.length > 0 && (
+ {
+ push(trashcanPagePathname)
+ toggleNav()
+ }}
+ onContextMenu={(event) => {
+ event.preventDefault()
+ // TODO: Implement context menu(restore all notes)
+ }}
+ />
+ )}
+ >
+ )}
+ >
+ )
+}
+
+export default StorageNavigatorFragment
diff --git a/src/mobile/components/molecules/TagList.tsx b/src/mobile/components/molecules/TagList.tsx
index fb8899e157..723555524c 100644
--- a/src/mobile/components/molecules/TagList.tsx
+++ b/src/mobile/components/molecules/TagList.tsx
@@ -2,7 +2,8 @@ import React, { useCallback } from 'react'
import ButtonIcon from '../../../components/atoms/ButtonIcon'
import styled from '../../../lib/styled'
import { noteListIconColor } from '../../../lib/styled/styleFunctions'
-import { IconTag, IconClose } from '../../../components/icons'
+import Icon from '../../../components/atoms/Icon'
+import { mdiClose, mdiTagMultiple } from '@mdi/js'
interface TagListItemProps {
tagName: string
@@ -18,7 +19,7 @@ const TagListItem = ({ tagName, removeTagByName }: TagListItemProps) => {
)
@@ -79,7 +80,7 @@ interface TagListProps {
const TagList = ({ tags, removeTagByName }: TagListProps) => {
return (
- } />
+
{tags.map((tag) => (
{
: }
+ iconPath={mdiPound}
label={tagName}
onClick={() => {
push(tagPathname)
@@ -83,11 +83,15 @@ const TagListFragment = ({ storage }: TagListFragmentProps) => {
toggleNav,
])
+ if (tagList.length === 0) {
+ return null
+ }
+
return (
<>
}
+ iconPath={mdiTagMultiple}
label={t('tag.tag')}
folded={tagList.length > 0 ? tagListIsFolded : undefined}
onFoldButtonClick={() => {
diff --git a/src/mobile/components/organisms/ContextMenu.tsx b/src/mobile/components/organisms/ContextMenu.tsx
index 5a65b4b7a4..19cf9cd7f0 100644
--- a/src/mobile/components/organisms/ContextMenu.tsx
+++ b/src/mobile/components/organisms/ContextMenu.tsx
@@ -78,6 +78,14 @@ const ContextMenuItem = styled.button`
}
`
+const SeparatorContextMenuItem = styled.div`
+ height: 10px;
+ box-sizing: border-box;
+ background-color: ${({ theme }) => theme.borderColor};
+ border: none;
+ width: 100%;
+`
+
interface ContextMenuProps {
contextMenu: ContextMenuContext
}
@@ -137,6 +145,8 @@ class ContextMenu extends React.Component {
{menu.label}
)
+ case MenuTypes.Separator:
+ return
default:
return (
diff --git a/src/mobile/components/organisms/GeneralPreferencesTab.tsx b/src/mobile/components/organisms/GeneralPreferencesTab.tsx
index 7e1b9aaf32..80d15d3900 100644
--- a/src/mobile/components/organisms/GeneralPreferencesTab.tsx
+++ b/src/mobile/components/organisms/GeneralPreferencesTab.tsx
@@ -8,7 +8,6 @@ import {
import LoginButton from '../../../components/atoms/LoginButton'
import UserInfo from '../molecules/UserInfo'
import { useUsers } from '../../../lib/accounts'
-import { IconArrowRotate } from '../../../components/icons'
import { FormCheckItem } from '../../../components/atoms/form'
import { usePreferences } from '../../../lib/preferences'
@@ -39,14 +38,7 @@ const GeneralPreferencesTab = () => {
ButtonComponent={SectionPrimaryButton}
>
{(loginState) =>
- loginState !== 'logging-in' ? (
- <>Sign in>
- ) : (
- <>
-
- Loggin in...
- >
- )
+ loginState !== 'logging-in' ? <>Sign in> : <>Loggin in...>
}
)}
diff --git a/src/mobile/components/organisms/Navigator.tsx b/src/mobile/components/organisms/Navigator.tsx
new file mode 100644
index 0000000000..7aa3c038c9
--- /dev/null
+++ b/src/mobile/components/organisms/Navigator.tsx
@@ -0,0 +1,175 @@
+import React, { useMemo, useCallback } from 'react'
+import { useRouter } from '../../lib/router'
+import { useDb } from '../../lib/db'
+import { entries } from '../../../lib/db/utils'
+import styled from '../../../lib/styled'
+import { useDialog, DialogIconTypes } from '../../../lib/dialog'
+import { useContextMenu, MenuTypes } from '../../../lib/contextMenu'
+import { usePreferences } from '../../../lib/preferences'
+import { useGeneralStatus } from '../../lib/generalStatus'
+import { useTranslation } from 'react-i18next'
+import Icon from '../../../components/atoms/Icon'
+import { mdiClose, mdiPlus, mdiTuneVertical } from '@mdi/js'
+import StorageNavigatorFragment from '../molecules/StorageNavigatorFragment'
+import { borderBottom } from '../../../lib/styled/styleFunctions'
+
+const StyledSideNavContainer = styled.nav`
+ display: flex;
+ background-color: ${({ theme }) => theme.sideNavBackgroundColor};
+ flex-direction: column;
+ height: 100%;
+ .topControl {
+ height: 44px;
+ display: flex;
+ -webkit-app-region: drag;
+ ${borderBottom}
+ .spacer {
+ flex: 1;
+ }
+ .button {
+ width: 44px;
+ height: 44px;
+ background-color: transparent;
+ border: none;
+ cursor: pointer;
+ font-size: 24px;
+
+ transition: color 200ms ease-in-out;
+ color: ${({ theme }) => theme.sideNavButtonColor};
+ &:hover {
+ color: ${({ theme }) => theme.sideNavButtonHoverColor};
+ }
+
+ &:active,
+ .active {
+ color: ${({ theme }) => theme.sideNavButtonActiveColor};
+ }
+ }
+ }
+
+ .storageList {
+ list-style: none;
+ margin: 0;
+ flex: 1;
+ overflow: auto;
+ display: flex;
+ flex-direction: column;
+ }
+
+ .empty {
+ padding: 4px;
+ padding-left: 26px;
+ margin-bottom: 4px;
+ color: ${({ theme }) => theme.sideNavLabelColor};
+ user-select: none;
+ }
+
+ .bottomControl {
+ height: 35px;
+ display: flex;
+ border-top: 1px solid ${({ theme }) => theme.colors.border};
+ button {
+ height: 35px;
+ border: none;
+ background-color: transparent;
+ display: flex;
+ align-items: center;
+ }
+ .addFolderButton {
+ flex: 1;
+ border-right: 1px solid ${({ theme }) => theme.colors.border};
+ }
+ .addFolderButtonIcon {
+ margin-right: 4px;
+ }
+ .moreButton {
+ width: 30px;
+ display: flex;
+ justify-content: center;
+ }
+ }
+`
+
+const Spacer = styled.div`
+ flex: 1;
+`
+
+interface NavigatorProps {
+ toggle: () => void
+}
+
+export default ({ toggle }: NavigatorProps) => {
+ const { createStorage, storageMap } = useDb()
+ const { popup } = useContextMenu()
+ const { prompt } = useDialog()
+ const { push } = useRouter()
+
+ const storageEntries = useMemo(() => {
+ return entries(storageMap)
+ }, [storageMap])
+
+ const openSideNavContextMenu = useCallback(
+ (event: React.MouseEvent) => {
+ event.preventDefault()
+ popup(event, [
+ {
+ type: MenuTypes.Normal,
+ label: 'New Storage',
+ onClick: async () => {
+ prompt({
+ title: 'Create a Storage',
+ message: 'Enter name of a storage to create',
+ iconType: DialogIconTypes.Question,
+ submitButtonLabel: 'Create Storage',
+ onClose: async (value: string | null) => {
+ if (value == null) return
+ const storage = await createStorage(value)
+ push(`/m/storages/${storage.id}/notes`)
+ },
+ })
+ },
+ },
+ ])
+ },
+ [popup, prompt, createStorage, push]
+ )
+
+ const { toggleClosed } = usePreferences()
+ const { toggleNav } = useGeneralStatus()
+
+ const { t } = useTranslation()
+
+ return (
+
+
+
+
+
+
+
+
+
+ {storageEntries.map(([, storage]) => (
+
+ ))}
+ {storageEntries.length === 0 && (
+
{t('storage.noStorage')}
+ )}
+
+
+
+
+ )
+}
diff --git a/src/mobile/components/organisms/NoteItem.tsx b/src/mobile/components/organisms/NoteItem.tsx
index 7f3b9a773c..4d291470b6 100644
--- a/src/mobile/components/organisms/NoteItem.tsx
+++ b/src/mobile/components/organisms/NoteItem.tsx
@@ -13,8 +13,9 @@ import { useDb } from '../../lib/db'
import { useDialog, DialogIconTypes } from '../../../lib/dialog'
import { useTranslation } from 'react-i18next'
import { useRouter } from '../../lib/router'
-import { IconTrash } from '../../../components/icons'
import { NoteDoc } from '../../../lib/db/types'
+import { mdiTrashCan } from '@mdi/js'
+import Icon from '../../../components/atoms/Icon'
export const NoteListItemContainer = styled.div`
margin: 0;
@@ -352,7 +353,7 @@ export default ({
}
}}
>
-
+
diff --git a/src/mobile/components/organisms/PreferencesModal.tsx b/src/mobile/components/organisms/PreferencesModal.tsx
index f3c9c17b8c..d20ec2a3ec 100644
--- a/src/mobile/components/organisms/PreferencesModal.tsx
+++ b/src/mobile/components/organisms/PreferencesModal.tsx
@@ -54,7 +54,7 @@ const PreferencesModal = () => {
return (
@@ -70,7 +70,7 @@ const PreferencesModal = () => {
return (
diff --git a/src/mobile/components/pages/AttachmentsPage.tsx b/src/mobile/components/pages/AttachmentsPage.tsx
index a8f7dbd909..09ba3a5211 100644
--- a/src/mobile/components/pages/AttachmentsPage.tsx
+++ b/src/mobile/components/pages/AttachmentsPage.tsx
@@ -18,7 +18,7 @@ const AttachmentsPage = () => {
return (
}
- title={<>Attachments in {storage.name}>}
+ titleLabel={`Attachments in ${storage.name}`}
>
diff --git a/src/mobile/components/pages/NotePage.tsx b/src/mobile/components/pages/NotePage.tsx
index 480f91e8a3..8a45cde030 100644
--- a/src/mobile/components/pages/NotePage.tsx
+++ b/src/mobile/components/pages/NotePage.tsx
@@ -16,11 +16,19 @@ import TopBarLayout from '../layouts/TopBarLayout'
import NoteDetail from '../organisms/NoteDetail'
import styled from '../../../lib/styled'
import Icon from '../../../components/atoms/Icon'
-import { mdiChevronLeft, mdiEyeOutline, mdiDotsVertical } from '@mdi/js'
+import {
+ mdiChevronLeft,
+ mdiEyeOutline,
+ mdiDotsVertical,
+ mdiFolderOpen,
+ mdiPound,
+ mdiTrashCan,
+ mdiBookOpen,
+} from '@mdi/js'
import TopBarButton from '../atoms/TopBarButton'
import TopBarToggleNavButton from '../atoms/TopBarToggleNavButton'
-import { IconFileOpen, IconTrash, IconTag } from '../../../components/icons'
import { useContextMenu, MenuTypes } from '../../../lib/contextMenu'
+import { values, getFolderNameFromPathname } from '../../../lib/db/utils'
const NotePageContainer = styled.div`
width: 100%;
@@ -64,8 +72,11 @@ const NotePage = ({ storage }: NotePageProps) => {
case 'storages.notes':
const { folderPathname } = routeParams
const folder = storage.folderMap[folderPathname]
+ if (folderPathname === '/') {
+ return values(storage.noteMap).filter((note) => !note.trashed)
+ }
if (folder == null) return []
- return (Object.values(storage.noteMap) as NoteDoc[]).filter(
+ return values(storage.noteMap).filter(
(note) =>
(note.folderPathname + '/').startsWith(folder.pathname + '/') &&
!note.trashed
@@ -78,13 +89,17 @@ const NotePage = ({ storage }: NotePageProps) => {
.map((noteId) => storage.noteMap[noteId]!)
.filter((note) => !note.trashed)
case 'storages.trashCan':
- return (Object.values(storage.noteMap) as NoteDoc[]).filter(
- (note) => note.trashed
- )
+ return values(storage.noteMap).filter((note) => note.trashed)
}
return []
}, [storage, routeParams])
+ const sortedNotes = useMemo(() => {
+ return notes.slice().sort((a, b) => {
+ return new Date(b.updatedAt).valueOf() - new Date(a.updatedAt).valueOf()
+ })
+ }, [notes])
+
const currentNote: NoteDoc | undefined = useMemo(() => {
if (storage == null || noteId == null) {
return undefined
@@ -147,31 +162,39 @@ const NotePage = ({ storage }: NotePageProps) => {
push(currentPathnameWithoutNoteId)
}, [push, currentPathnameWithoutNoteId])
- const noteListTitle = useMemo(() => {
+ const { titleIconPath, titleLabel } = useMemo<{
+ titleIconPath?: string
+ titleLabel: React.ReactNode
+ }>(() => {
switch (routeParams.name) {
case 'storages.notes':
- return (
- <>
- {routeParams.folderPathname} in{' '}
- {storage!.name}
- >
- )
+ const folderName = getFolderNameFromPathname(routeParams.folderPathname)
+ if (folderName === null) {
+ return {
+ titleIconPath: mdiBookOpen,
+ titleLabel: 'All Notes',
+ }
+ }
+ return {
+ titleIconPath: mdiFolderOpen,
+ titleLabel: {folderName},
+ }
case 'storages.tags.show':
- return (
- <>
- {routeParams.tagName} in {storage!.name}
- >
- )
+ return {
+ titleIconPath: mdiPound,
+ titleLabel: {routeParams.tagName},
+ }
case 'storages.trashCan':
- return (
- <>
- Trashed Notes in {storage!.name}
- >
- )
+ return {
+ titleIconPath: mdiTrashCan,
+ titleLabel: 'Trashed Notes',
+ }
default:
- return 'unknown'
+ return {
+ titleLabel: 'Unknown page',
+ }
}
- }, [routeParams, storage])
+ }, [routeParams])
const toggleNoteViewMode = useCallback(() => {
setGeneralStatus({
@@ -206,12 +229,13 @@ const NotePage = ({ storage }: NotePageProps) => {
}}
>
}
>
{
}}
>
{
const [storageType, setStorageType] = useState<'cloud' | 'local'>('cloud')
return (
- } title='New Storage'>
+ }
+ titleLabel='New Storage'
+ >
- {t('Create new storage')}
Storage Type
diff --git a/src/mobile/components/pages/StorageEditPage.tsx b/src/mobile/components/pages/StorageEditPage.tsx
index 73d1eec4be..4e78f663f9 100644
--- a/src/mobile/components/pages/StorageEditPage.tsx
+++ b/src/mobile/components/pages/StorageEditPage.tsx
@@ -63,7 +63,7 @@ const StorageEditPage = ({ storage }: StorageEditPageProps) => {
return (
}
- title={t('storage.edit')}
+ titleLabel={t('storage.edit')}
>
diff --git a/src/mobile/lib/redirect.ts b/src/mobile/lib/redirect.ts
index eecaa377e6..1d4841e3ad 100644
--- a/src/mobile/lib/redirect.ts
+++ b/src/mobile/lib/redirect.ts
@@ -6,12 +6,15 @@ import { useEffectOnce } from 'react-use'
export default function useRedirectHandler() {
const db = useDb()
- const { replace } = useRouter()
+ const { replace, pathname } = useRouter()
const storageMapRef = useRef(db.storageMap)
useEffectOnce(() => {
const storageEntries = entries(storageMapRef.current)
+ if (pathname.startsWith('/m') && pathname !== '/m') {
+ return
+ }
if (storageEntries.length === 0) {
replace('/m/storages')
} else {
diff --git a/webpack.mobile.config.ts b/webpack.mobile.config.ts
index 079038ea94..fa84e1297a 100644
--- a/webpack.mobile.config.ts
+++ b/webpack.mobile.config.ts
@@ -42,7 +42,7 @@ module.exports = (env, argv) => {
},
{
test: /\.tsx?$/,
- use: [{ loader: 'ts-loader' }],
+ use: [{ loader: 'ts-loader', options: { transpileOnly: true } }],
exclude: /node_modules/,
},
{