lint: dprint

This commit is contained in:
lin onetwo 2023-05-19 11:22:38 +08:00
parent a3bd62846d
commit 830e11aa77
151 changed files with 2071 additions and 1541 deletions

View file

@ -1,30 +1,30 @@
/* eslint-disable @typescript-eslint/no-unused-expressions */
import { setWorldConstructor, Given, Then } from '@cucumber/cucumber';
import { Given, setWorldConstructor, Then } from '@cucumber/cucumber';
import { delay } from 'bluebird';
import { expect } from 'chai';
import { TidGiWorld } from '../supports/world';
setWorldConstructor(TidGiWorld);
Given('the app is launched', async function (this: TidGiWorld) {
Given('the app is launched', async function(this: TidGiWorld) {
await delay(100);
await this.start();
const windowCount = await this.app?.client?.getWindowCount();
expect(windowCount).equal(1);
});
Then('the element {string} is on the page', async function (this: TidGiWorld, elementSelector: string) {
Then('the element {string} is on the page', async function(this: TidGiWorld, elementSelector: string) {
const result = await this.getElement(elementSelector);
expect(result).to.not.be.undefined;
this.updateContext({ previousElement: result });
});
Then('click on this element', async function (this: TidGiWorld) {
Then('click on this element', async function(this: TidGiWorld) {
expect(this.context?.previousElement).to.not.be.undefined;
if (this.context?.previousElement !== undefined) {
await this.context.previousElement.click();
}
});
Then('click on {string} element', async function (this: TidGiWorld, elementSelector: string) {
Then('click on {string} element', async function(this: TidGiWorld, elementSelector: string) {
const result = await this.getElement(elementSelector);
expect(result).to.not.be.undefined;
if (result !== undefined) {
@ -32,7 +32,7 @@ Then('click on {string} element', async function (this: TidGiWorld, elementSelec
await result.click();
}
});
Then('{string} window show up', async function (this: TidGiWorld, windowName: string) {
Then('{string} window show up', async function(this: TidGiWorld, windowName: string) {
// await delay(1000);
const windowCount = await this.app?.client?.getWindowCount();
expect(windowCount).equal(2);

View file

@ -1,17 +1,17 @@
import { After, Before } from '@cucumber/cucumber';
import fs from 'fs-extra';
import { DEFAULT_WIKI_FOLDER } from '../../src/constants/paths';
import { SETTINGS_FOLDER } from '../../src/constants/appPaths';
import { DEFAULT_WIKI_FOLDER } from '../../src/constants/paths';
import { TidGiWorld } from './world';
Before(async function () {
Before(async function() {
// clear setting folder
await fs.remove(SETTINGS_FOLDER);
await fs.remove(DEFAULT_WIKI_FOLDER);
});
After(async function (this: TidGiWorld, testCase) {
After(async function(this: TidGiWorld, testCase) {
// print logs if test failed
// if (this.app !== undefined && testCase.result?.status === Status.FAILED) {
// console.log('main:\n---\n');

View file

@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/restrict-template-expressions */
const packageJson = require('./package.json');
const beforeAsar = require('./scripts/beforeAsar')
const beforeAsar = require('./scripts/beforeAsar');
const { version, description } = packageJson;
@ -91,7 +91,7 @@ const config = {
An unhandled rejection has occurred inside Forge:
[object Object]
*/
{
{
name: '@reforged/maker-appimage',
platforms: ['linux'],
config: {

View file

@ -15,7 +15,6 @@ const fs = require('fs-extra');
const util = require('util');
/**
*
* @param {*} buildPath /var/folders/qj/7j0zx32d0l75zmnrl1w3m3b80000gn/T/electron-packager/darwin-x64/TidGi-darwin-x64/Electron.app/Contents/Resources/app
* @param {*} electronVersion 12.0.6
* @param {*} platform darwin

View file

@ -1,9 +1,9 @@
import React, { useState, useCallback, useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import styled from 'styled-components';
import Button from '@material-ui/core/Button';
import TextField from '@material-ui/core/TextField';
import Typography from '@material-ui/core/Typography';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import styled from 'styled-components';
const Root = styled.div`
display: flex;
@ -66,11 +66,11 @@ export default function FindInPage(): JSX.Element | null {
return (
<Root>
<InfoContainer>
<Typography variant="body2">
<Typography variant='body2'>
<strong>{activeMatch}</strong>
<span> / </span>
<span>/</span>
<strong>{matches}</strong>
<span> {t('Menu.FindMatches')}</span>
<span>{t('Menu.FindMatches')}</span>
</Typography>
</InfoContainer>
<div>
@ -79,7 +79,7 @@ export default function FindInPage(): JSX.Element | null {
inputRef={inputReference}
placeholder={t('Menu.Find')}
value={text}
margin="dense"
margin='dense'
onChange={(event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const value = event.target.value;
if (typeof value !== 'string') return;
@ -119,41 +119,45 @@ export default function FindInPage(): JSX.Element | null {
/>
</div>
<Button
size="small"
size='small'
disabled={text.length === 0 || matches < 1}
onClick={() => {
if (text.length > 0) {
void window.service.window.findInPage(text, false);
}
}}>
}}
>
{t('Menu.FindPrevious')}
</Button>
<Button
size="small"
size='small'
disabled={text.length === 0 || matches < 1}
onClick={() => {
if (text.length > 0) {
void window.service.window.findInPage(text, true);
}
}}>
}}
>
{t('Menu.FindNext')}
</Button>
<Button
size="small"
size='small'
disabled={text.length === 0}
onClick={() => {
if (text.length > 0) {
void window.service.window.findInPage(text, true);
}
}}>
}}
>
{t('Menu.Find')}
</Button>
<Button
size="small"
size='small'
onClick={() => {
void window.service.window.stopFindInPage(true);
openSetter(false);
}}>
}}
>
{t('Menu.Close')}
</Button>
</Root>

View file

@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
import React from 'react';
import Menu from '@material-ui/core/Menu';
import React from 'react';
interface Props {
buttonElement: React.ReactElement;

View file

@ -1,9 +1,9 @@
import React, { useCallback, useState } from 'react';
import styled, { keyframes } from 'styled-components';
import { Snackbar, Button, IconButton, Tooltip } from '@material-ui/core';
import { Button, IconButton, Snackbar, Tooltip } from '@material-ui/core';
import { Close as CloseIcon } from '@material-ui/icons';
import { useTranslation } from 'react-i18next';
import useDebouncedCallback from 'beautiful-react-hooks/useDebouncedCallback';
import React, { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import styled, { keyframes } from 'styled-components';
const progressAnimation = keyframes`
from {
@ -65,7 +65,7 @@ export function useRestartSnackbar(waitBeforeCountDown = 1000, waitBeforeRestart
return [
requestRestartCountDown,
<div key="RestartSnackbar">
<div key='RestartSnackbar'>
<Snackbar
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
open={opened}
@ -88,14 +88,15 @@ export function useRestartSnackbar(waitBeforeCountDown = 1000, waitBeforeRestart
<RestartButton
key={currentWaitBeforeRestart}
currentWaitBeforeRestart={currentWaitBeforeRestart}
color="secondary"
size="small"
onClick={handleCloseAndRestart}>
color='secondary'
size='small'
onClick={handleCloseAndRestart}
>
{t('Dialog.RestartNow')}
</RestartButton>
<Tooltip title={<span>{t('Dialog.Later')}</span>}>
<IconButton size="small" aria-label="close" color="inherit" onClick={handleCancelRestart}>
<CloseIcon fontSize="small" />
<IconButton size='small' aria-label='close' color='inherit' onClick={handleCancelRestart}>
<CloseIcon fontSize='small' />
</IconButton>
</Tooltip>
</>

View file

@ -4,20 +4,20 @@ export const RootStyle = styled.div`
.Mui-selected,
.Mui-checked {
${({ theme }) =>
theme.palette.mode === 'dark'
? css`
theme.palette.mode === 'dark'
? css`
color: ${theme.palette.primary.light} !important;
`
: ''};
: ''};
}
.Mui-disabled {
${({ theme }) =>
theme.palette.mode === 'dark'
? css`
theme.palette.mode === 'dark'
? css`
color: ${theme.palette.primary.dark} !important;
-webkit-text-fill-color: ${theme.palette.primary.light};
`
: ''};
: ''};
}
label,

View file

@ -1,14 +1,14 @@
/* eslint-disable @typescript-eslint/no-misused-promises */
import Promise from 'bluebird';
import React, { useState, useEffect, useMemo, useCallback } from 'react';
import styled from 'styled-components';
import { useQuery, useMutation, GraphQLClient, ClientContext } from 'graphql-hooks';
import { trim } from 'lodash';
import { useTranslation } from 'react-i18next';
import useDebouncedCallback from 'beautiful-react-hooks/useDebouncedCallback';
import Promise from 'bluebird';
import { ClientContext, GraphQLClient, useMutation, useQuery } from 'graphql-hooks';
import { trim } from 'lodash';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import styled from 'styled-components';
import { TextField, LinearProgress, List, ListItem, ListItemIcon, ListItemText, Button } from '@material-ui/core';
import { Folder as FolderIcon, Cached as CachedIcon, CreateNewFolder as CreateNewFolderIcon } from '@material-ui/icons';
import { Button, LinearProgress, List, ListItem, ListItemIcon, ListItemText, TextField } from '@material-ui/core';
import { Cached as CachedIcon, CreateNewFolder as CreateNewFolderIcon, Folder as FolderIcon } from '@material-ui/icons';
import { GITHUB_GRAPHQL_API } from '@/constants/auth';
import { useUserInfoObservable } from '@services/auth/hooks';
@ -80,7 +80,7 @@ export default function SearchGithubRepo(props: Props): JSX.Element {
[],
);
useEffect(() => {
graphqlClient.setHeader('Authorization', accessToken !== undefined ? `Bearer ${accessToken}` : '');
graphqlClient.setHeader('Authorization', accessToken === undefined ? '' : `Bearer ${accessToken}`);
}, [accessToken, graphqlClient]);
if (githubUsername === '' || githubUsername === undefined || accessToken === '' || accessToken === undefined) {
@ -132,7 +132,9 @@ function SearchGithubRepoResultList({
const timeoutHandle = setTimeout(async () => {
await refetchDebounced();
}, 100);
return () => clearTimeout(timeoutHandle);
return () => {
clearTimeout(timeoutHandle);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [githubUsername, accessToken]);
// try refetch on error
@ -143,7 +145,9 @@ function SearchGithubRepoResultList({
await refetchDebounced();
retryIntervalSetter(retryInterval * 10);
}, retryInterval);
return () => clearTimeout(timeoutHandle);
return () => {
clearTimeout(timeoutHandle);
};
}
return () => {};
// eslint-disable-next-line react-hooks/exhaustive-deps
@ -189,11 +193,18 @@ function SearchGithubRepoResultList({
value={githubRepoSearchString}
helperText={helperText}
/>
{(loading || isCreatingRepo) && <LinearProgress variant="query" />}
{(loading || isCreatingRepo) && <LinearProgress variant='query' />}
<List component="nav" aria-label="main mailbox folders">
<List component='nav' aria-label='main mailbox folders'>
{repoList.map(({ name, url }) => (
<ListItem button key={url} onClick={() => onSelectRepo(url, name)} selected={trim(githubWikiUrl) === trim(url)}>
<ListItem
button
key={url}
onClick={() => {
onSelectRepo(url, name);
}}
selected={trim(githubWikiUrl) === trim(url)}
>
<ListItemIcon>
<FolderIcon />
</ListItemIcon>
@ -220,20 +231,19 @@ function SearchGithubRepoResultList({
isCreatingRepoSetter(false);
githubWikiUrlSetter(wikiUrlToCreate);
}}
selected={isCreateNewRepo}>
selected={isCreateNewRepo}
>
<ListItemIcon>
<CreateNewFolderIcon />
</ListItemIcon>
<ListItemText
primary={`${
isCreateMainWorkspace ? t('AddWorkspace.CreatePublicRepository') : t('AddWorkspace.CreatePrivateRepository')
} ${githubRepoSearchString}`}
primary={`${isCreateMainWorkspace ? t('AddWorkspace.CreatePublicRepository') : t('AddWorkspace.CreatePrivateRepository')} ${githubRepoSearchString}`}
/>
</ListItem>
)}
</List>
{repoList.length === 0 && (
<ReloadButton color="secondary" endIcon={<CachedIcon />} onClick={async () => await refetchDebounced()}>
<ReloadButton color='secondary' endIcon={<CachedIcon />} onClick={async () => await refetchDebounced()}>
{t('AddWorkspace.Reload')}
</ReloadButton>
)}

View file

@ -1,12 +1,12 @@
import styled from 'styled-components';
import { useTranslation } from 'react-i18next';
import styled from 'styled-components';
import { TextField, Button } from '@material-ui/core';
import { Button, TextField } from '@material-ui/core';
import { SupportedStorageServices } from '@services/types';
import { useUserInfoObservable } from '@services/auth/hooks';
import { useAuth } from './gitTokenHooks';
import { getServiceBranchTypes, getServiceEmailTypes, getServiceTokenTypes, getServiceUserNameTypes } from '@services/auth/interface';
import { SupportedStorageServices } from '@services/types';
import { useAuth } from './gitTokenHooks';
const AuthingLoginButton = styled(Button)`
width: 100%;

View file

@ -1,8 +1,8 @@
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
import { useCallback, useMemo } from 'react';
import { AuthenticationClient } from 'authing-js-sdk';
import { APP_DOMAIN, APP_ID } from '@/constants/auth';
import { SupportedStorageServices } from '@services/types';
import { APP_ID, APP_DOMAIN } from '@/constants/auth';
import { AuthenticationClient } from 'authing-js-sdk';
import { useCallback, useMemo } from 'react';
export function useAuth(storageService: SupportedStorageServices): [() => Promise<void>, () => Promise<void>] {
const authing = useMemo(
@ -42,7 +42,9 @@ export function useAuth(storageService: SupportedStorageServices): [() => Promis
}
}
},
onError: (code, message) => onFailure(new Error(message + String(code))),
onError: (code, message) => {
onFailure(new Error(message + String(code)));
},
});
} catch (error) {
onFailure(error as Error);

View file

@ -1,10 +1,10 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import React, { useEffect, useState } from 'react';
import styled, { DefaultTheme, keyframes } from 'styled-components';
import { Tab as TabRaw, ListItemText as ListItemTextRaw } from '@material-ui/core';
import { TabPanel as TabPanelRaw, TabContext, TabList as TabListRaw } from '@material-ui/lab';
import { ListItemText as ListItemTextRaw, Tab as TabRaw } from '@material-ui/core';
import { TabContext, TabList as TabListRaw, TabPanel as TabPanelRaw } from '@material-ui/lab';
import { SupportedStorageServices } from '@services/types';
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import styled, { DefaultTheme, keyframes } from 'styled-components';
import { GitTokenForm } from './GitTokenForm';
@ -45,7 +45,8 @@ const TabsContainer = styled.div`
min-width: 160px;
}
`;
const backgroundColorShift = ({ theme }: { theme: DefaultTheme }) => keyframes`
const backgroundColorShift = ({ theme }: { theme: DefaultTheme }) =>
keyframes`
from {background-color: ${theme.palette.background.default};}
to {background-color: ${theme.palette.background.default};}
`;
@ -85,14 +86,17 @@ export function TokenForm({ storageProvider, storageProviderSetter }: Props): JS
<TabContext value={currentTab}>
<TabsContainer>
<TabList
onChange={(_event, newValue) => currentTabSetter(newValue as SupportedStorageServices)}
orientation="vertical"
variant="scrollable"
onChange={(_event, newValue) => {
currentTabSetter(newValue as SupportedStorageServices);
}}
orientation='vertical'
variant='scrollable'
value={currentTab}
aria-label="Vertical tabs example">
<Tab label="GitHub" value={SupportedStorageServices.github} />
<Tab label="GitLab" value={SupportedStorageServices.gitlab} />
<Tab label="Gitee" value={SupportedStorageServices.gitee} />
aria-label='Vertical tabs example'
>
<Tab label='GitHub' value={SupportedStorageServices.github} />
<Tab label='GitLab' value={SupportedStorageServices.gitlab} />
<Tab label='Gitee' value={SupportedStorageServices.gitee} />
</TabList>
<TabPanel value={SupportedStorageServices.github}>
<GitTokenForm storageService={SupportedStorageServices.github} />

View file

@ -1,10 +1,10 @@
import { useCallback, MouseEvent, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { WorkspaceSelector } from './WorkspaceSelector';
import { IWorkspace } from '@services/workspaces/interface';
import { getWorkspaceMenuTemplate, openWorkspaceTagTiddler } from '@services/workspaces/getWorkspaceMenuTemplate';
import { IWorkspace } from '@services/workspaces/interface';
import { MouseEvent, useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { WorkspaceSelector } from './WorkspaceSelector';
import defaultIcon from '@/images/default-icon.png';

View file

@ -1,6 +1,6 @@
import { DndContext, useSensor, useSensors, PointerSensor } from '@dnd-kit/core';
import { SortableContext, arrayMove, verticalListSortingStrategy } from '@dnd-kit/sortable';
import { DndContext, PointerSensor, useSensor, useSensors } from '@dnd-kit/core';
import { restrictToVerticalAxis } from '@dnd-kit/modifiers';
import { arrayMove, SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
import { IWorkspace, IWorkspaceWithMetadata } from '@services/workspaces/interface';
import { SortableWorkspaceSelector } from './SortableWorkspaceSelector';
@ -38,7 +38,8 @@ export function SortableWorkspaceSelectorList({ workspacesList, sidebarShortcutH
});
await window.service.workspace.setWorkspaces(newWorkspaces);
}}>
}}
>
<SortableContext items={workspaceIDs} strategy={verticalListSortingStrategy}>
{workspacesList
.sort((a, b) => a.order - b.order)

View file

@ -1,11 +1,11 @@
import Promise from 'bluebird';
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import BadgeRaw from '@material-ui/core/Badge';
import Promise from 'bluebird';
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import styled, { css, keyframes } from 'styled-components';
import defaultIcon from '../../images/default-icon.png';
import { getAssetsFileUrl } from '@/helpers/url';
import defaultIcon from '../../images/default-icon.png';
Promise.config({ cancellation: true });
@ -28,21 +28,21 @@ const Root = styled.div<{ active?: boolean; hibernated?: boolean; workspaceClick
border: 0;
border-color: transparent;
${({ hibernated }) =>
hibernated === true &&
css`
hibernated === true &&
css`
opacity: 0.4;
`}
${({ active }) =>
active === true &&
css`
active === true &&
css`
opacity: 1;
`}
box-sizing: border-box;
border-left: ${({ workspaceCount }) => (workspaceCount > 1 ? '3px' : '0')} solid
${({ active, theme }) => (active === true ? theme.palette.text.primary : 'transparent')};
${({ workspaceClickedLoading }) =>
workspaceClickedLoading === true &&
css`
workspaceClickedLoading === true &&
css`
&:hover {
cursor: wait;
}
@ -69,15 +69,15 @@ const Avatar = styled.div<IAvatarProps>`
text-transform: uppercase;
overflow: hidden;
${({ large }) =>
large === true &&
css`
large === true &&
css`
height: 44px;
width: 44px;
line-height: 44px;
`}
${({ transparent }) =>
transparent === true &&
css`
transparent === true &&
css`
background: transparent;
border: none;
border-radius: 0;
@ -91,9 +91,9 @@ const Avatar = styled.div<IAvatarProps>`
color: ${({ theme }) => theme.palette.common.black};
}
${({ addAvatar }: IAvatarProps) =>
addAvatar
? ''
: css`
addAvatar
? ''
: css`
background-color: transparent;
`}
`;
@ -102,8 +102,8 @@ const AvatarPicture = styled.img<{ large?: boolean }>`
height: calc(36px - 2px);
width: calc(36px - 2px);
${({ large }) =>
large === true &&
css`
large === true &&
css`
height: 44px;
width: 44px;
`}
@ -119,8 +119,8 @@ const ShortcutText = styled.p<{ active?: boolean }>`
word-break: break-all;
text-align: center;
${({ active }) =>
active === true &&
css`
active === true &&
css`
text-decoration: underline;
text-underline-offset: 0.2em;
`}
@ -172,28 +172,32 @@ export function WorkspaceSelector({
active={active}
onClick={workspaceClickedLoading ? () => {} : onClick}
workspaceClickedLoading={workspaceClickedLoading}
workspaceCount={workspaceCount}>
<Badge color="secondary" badgeContent={badgeCount} max={99}>
workspaceCount={workspaceCount}
>
<Badge color='secondary' badgeContent={badgeCount} max={99}>
{!hideSideBarIcon && (
<Avatar
large={!showSidebarShortcutHints}
transparent={transparentBackground}
addAvatar={id === 'add'}
highlightAdd={index === 0}
id={id === 'add' || id === 'guide' ? 'add-workspace-button' : `workspace-avatar-${id}`}>
{id === 'add' ? (
'+'
) : id === 'guide' ? (
'※'
) : (
<AvatarPicture alt="Icon" large={!showSidebarShortcutHints} src={getAssetsFileUrl(picturePath ?? defaultIcon)} draggable={false} />
)}
id={id === 'add' || id === 'guide' ? 'add-workspace-button' : `workspace-avatar-${id}`}
>
{id === 'add'
? (
'+'
)
: (id === 'guide'
? (
'※'
)
: <AvatarPicture alt='Icon' large={!showSidebarShortcutHints} src={getAssetsFileUrl(picturePath ?? defaultIcon)} draggable={false} />)}
</Avatar>
)}
</Badge>
{(showSidebarShortcutHints || hideSideBarIcon) && (
<ShortcutText active={active}>
{id === 'add' ? t('WorkspaceSelector.Add') : id === 'guide' ? t('WorkspaceSelector.Guide') : shortWorkspaceName}
{id === 'add' ? t('WorkspaceSelector.Add') : (id === 'guide' ? t('WorkspaceSelector.Guide') : shortWorkspaceName)}
</ShortcutText>
)}
</Root>

View file

@ -1,3 +1,3 @@
export * from './WorkspaceSelector';
export * from './SortableWorkspaceSelector';
export * from './SortableWorkspaceSelectorList';
export * from './WorkspaceSelector';

View file

@ -4,10 +4,10 @@ import { SVGContainer } from './SVGContainer';
function CommandPaletteSVG(): JSX.Element {
return (
<svg width="22pt" height="22pt" viewBox="0 0 512 512" style={{ transform: 'rotate(225deg)' }} fill="currentColor">
<svg width='22pt' height='22pt' viewBox='0 0 512 512' style={{ transform: 'rotate(225deg)' }} fill='currentColor'>
<path
d="M224 96l16-32 32-16-32-16-16-32-16 32-32 16 32 16 16 32zM80 160l26.66-53.33L160 80l-53.34-26.67L80 0 53.34 53.33 0 80l53.34 26.67L80 160zm0-96c8.84 0 16 7.16 16 16s-7.16 16-16 16-16-7.16-16-16 7.16-16 16-16zm352 224l-26.66 53.33L352 368l53.34 26.67L432 448l26.66-53.33L512 368l-53.34-26.67L432 288zm0 96c-8.84 0-16-7.16-16-16s7.16-16 16-16 16 7.16 16 16-7.16 16-16 16zm70.63-306.04L434.04 9.37C427.79 3.12 419.6 0 411.41 0s-16.38 3.12-22.63 9.37L9.37 388.79c-12.5 12.5-12.5 32.76 0 45.25l68.59 68.59c6.25 6.25 14.44 9.37 22.63 9.37s16.38-3.12 22.63-9.37l379.41-379.41c12.49-12.5 12.49-32.76 0-45.26zM100.59 480L32 411.41l258.38-258.4 68.6 68.6L100.59 480zm281.02-281.02l-68.6-68.6L411.38 32h.03L480 100.59l-98.39 98.39z"
fillRule="evenodd"
d='M224 96l16-32 32-16-32-16-16-32-16 32-32 16 32 16 16 32zM80 160l26.66-53.33L160 80l-53.34-26.67L80 0 53.34 53.33 0 80l53.34 26.67L80 160zm0-96c8.84 0 16 7.16 16 16s-7.16 16-16 16-16-7.16-16-16 7.16-16 16-16zm352 224l-26.66 53.33L352 368l53.34 26.67L432 448l26.66-53.33L512 368l-53.34-26.67L432 288zm0 96c-8.84 0-16-7.16-16-16s7.16-16 16-16 16 7.16 16 16-7.16 16-16 16zm70.63-306.04L434.04 9.37C427.79 3.12 419.6 0 411.41 0s-16.38 3.12-22.63 9.37L9.37 388.79c-12.5 12.5-12.5 32.76 0 45.25l68.59 68.59c6.25 6.25 14.44 9.37 22.63 9.37s16.38-3.12 22.63-9.37l379.41-379.41c12.49-12.5 12.49-32.76 0-45.26zM100.59 480L32 411.41l258.38-258.4 68.6 68.6L100.59 480zm281.02-281.02l-68.6-68.6L411.38 32h.03L480 100.59l-98.39 98.39z'
fillRule='evenodd'
/>
</svg>
);

View file

@ -7,7 +7,7 @@ import { sourcePath } from './paths';
export const USER_DATA_FOLDER = app.getPath('userData');
export const SETTINGS_FOLDER = isDevelopmentOrTest
? path.resolve(sourcePath, '..', developmentSettingFolderName)
: // eslint-disable-next-line @typescript-eslint/no-var-requires
path.resolve(USER_DATA_FOLDER, 'settings');
// eslint-disable-next-line @typescript-eslint/no-var-requires
: path.resolve(USER_DATA_FOLDER, 'settings');
export const LOCAL_GIT_DIRECTORY = path.resolve(isDevelopmentOrTest ? path.join(sourcePath, '..') : process.resourcesPath, 'node_modules', 'dugite', 'git');
export const LOG_FOLDER = isDevelopmentOrTest ? path.resolve(sourcePath, '..', 'logs') : path.resolve(USER_DATA_FOLDER, 'logs');

View file

@ -5,5 +5,5 @@
const electron = require('electron');
const { isPackaged } = require('electron-is-packaged');
export const isElectronDevelopment =
!isPackaged && (process.env.NODE_ENV === 'development' || (typeof electron === 'string' || electron.app === undefined ? false : !electron.app.isPackaged));
export const isElectronDevelopment = !isPackaged &&
(process.env.NODE_ENV === 'development' || (typeof electron === 'string' || electron.app === undefined ? false : !electron.app.isPackaged));

View file

@ -1,6 +1,6 @@
import { LOCALIZATION_FOLDER } from '@/constants/paths';
import fs from 'fs-extra';
import path from 'path';
import { LOCALIZATION_FOLDER } from '@/constants/paths';
export const supportedLanguagesMap = fs.readJsonSync(path.join(LOCALIZATION_FOLDER, 'supportedLanguages.json')) as Record<string, string>;
export const tiddlywikiLanguagesMap = fs.readJsonSync(path.join(LOCALIZATION_FOLDER, 'tiddlywikiLanguages.json')) as Record<string, string | undefined>;

View file

@ -1,8 +1,8 @@
import path from 'path';
import os from 'os';
import path from 'path';
import { isMac } from '../helpers/system';
import { isDevelopmentOrTest } from './environment';
import { developmentWikiFolderName, localizationFolderName } from './fileNames';
import { isMac } from '../helpers/system';
/** src folder */
export const sourcePath = path.resolve(__dirname, '..');

View file

@ -1,4 +1,4 @@
export enum WikiStateKey {
titleBarOpened = 'titleBarOpened',
sideBarOpened = 'sideBarOpened',
titleBarOpened = 'titleBarOpened',
}

View file

@ -1,13 +1,13 @@
import fs from 'fs-extra';
import settings from 'electron-settings';
import { parse as bestEffortJsonParser } from 'best-effort-json-parser';
import { SETTINGS_FOLDER } from '@/constants/appPaths';
import { logger } from '@services/libs/log';
import { parse as bestEffortJsonParser } from 'best-effort-json-parser';
import settings from 'electron-settings';
import fs from 'fs-extra';
import { isWin } from './system';
export function fixSettingFileWhenError(jsonError: Error): void {
logger.error('Setting file format bad: ' + jsonError.message);
const jsonContent = fs.readFileSync(settings.file(), 'utf-8');
const jsonContent = fs.readFileSync(settings.file(), 'utf8');
logger.info('Try to fix JSON content.');
try {
const repaired = bestEffortJsonParser(jsonContent) as Record<string, unknown>;

View file

@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
import { platform, type, networkInterfaces } from 'os';
import ip from 'ipaddr.js';
import { networkInterfaces, platform, type } from 'os';
/**
* Copy from https://github.com/sindresorhus/internal-ip, to fi xsilverwind/default-gateway 's bug

View file

@ -1,5 +1,5 @@
import { app } from 'electron';
import { logger } from '@services/libs/log';
import { app } from 'electron';
const gotTheLock = app.requestSingleInstanceLock();

View file

@ -1,6 +1,6 @@
export function extractDomain(fullUrl: string | undefined): string | undefined {
const matches = /^([a-zA-Z\-]+):\/\/([^#/?]+)(?:[#/?]|$)/i.exec(fullUrl ?? '');
const domain = matches !== null ? matches[1] : undefined;
const matches = /^([a-z\-]+):\/\/([^#/?]+)(?:[#/?]|$)/i.exec(fullUrl ?? '');
const domain = matches === null ? undefined : matches[1];
// https://stackoverflow.com/a/9928725
return typeof domain === 'string' ? domain.replace(/^(www\.)/, '') : undefined;
}

View file

@ -1,6 +1,6 @@
import { useEffect, useState, useCallback } from 'react';
import { AsyncReturnType } from 'type-fest';
import useDebouncedCallback from 'beautiful-react-hooks/useDebouncedCallback';
import { useCallback, useEffect, useState } from 'react';
import { AsyncReturnType } from 'type-fest';
/**
* Use value from service, especially constant value that never changes

View file

@ -1,34 +1,34 @@
/* eslint-disable unicorn/prefer-top-level-await */
/* eslint-disable @typescript-eslint/no-misused-promises */
import { uninstall } from './helpers/installV8Cache';
import 'source-map-support/register';
import 'reflect-metadata';
import './helpers/singleInstance';
import './helpers/configSetting';
import fs from 'fs-extra';
import path from 'path';
import { ipcMain, protocol, powerMonitor, app } from 'electron';
import { app, ipcMain, powerMonitor, protocol } from 'electron';
import settings from 'electron-settings';
import unhandled from 'electron-unhandled';
import fs from 'fs-extra';
import path from 'path';
import { buildLanguageMenu } from '@services/menu/buildLanguageMenu';
import { MainChannel } from '@/constants/channels';
import { isTest } from '@/constants/environment';
import { container } from '@services/container';
import { logger } from '@services/libs/log';
import { initRendererI18NHandler } from '@services/libs/i18n';
import { logger } from '@services/libs/log';
import { buildLanguageMenu } from '@services/menu/buildLanguageMenu';
import { bindServiceAndProxy } from '@services/libs/bindServiceAndProxy';
import serviceIdentifier from '@services/serviceIdentifier';
import { WindowNames } from '@services/windows/WindowProperties';
import { bindServiceAndProxy } from '@services/libs/bindServiceAndProxy';
import type { IPreferenceService } from './services/preferences/interface';
import type { IWikiService } from './services/wiki/interface';
import type { IWindowService } from './services/windows/interface';
import type { IWorkspaceViewService } from './services/workspacesView/interface';
import type { IUpdaterService } from '@services/updater/interface';
import { reportErrorToGithubWithTemplates } from '@services/native/reportError';
import type { IUpdaterService } from '@services/updater/interface';
import { IWikiGitWorkspaceService } from '@services/wikiGitWorkspace/interface';
import { isLinux, isMac } from './helpers/system';
import type { IPreferenceService } from './services/preferences/interface';
import type { IWindowService } from './services/windows/interface';
import type { IWorkspaceViewService } from './services/workspacesView/interface';
logger.info('App booting');
@ -82,9 +82,10 @@ ipcMain.once(MainChannel.commonInitFinished, () => {
*/
const whenCommonInitFinished = async (): Promise<void> => {
if (commonInitFinished) {
return await Promise.resolve();
await Promise.resolve();
return;
}
return await new Promise((resolve) => {
await new Promise<void>((resolve) => {
ipcMain.once(MainChannel.commonInitFinished, () => {
commonInitFinished = true;
resolve();
@ -112,8 +113,8 @@ const commonInit = async (): Promise<void> => {
// if user want a menubar, we create a new window for that
await Promise.all([
windowService.open(WindowNames.main),
preferenceService.get('attachToMenubar').then((attachToMenubar) => {
attachToMenubar && windowService.open(WindowNames.menuBar);
preferenceService.get('attachToMenubar').then(async (attachToMenubar) => {
attachToMenubar && await windowService.open(WindowNames.menuBar);
}),
]);
// perform wiki startup and git sync for each workspace
@ -160,7 +161,9 @@ app.on('ready', async () => {
}
await updaterService.checkForUpdates();
})
.catch((error) => console.error(error));
.catch((error) => {
console.error(error);
});
powerMonitor.on('shutdown', () => {
app.quit();
});

View file

@ -1,10 +1,10 @@
import React from 'react';
import styled from 'styled-components';
import { Trans, useTranslation } from 'react-i18next';
import { Helmet } from 'react-helmet';
import { Trans, useTranslation } from 'react-i18next';
import styled from 'styled-components';
import { Button, DialogContent as DialogContentRaw } from '@material-ui/core';
import { usePromiseValue } from '@/helpers/useServiceValue';
import { Button, DialogContent as DialogContentRaw } from '@material-ui/core';
import iconPath from '../../build-resources/icon.png';
const DialogContent = styled(DialogContentRaw)`
@ -81,11 +81,11 @@ export default function About(): JSX.Element {
return (
<DialogContent>
<div id="test" data-usage="For spectron automating testing" />
<div id='test' data-usage='For spectron automating testing' />
<Helmet>
<title>{t('ContextMenu.About')}</title>
</Helmet>
<Icon src={iconPath} alt="TidGi" />
<Icon src={iconPath} alt='TidGi' />
<Title>TidGi ({platform ?? 'Unknown Platform'})</Title>
<TidGiVersion>{`Version v${appVersion ?? ' - '}.`}</TidGiVersion>
<DependenciesVersionsContainer>
@ -97,45 +97,59 @@ export default function About(): JSX.Element {
</DependenciesVersionsContainer>
<ButtonContainer>
<GoToTheWebsiteButton onClick={async () => await window.service.native.open('https://github.com/tiddly-gittly/TidGi-Desktop')}>
<GoToTheWebsiteButton
onClick={async () => {
await window.service.native.open('https://github.com/tiddly-gittly/TidGi-Desktop');
}}
>
Website
</GoToTheWebsiteButton>
<GoToTheWebsiteButton onClick={async () => await window.service.native.open('https://github.com/tiddly-gittly/TidGi-Desktop/issues/new/choose')}>
<GoToTheWebsiteButton
onClick={async () => {
await window.service.native.open('https://github.com/tiddly-gittly/TidGi-Desktop/issues/new/choose');
}}
>
Support
</GoToTheWebsiteButton>
</ButtonContainer>
<MadeBy>
<Trans t={t} i18nKey="Dialog.MadeWithLove">
<span>Made with </span>
<span role="img" aria-label="love">
<Trans t={t} i18nKey='Dialog.MadeWithLove'>
<span>Made with</span>
<span role='img' aria-label='love'>
</span>
<span> by </span>
<span>by</span>
</Trans>
<Link
onClick={async () => await window.service.native.open('https://onetwo.ren/wiki/')}
onClick={async () => {
await window.service.native.open('https://onetwo.ren/wiki/');
}}
onKeyDown={async (event) => {
if (event.key !== 'Enter') {
return;
}
await window.service.native.open('https://onetwo.ren/wiki/');
}}
role="link"
tabIndex={0}>
role='link'
tabIndex={0}
>
{t('LinOnetwo')}
</Link>
<span> && </span>
<span>&&</span>
<Link
onClick={async () => await window.service.native.open('https://webcatalog.app/?utm_source=tidgi_app')}
onClick={async () => {
await window.service.native.open('https://webcatalog.app/?utm_source=tidgi_app');
}}
onKeyDown={async (event) => {
if (event.key !== 'Enter') {
return;
}
await window.service.native.open('https://webcatalog.app/?utm_source=tidgi_app');
}}
role="link"
tabIndex={0}>
role='link'
tabIndex={0}
>
{t('Preference.WebCatalog')}
</Link>
</MadeBy>

View file

@ -1,13 +1,13 @@
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
import { useTranslation } from 'react-i18next';
import { Typography, LinearProgress, Snackbar } from '@material-ui/core';
import { LinearProgress, Snackbar, Typography } from '@material-ui/core';
import Alert from '@material-ui/lab/Alert';
import { CloseButton, ReportErrorFabButton, WikiLocation } from './FormComponents';
import { useCloneWiki, useValidateCloneWiki } from './useCloneWiki';
import type { IWikiWorkspaceFormProps } from './useForm';
import { useValidateCloneWiki, useCloneWiki } from './useCloneWiki';
import { useWikiCreationProgress } from './useIndicator';
import { WikiLocation, CloseButton, ReportErrorFabButton } from './FormComponents';
export function CloneWikiDoneButton({ form, isCreateMainWorkspace, errorInWhichComponentSetter }: IWikiWorkspaceFormProps): JSX.Element {
const { t } = useTranslation();
@ -21,7 +21,7 @@ export function CloneWikiDoneButton({ form, isCreateMainWorkspace, errorInWhichC
if (hasError) {
return (
<>
<CloseButton variant="contained" disabled>
<CloseButton variant='contained' disabled>
{wikiCreationMessage}
</CloseButton>
{wikiCreationMessage !== undefined && <ReportErrorFabButton message={wikiCreationMessage} />}
@ -30,29 +30,37 @@ export function CloneWikiDoneButton({ form, isCreateMainWorkspace, errorInWhichC
}
return (
<>
{inProgressOrError && <LinearProgress color="secondary" />}
<Snackbar open={logPanelOpened} autoHideDuration={5000} onClose={() => logPanelSetter(false)}>
<Alert severity="info">{wikiCreationMessage}</Alert>
{inProgressOrError && <LinearProgress color='secondary' />}
<Snackbar
open={logPanelOpened}
autoHideDuration={5000}
onClose={() => {
logPanelSetter(false);
}}
>
<Alert severity='info'>{wikiCreationMessage}</Alert>
</Snackbar>
{isCreateMainWorkspace ? (
<CloseButton variant="contained" color="secondary" disabled={inProgressOrError} onClick={onSubmit}>
<Typography variant="body1" display="inline">
{t('AddWorkspace.CloneWiki')}
</Typography>
<WikiLocation>{form.wikiFolderLocation}</WikiLocation>
</CloseButton>
) : (
<CloseButton variant="contained" color="secondary" disabled={inProgressOrError} onClick={onSubmit}>
<Typography variant="body1" display="inline">
{t('AddWorkspace.CloneWiki')}
</Typography>
<WikiLocation>{form.wikiFolderLocation}</WikiLocation>
<Typography variant="body1" display="inline">
{t('AddWorkspace.AndLinkToMainWorkspace')}
</Typography>
</CloseButton>
)}
{isCreateMainWorkspace
? (
<CloseButton variant='contained' color='secondary' disabled={inProgressOrError} onClick={onSubmit}>
<Typography variant='body1' display='inline'>
{t('AddWorkspace.CloneWiki')}
</Typography>
<WikiLocation>{form.wikiFolderLocation}</WikiLocation>
</CloseButton>
)
: (
<CloseButton variant='contained' color='secondary' disabled={inProgressOrError} onClick={onSubmit}>
<Typography variant='body1' display='inline'>
{t('AddWorkspace.CloneWiki')}
</Typography>
<WikiLocation>{form.wikiFolderLocation}</WikiLocation>
<Typography variant='body1' display='inline'>
{t('AddWorkspace.AndLinkToMainWorkspace')}
</Typography>
</CloseButton>
)}
</>
);
}

View file

@ -1,20 +1,13 @@
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
import React, { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Typography, MenuItem } from '@material-ui/core';
import { MenuItem, Typography } from '@material-ui/core';
import { Folder as FolderIcon } from '@material-ui/icons';
import React from 'react';
import { useTranslation } from 'react-i18next';
import {
CreateContainer,
LocationPickerContainer,
LocationPickerInput,
LocationPickerButton,
SoftLinkToMainWikiSelect,
SubWikiTagAutoComplete,
} from './FormComponents';
import { CreateContainer, LocationPickerButton, LocationPickerContainer, LocationPickerInput, SoftLinkToMainWikiSelect, SubWikiTagAutoComplete } from './FormComponents';
import type { IWikiWorkspaceFormProps } from './useForm';
import { useValidateCloneWiki } from './useCloneWiki';
import type { IWikiWorkspaceFormProps } from './useForm';
export function CloneWikiForm({ form, isCreateMainWorkspace, errorInWhichComponent, errorInWhichComponentSetter }: IWikiWorkspaceFormProps): JSX.Element {
const { t } = useTranslation();
@ -24,7 +17,9 @@ export function CloneWikiForm({ form, isCreateMainWorkspace, errorInWhichCompone
<LocationPickerContainer>
<LocationPickerInput
error={errorInWhichComponent.parentFolderLocation}
onChange={(event) => form.parentFolderLocationSetter(event.target.value)}
onChange={(event) => {
form.parentFolderLocationSetter(event.target.value);
}}
label={t('AddWorkspace.WorkspaceParentFolder')}
value={form.parentFolderLocation}
/>
@ -37,8 +32,9 @@ export function CloneWikiForm({ form, isCreateMainWorkspace, errorInWhichCompone
form.parentFolderLocationSetter(filePaths[0]);
}
}}
endIcon={<FolderIcon />}>
<Typography variant="button" display="inline">
endIcon={<FolderIcon />}
>
<Typography variant='button' display='inline'>
{t('AddWorkspace.Choose')}
</Typography>
</LocationPickerButton>
@ -46,7 +42,9 @@ export function CloneWikiForm({ form, isCreateMainWorkspace, errorInWhichCompone
<LocationPickerContainer>
<LocationPickerInput
error={errorInWhichComponent.wikiFolderName}
onChange={(event) => form.wikiFolderNameSetter(event.target.value)}
onChange={(event) => {
form.wikiFolderNameSetter(event.target.value);
}}
label={t('AddWorkspace.WorkspaceFolderNameToCreate')}
helperText={`${t('AddWorkspace.CloneWiki')}${form.wikiFolderLocation ?? ''}`}
value={form.wikiFolderName}
@ -57,16 +55,15 @@ export function CloneWikiForm({ form, isCreateMainWorkspace, errorInWhichCompone
<SoftLinkToMainWikiSelect
error={errorInWhichComponent.mainWikiToLink}
label={t('AddWorkspace.MainWorkspaceLocation')}
helperText={
form.mainWikiToLink.wikiFolderLocation &&
helperText={form.mainWikiToLink.wikiFolderLocation &&
`${t('AddWorkspace.SubWorkspaceWillLinkTo')}
${form.mainWikiToLink.wikiFolderLocation}/tiddlers/${form.wikiFolderName}`
}
${form.mainWikiToLink.wikiFolderLocation}/tiddlers/${form.wikiFolderName}`}
value={form.mainWikiToLinkIndex}
onChange={(event) => {
const index = event.target.value as unknown as number;
form.mainWikiToLinkSetter(form.mainWorkspaceList[index]);
}}>
}}
>
{form.mainWorkspaceList.map((workspace, index) => (
<MenuItem key={index} value={index}>
{workspace.name}
@ -76,7 +73,9 @@ export function CloneWikiForm({ form, isCreateMainWorkspace, errorInWhichCompone
<SubWikiTagAutoComplete
options={form.fileSystemPaths.map((fileSystemPath) => fileSystemPath.tagName)}
value={form.tagName}
onInputChange={(_, value) => form.tagNameSetter(value)}
onInputChange={(_, value) => {
form.tagNameSetter(value);
}}
renderInput={(parameters) => (
<LocationPickerInput
{...parameters}

View file

@ -1,9 +1,9 @@
import React from 'react';
import styled from 'styled-components';
import { useTranslation } from 'react-i18next';
import styled from 'styled-components';
import Paper from '@material-ui/core/Paper';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Paper from '@material-ui/core/Paper';
import SwitchRaw from '@material-ui/core/Switch';
import Typography from '@material-ui/core/Typography';
@ -36,10 +36,17 @@ export function MainSubWikiDescription({
return (
<Container elevation={0} square>
<FormControlLabel
control={<Switch checked={isCreateMainWorkspace} onChange={(event) => isCreateMainWorkspaceSetter(event.target.checked)} />}
control={
<Switch
checked={isCreateMainWorkspace}
onChange={(event) => {
isCreateMainWorkspaceSetter(event.target.checked);
}}
/>
}
label={label}
/>
<Typography variant="body2" display="inline">
<Typography variant='body2' display='inline'>
{description}
</Typography>
</Container>
@ -63,10 +70,17 @@ export function SyncedWikiDescription({
return (
<Container elevation={0} square>
<FormControlLabel
control={<Switch checked={isCreateSyncedWorkspace} onChange={(event) => isCreateSyncedWorkspaceSetter(event.target.checked)} />}
control={
<Switch
checked={isCreateSyncedWorkspace}
onChange={(event) => {
isCreateSyncedWorkspaceSetter(event.target.checked);
}}
/>
}
label={label}
/>
<Typography variant="body2" display="inline">
<Typography variant='body2' display='inline'>
{description}
</Typography>
</Container>

View file

@ -1,12 +1,12 @@
import { useTranslation } from 'react-i18next';
import { Typography, LinearProgress, Snackbar } from '@material-ui/core';
import { LinearProgress, Snackbar, Typography } from '@material-ui/core';
import Alert from '@material-ui/lab/Alert';
import { CloseButton, ReportErrorFabButton, WikiLocation } from './FormComponents';
import { useExistedWiki, useValidateExistedWiki } from './useExistedWiki';
import type { IWikiWorkspaceFormProps } from './useForm';
import { useValidateExistedWiki, useExistedWiki } from './useExistedWiki';
import { useWikiCreationProgress } from './useIndicator';
import { WikiLocation, CloseButton, ReportErrorFabButton } from './FormComponents';
export function ExistedWikiDoneButton({
form,
@ -26,7 +26,7 @@ export function ExistedWikiDoneButton({
if (hasError) {
return (
<>
<CloseButton variant="contained" disabled>
<CloseButton variant='contained' disabled>
{wikiCreationMessage}
</CloseButton>
{wikiCreationMessage !== undefined && <ReportErrorFabButton message={wikiCreationMessage} />}
@ -35,29 +35,37 @@ export function ExistedWikiDoneButton({
}
return (
<>
{inProgressOrError && <LinearProgress color="secondary" />}
<Snackbar open={logPanelOpened} autoHideDuration={5000} onClose={() => logPanelSetter(false)}>
<Alert severity="info">{wikiCreationMessage}</Alert>
{inProgressOrError && <LinearProgress color='secondary' />}
<Snackbar
open={logPanelOpened}
autoHideDuration={5000}
onClose={() => {
logPanelSetter(false);
}}
>
<Alert severity='info'>{wikiCreationMessage}</Alert>
</Snackbar>
{isCreateMainWorkspace ? (
<CloseButton variant="contained" color="secondary" disabled={inProgressOrError} onClick={onSubmit}>
<Typography variant="body1" display="inline">
{t('AddWorkspace.ImportWiki')}
</Typography>
<WikiLocation>{form.wikiFolderLocation}</WikiLocation>
</CloseButton>
) : (
<CloseButton variant="contained" color="secondary" disabled={inProgressOrError} onClick={onSubmit}>
<Typography variant="body1" display="inline">
{t('AddWorkspace.ImportWiki')}
</Typography>
<WikiLocation>{form.wikiFolderLocation}</WikiLocation>
<Typography variant="body1" display="inline">
{t('AddWorkspace.AndLinkToMainWorkspace')}
</Typography>
</CloseButton>
)}
{isCreateMainWorkspace
? (
<CloseButton variant='contained' color='secondary' disabled={inProgressOrError} onClick={onSubmit}>
<Typography variant='body1' display='inline'>
{t('AddWorkspace.ImportWiki')}
</Typography>
<WikiLocation>{form.wikiFolderLocation}</WikiLocation>
</CloseButton>
)
: (
<CloseButton variant='contained' color='secondary' disabled={inProgressOrError} onClick={onSubmit}>
<Typography variant='body1' display='inline'>
{t('AddWorkspace.ImportWiki')}
</Typography>
<WikiLocation>{form.wikiFolderLocation}</WikiLocation>
<Typography variant='body1' display='inline'>
{t('AddWorkspace.AndLinkToMainWorkspace')}
</Typography>
</CloseButton>
)}
</>
);
}

View file

@ -1,20 +1,13 @@
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
import { MenuItem, Typography } from '@material-ui/core';
import { Folder as FolderIcon } from '@material-ui/icons';
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { Typography, MenuItem } from '@material-ui/core';
import { Folder as FolderIcon } from '@material-ui/icons';
import {
CreateContainer,
LocationPickerContainer,
LocationPickerInput,
LocationPickerButton,
SoftLinkToMainWikiSelect,
SubWikiTagAutoComplete,
} from './FormComponents';
import { CreateContainer, LocationPickerButton, LocationPickerContainer, LocationPickerInput, SoftLinkToMainWikiSelect, SubWikiTagAutoComplete } from './FormComponents';
import type { IWikiWorkspaceFormProps } from './useForm';
import { useValidateExistedWiki } from './useExistedWiki';
import type { IWikiWorkspaceFormProps } from './useForm';
export function ExistedWikiForm({
form,
@ -73,8 +66,9 @@ export function ExistedWikiForm({
onLocationChange(filePaths[0]);
}
}}
endIcon={<FolderIcon />}>
<Typography variant="button" display="inline">
endIcon={<FolderIcon />}
>
<Typography variant='button' display='inline'>
{t('AddWorkspace.Choose')}
</Typography>
</LocationPickerButton>
@ -84,16 +78,15 @@ export function ExistedWikiForm({
<SoftLinkToMainWikiSelect
error={errorInWhichComponent.mainWikiToLink}
label={t('AddWorkspace.MainWorkspaceLocation')}
helperText={
mainWikiToLink.wikiFolderLocation &&
helperText={mainWikiToLink.wikiFolderLocation &&
`${t('AddWorkspace.SubWorkspaceWillLinkTo')}
${mainWikiToLink.wikiFolderLocation}/tiddlers/${wikiFolderName}`
}
${mainWikiToLink.wikiFolderLocation}/tiddlers/${wikiFolderName}`}
value={mainWikiToLinkIndex}
onChange={(event) => {
const index = event.target.value as unknown as number;
mainWikiToLinkSetter(mainWorkspaceList[index]);
}}>
}}
>
{mainWorkspaceList.map((workspace, index) => (
<MenuItem key={index} value={index}>
{workspace.name}
@ -103,7 +96,9 @@ export function ExistedWikiForm({
<SubWikiTagAutoComplete
options={fileSystemPaths.map((fileSystemPath) => fileSystemPath.tagName)}
value={tagName}
onInputChange={(_, value) => tagNameSetter(value)}
onInputChange={(_, value) => {
tagNameSetter(value);
}}
renderInput={(parameters) => (
<LocationPickerInput
{...parameters}

View file

@ -1,6 +1,6 @@
import styled, { css } from 'styled-components';
import { Paper, Button, TextField, Autocomplete, Typography, Fab, Tooltip } from '@material-ui/core';
import { Autocomplete, Button, Fab, Paper, TextField, Tooltip, Typography } from '@material-ui/core';
import { useTranslation } from 'react-i18next';
import styled, { css } from 'styled-components';
export const CreateContainer = styled(Paper)`
padding: 10px;
@ -28,9 +28,9 @@ export const LocationPickerButton = styled(Button)`
`;
export const CloseButton = styled(Button)`
${({ disabled }) =>
disabled === true
? ''
: css`
disabled === true
? ''
: css`
white-space: nowrap;
`}
width: 100%;
@ -61,12 +61,13 @@ export function ReportErrorButton(props: { message: string }): JSX.Element {
return (
<Tooltip title={(t('Dialog.ReportBugDetail') ?? '') + (t('Menu.ReportBugViaGithub') ?? '')}>
<Button
color="secondary"
color='secondary'
onClick={() => {
const error = new Error(props.message);
error.stack = 'ReportErrorButton';
void window.service.native.openNewGitHubIssue(error);
}}>
}}
>
{t('Dialog.ReportBug')}
</Button>
</Tooltip>
@ -85,12 +86,13 @@ export function ReportErrorFabButton(props: { message: string }): JSX.Element {
return (
<Tooltip title={(t('Dialog.ReportBugDetail') ?? '') + (t('Menu.ReportBugViaGithub') ?? '')}>
<AbsoluteFab
color="default"
color='default'
onClick={() => {
const error = new Error(props.message);
error.stack = 'ReportErrorButton';
void window.service.native.openNewGitHubIssue(error);
}}>
}}
>
{t('Dialog.ReportBug')}
</AbsoluteFab>
</Tooltip>

View file

@ -26,7 +26,14 @@ export function GitRepoUrlForm({
return (
<CreateContainer elevation={2} square>
<LocationPickerContainer>
<LocationPickerInput error={error} onChange={(event) => gitRepoUrlSetter(event.target.value)} label={t('AddWorkspace.GitRepoUrl')} value={gitRepoUrl} />
<LocationPickerInput
error={error}
onChange={(event) => {
gitRepoUrlSetter(event.target.value);
}}
label={t('AddWorkspace.GitRepoUrl')}
value={gitRepoUrl}
/>
</LocationPickerContainer>
{storageProvider === SupportedStorageServices.github && (
<SearchGithubRepo

View file

@ -1,12 +1,12 @@
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
import { useTranslation } from 'react-i18next';
import Alert from '@material-ui/lab/Alert';
import { useTranslation } from 'react-i18next';
import { Typography, LinearProgress, Snackbar } from '@material-ui/core';
import { LinearProgress, Snackbar, Typography } from '@material-ui/core';
import { CloseButton, ReportErrorFabButton, WikiLocation } from './FormComponents';
import type { IWikiWorkspaceFormProps } from './useForm';
import { useValidateHtmlWiki, useImportHtmlWiki } from './useImportHtmlWiki';
import { useImportHtmlWiki, useValidateHtmlWiki } from './useImportHtmlWiki';
import { useWikiCreationProgress } from './useIndicator';
import { WikiLocation, CloseButton, ReportErrorFabButton } from './FormComponents';
export function ImportHtmlWikiDoneButton({
form,
@ -33,7 +33,7 @@ export function ImportHtmlWikiDoneButton({
if (hasError) {
return (
<>
<CloseButton variant="contained" disabled>
<CloseButton variant='contained' disabled>
{wikiCreationMessage}
</CloseButton>
{wikiCreationMessage !== undefined && <ReportErrorFabButton message={wikiCreationMessage} />}
@ -42,14 +42,20 @@ export function ImportHtmlWikiDoneButton({
}
return (
<>
{inProgressOrError && <LinearProgress color="secondary" />}
{inProgressOrError && <LinearProgress color='secondary' />}
{/* 这个好像是log面板 */}
<Snackbar open={logPanelOpened} autoHideDuration={5000} onClose={() => logPanelSetter(false)}>
<Alert severity="info">{wikiCreationMessage}</Alert>
<Snackbar
open={logPanelOpened}
autoHideDuration={5000}
onClose={() => {
logPanelSetter(false);
}}
>
<Alert severity='info'>{wikiCreationMessage}</Alert>
</Snackbar>
<CloseButton variant="contained" color="secondary" disabled={inProgressOrError} onClick={onSubmit}>
<Typography variant="body1" display="inline">
<CloseButton variant='contained' color='secondary' disabled={inProgressOrError} onClick={onSubmit}>
<Typography variant='body1' display='inline'>
{t('AddWorkspace.ImportWiki')}
</Typography>
<WikiLocation>{form.wikiHtmlPath}</WikiLocation>

View file

@ -1,9 +1,9 @@
import { useTranslation } from 'react-i18next';
import { Typography } from '@material-ui/core';
import { Folder as FolderIcon } from '@material-ui/icons';
import { useTranslation } from 'react-i18next';
import { useValidateHtmlWiki } from './useImportHtmlWiki';
import { CreateContainer, LocationPickerContainer, LocationPickerInput, LocationPickerButton } from './FormComponents';
import { CreateContainer, LocationPickerButton, LocationPickerContainer, LocationPickerInput } from './FormComponents';
import type { IWikiWorkspaceFormProps } from './useForm';
@ -45,8 +45,9 @@ export function ImportHtmlWikiForm({
}
}
}}
endIcon={<FolderIcon />}>
<Typography variant="button" display="inline">
endIcon={<FolderIcon />}
>
<Typography variant='button' display='inline'>
{t('AddWorkspace.Choose')}
</Typography>
</LocationPickerButton>
@ -54,7 +55,9 @@ export function ImportHtmlWikiForm({
<LocationPickerContainer>
<LocationPickerInput
error={errorInWhichComponent.parentFolderLocation}
onChange={(event) => form.parentFolderLocationSetter(event.target.value)}
onChange={(event) => {
form.parentFolderLocationSetter(event.target.value);
}}
label={t('AddWorkspace.WorkspaceParentFolder')}
value={parentFolderLocation}
/>
@ -67,8 +70,9 @@ export function ImportHtmlWikiForm({
form.parentFolderLocationSetter(filePaths[0]);
}
}}
endIcon={<FolderIcon />}>
<Typography variant="button" display="inline">
endIcon={<FolderIcon />}
>
<Typography variant='button' display='inline'>
{t('AddWorkspace.Choose')}
</Typography>
</LocationPickerButton>
@ -76,7 +80,9 @@ export function ImportHtmlWikiForm({
<LocationPickerContainer>
<LocationPickerInput
error={errorInWhichComponent.wikiFolderName}
onChange={(event) => wikiFolderNameSetter(event.target.value)}
onChange={(event) => {
wikiFolderNameSetter(event.target.value);
}}
label={t('AddWorkspace.ExtractedWikiFolderName')}
helperText={`${t('AddWorkspace.CreateWiki')}${wikiFolderLocation ?? ''}`}
value={wikiFolderName}

View file

@ -1,13 +1,13 @@
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
import { useTranslation } from 'react-i18next';
import { Typography, LinearProgress, Snackbar } from '@material-ui/core';
import { LinearProgress, Snackbar, Typography } from '@material-ui/core';
import Alert from '@material-ui/lab/Alert';
import { CloseButton, ReportErrorFabButton, WikiLocation } from './FormComponents';
import type { IWikiWorkspaceFormProps } from './useForm';
import { useValidateNewWiki, useNewWiki } from './useNewWiki';
import { useWikiCreationProgress } from './useIndicator';
import { WikiLocation, CloseButton, ReportErrorFabButton } from './FormComponents';
import { useNewWiki, useValidateNewWiki } from './useNewWiki';
export function NewWikiDoneButton({
form,
@ -27,7 +27,7 @@ export function NewWikiDoneButton({
if (hasError) {
return (
<>
<CloseButton variant="contained" disabled={true}>
<CloseButton variant='contained' disabled={true}>
{wikiCreationMessage}
</CloseButton>
{wikiCreationMessage !== undefined && <ReportErrorFabButton message={wikiCreationMessage} />}
@ -36,29 +36,37 @@ export function NewWikiDoneButton({
}
return (
<>
{inProgressOrError && <LinearProgress color="secondary" />}
<Snackbar open={logPanelOpened} autoHideDuration={5000} onClose={() => logPanelSetter(false)}>
<Alert severity="info">{wikiCreationMessage}</Alert>
{inProgressOrError && <LinearProgress color='secondary' />}
<Snackbar
open={logPanelOpened}
autoHideDuration={5000}
onClose={() => {
logPanelSetter(false);
}}
>
<Alert severity='info'>{wikiCreationMessage}</Alert>
</Snackbar>
{isCreateMainWorkspace ? (
<CloseButton variant="contained" color="secondary" disabled={inProgressOrError} onClick={onSubmit}>
<Typography variant="body1" display="inline">
{t('AddWorkspace.CreateWiki')}
</Typography>
<WikiLocation>{form.wikiFolderLocation}</WikiLocation>
</CloseButton>
) : (
<CloseButton variant="contained" color="secondary" disabled={inProgressOrError} onClick={onSubmit}>
<Typography variant="body1" display="inline">
{t('AddWorkspace.CreateWiki')}
</Typography>
<WikiLocation>{form.wikiFolderLocation}</WikiLocation>
<Typography variant="body1" display="inline">
{t('AddWorkspace.AndLinkToMainWorkspace')}
</Typography>
</CloseButton>
)}
{isCreateMainWorkspace
? (
<CloseButton variant='contained' color='secondary' disabled={inProgressOrError} onClick={onSubmit}>
<Typography variant='body1' display='inline'>
{t('AddWorkspace.CreateWiki')}
</Typography>
<WikiLocation>{form.wikiFolderLocation}</WikiLocation>
</CloseButton>
)
: (
<CloseButton variant='contained' color='secondary' disabled={inProgressOrError} onClick={onSubmit}>
<Typography variant='body1' display='inline'>
{t('AddWorkspace.CreateWiki')}
</Typography>
<WikiLocation>{form.wikiFolderLocation}</WikiLocation>
<Typography variant='body1' display='inline'>
{t('AddWorkspace.AndLinkToMainWorkspace')}
</Typography>
</CloseButton>
)}
</>
);
}

View file

@ -1,16 +1,9 @@
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
import { useTranslation } from 'react-i18next';
import { Typography, MenuItem } from '@material-ui/core';
import { MenuItem, Typography } from '@material-ui/core';
import { Folder as FolderIcon } from '@material-ui/icons';
import { useTranslation } from 'react-i18next';
import {
CreateContainer,
LocationPickerContainer,
LocationPickerInput,
LocationPickerButton,
SoftLinkToMainWikiSelect,
SubWikiTagAutoComplete,
} from './FormComponents';
import { CreateContainer, LocationPickerButton, LocationPickerContainer, LocationPickerInput, SoftLinkToMainWikiSelect, SubWikiTagAutoComplete } from './FormComponents';
import type { IWikiWorkspaceFormProps } from './useForm';
import { useValidateNewWiki } from './useNewWiki';
@ -29,7 +22,9 @@ export function NewWikiForm({
<LocationPickerContainer>
<LocationPickerInput
error={errorInWhichComponent.parentFolderLocation}
onChange={(event) => form.parentFolderLocationSetter(event.target.value)}
onChange={(event) => {
form.parentFolderLocationSetter(event.target.value);
}}
label={t('AddWorkspace.WorkspaceParentFolder')}
value={form.parentFolderLocation}
/>
@ -42,8 +37,9 @@ export function NewWikiForm({
form.parentFolderLocationSetter(filePaths[0]);
}
}}
endIcon={<FolderIcon />}>
<Typography variant="button" display="inline">
endIcon={<FolderIcon />}
>
<Typography variant='button' display='inline'>
{t('AddWorkspace.Choose')}
</Typography>
</LocationPickerButton>
@ -51,7 +47,9 @@ export function NewWikiForm({
<LocationPickerContainer>
<LocationPickerInput
error={errorInWhichComponent.wikiFolderName}
onChange={(event) => form.wikiFolderNameSetter(event.target.value)}
onChange={(event) => {
form.wikiFolderNameSetter(event.target.value);
}}
label={t('AddWorkspace.WorkspaceFolderNameToCreate')}
helperText={`${t('AddWorkspace.CreateWiki')}${form.wikiFolderLocation ?? ''}`}
value={form.wikiFolderName}
@ -72,7 +70,8 @@ export function NewWikiForm({
onChange={(event) => {
const index = event.target.value as unknown as number;
form.mainWikiToLinkSetter(form.mainWorkspaceList[index]);
}}>
}}
>
{form.mainWorkspaceList.map((workspace, index) => (
<MenuItem key={index} value={index}>
{workspace.name}
@ -82,7 +81,9 @@ export function NewWikiForm({
<SubWikiTagAutoComplete
options={form.fileSystemPaths.map((fileSystemPath) => fileSystemPath.tagName)}
value={form.tagName}
onInputChange={(_, value) => form.tagNameSetter(value)}
onInputChange={(_, value) => {
form.tagNameSetter(value);
}}
renderInput={(parameters) => (
<LocationPickerInput
error={errorInWhichComponent.tagName}

View file

@ -1,29 +1,29 @@
import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
import { useTranslation } from 'react-i18next';
import { Helmet } from 'react-helmet';
import { Accordion as AccordionRaw, AccordionSummary, AccordionDetails, AppBar, Paper as PaperRaw, Tab as TabRaw } from '@material-ui/core';
import { TabPanel as TabPanelRaw, TabContext, TabList as TabListRaw } from '@material-ui/lab';
import { Accordion as AccordionRaw, AccordionDetails, AccordionSummary, AppBar, Paper as PaperRaw, Tab as TabRaw } from '@material-ui/core';
import { ExpandMore as ExpandMoreIcon } from '@material-ui/icons';
import { TabContext, TabList as TabListRaw, TabPanel as TabPanelRaw } from '@material-ui/lab';
import React, { useEffect, useState } from 'react';
import { Helmet } from 'react-helmet';
import { useTranslation } from 'react-i18next';
import styled from 'styled-components';
import { SupportedStorageServices } from '@services/types';
import { MainSubWikiDescription, SyncedWikiDescription } from './Description';
import { NewWikiForm } from './NewWikiForm';
import { NewWikiDoneButton } from './NewWikiDoneButton';
import { ExistedWikiForm } from './ExistedWikiForm';
import { ExistedWikiDoneButton } from './ExistedWikiDoneButton';
import { CloneWikiForm } from './CloneWikiForm';
import { CloneWikiDoneButton } from './CloneWikiDoneButton';
import { IErrorInWhichComponent, useIsCreateSyncedWorkspace, useWikiWorkspaceForm } from './useForm';
import { CloneWikiForm } from './CloneWikiForm';
import { ExistedWikiDoneButton } from './ExistedWikiDoneButton';
import { ExistedWikiForm } from './ExistedWikiForm';
import { NewWikiDoneButton } from './NewWikiDoneButton';
import { NewWikiForm } from './NewWikiForm';
import { IErrorInWhichComponent, useWikiWorkspaceForm } from './useForm';
import { TokenForm } from '@/components/TokenForm';
import { GitRepoUrlForm } from './GitRepoUrlForm';
import { LocationPickerContainer, LocationPickerInput } from './FormComponents';
import { usePromiseValue } from '@/helpers/useServiceValue';
import { ImportHtmlWikiForm } from './ImportHtmlWikiForm';
import { LocationPickerContainer, LocationPickerInput } from './FormComponents';
import { GitRepoUrlForm } from './GitRepoUrlForm';
import { ImportHtmlWikiDoneButton } from './ImportHtmlWikiDoneButton';
import { ImportHtmlWikiForm } from './ImportHtmlWikiForm';
enum CreateWorkspaceTabs {
CloneOnlineWiki = 'CloneOnlineWiki',
@ -108,19 +108,22 @@ export function AddWorkspace(): JSX.Element {
return (
<TabContext value={currentTab}>
<div id="test" data-usage="For spectron automating testing" />
<div id='test' data-usage='For spectron automating testing' />
<Helmet>
<title>
{t('AddWorkspace.AddWorkspace')} {wikiFolderName}
</title>
</Helmet>
<AppBar position="static">
<AppBar position='static'>
<Paper square>
<TabList
onChange={(_event, newValue) => currentTabSetter(newValue as CreateWorkspaceTabs)}
variant="scrollable"
onChange={(_event, newValue) => {
currentTabSetter(newValue as CreateWorkspaceTabs);
}}
variant='scrollable'
value={currentTab}
aria-label={t('AddWorkspace.SwitchCreateNewOrOpenExisted')}>
aria-label={t('AddWorkspace.SwitchCreateNewOrOpenExisted')}
>
<Tab label={t('AddWorkspace.CreateNewWiki')} value={CreateWorkspaceTabs.CreateNewWiki} />
<Tab label={t(`AddWorkspace.CloneOnlineWiki`)} value={CreateWorkspaceTabs.CloneOnlineWiki} />
<Tab label={t('AddWorkspace.OpenLocalWiki')} value={CreateWorkspaceTabs.OpenLocalWiki} />

View file

@ -74,9 +74,9 @@ export function useExistedWiki(
const parentFolderLocationForExistedFolder = await window.service.native.path('dirname', form.wikiFolderLocation);
if (!wikiFolderNameForExistedFolder || !parentFolderLocationForExistedFolder) {
throw new Error(
`Undefined folder name: parentFolderLocationForExistedFolder: ${
`Undefined folder name: parentFolderLocationForExistedFolder: ${parentFolderLocationForExistedFolder ?? '-'}, parentFolderLocationForExistedFolder: ${
parentFolderLocationForExistedFolder ?? '-'
}, parentFolderLocationForExistedFolder: ${parentFolderLocationForExistedFolder ?? '-'}`,
}`,
);
}
await window.service.wiki.ensureWikiExist(form.wikiFolderLocation, false);

View file

@ -16,7 +16,9 @@ import type { INewWikiRequiredFormData } from './useNewWiki';
export function useIsCreateSyncedWorkspace(): [boolean, React.Dispatch<React.SetStateAction<boolean>>] {
const [isCreateSyncedWorkspace, isCreateSyncedWorkspaceSetter] = useState(false);
useEffect(() => {
void window.service.auth.getRandomStorageServiceUserInfo().then((result) => isCreateSyncedWorkspaceSetter(result !== undefined));
void window.service.auth.getRandomStorageServiceUserInfo().then((result) => {
isCreateSyncedWorkspaceSetter(result !== undefined);
});
}, []);
return [isCreateSyncedWorkspace, isCreateSyncedWorkspaceSetter];
}
@ -29,7 +31,9 @@ export function useWikiWorkspaceForm(options?: { fromExisted: boolean }) {
const [wikiPort, wikiPortSetter] = useState(5212);
useEffect(() => {
// only update default port on component mount
void window.service.workspace.countWorkspaces().then((workspaceCount) => wikiPortSetter(wikiPort + workspaceCount));
void window.service.workspace.countWorkspaces().then((workspaceCount) => {
wikiPortSetter(wikiPort + workspaceCount);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@ -79,7 +83,7 @@ export function useWikiWorkspaceForm(options?: { fromExisted: boolean }) {
useEffect(() => {
void (async function getDefaultExistedWikiFolderPathEffect() {
const desktopPathAsDefaultExistedWikiFolderPath = await window.service.context.get('DEFAULT_WIKI_FOLDER');
wikiFolderNameSetter(mainWorkspaceList[mainWorkspaceList.length - 1]?.wikiFolderLocation ?? 'wiki');
wikiFolderNameSetter(mainWorkspaceList.at(-1)?.wikiFolderLocation ?? 'wiki');
parentFolderLocationSetter(desktopPathAsDefaultExistedWikiFolderPath);
})();
// we only do this on component init

View file

@ -3,7 +3,7 @@ import { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { IErrorInWhichComponent, IWikiWorkspaceForm } from './useForm';
import { updateErrorInWhichComponentSetterByErrorMessage } from './useIndicator';
import { useValidateNewWiki, useNewWiki } from './useNewWiki';
import { useNewWiki, useValidateNewWiki } from './useNewWiki';
export function useValidateHtmlWiki(
isCreateMainWorkspace: boolean,
@ -16,14 +16,14 @@ export function useValidateHtmlWiki(
const [hasError, hasErrorSetter] = useState<boolean>(false);
useValidateNewWiki(isCreateMainWorkspace, isCreateSyncedWorkspace, form, errorInWhichComponentSetter);
useEffect(() => {
if (!form.wikiHtmlPath) {
wikiCreationMessageSetter(`${t('AddWorkspace.NotFilled')}${t('AddWorkspace.LocalWikiHtml')}`);
errorInWhichComponentSetter({ wikiHtmlPath: true });
hasErrorSetter(true);
} else {
if (form.wikiHtmlPath) {
wikiCreationMessageSetter('');
errorInWhichComponentSetter({});
hasErrorSetter(false);
} else {
wikiCreationMessageSetter(`${t('AddWorkspace.NotFilled')}${t('AddWorkspace.LocalWikiHtml')}`);
errorInWhichComponentSetter({ wikiHtmlPath: true });
hasErrorSetter(true);
}
}, [t, form.wikiHtmlPath, form.parentFolderLocation, form.wikiFolderName, errorInWhichComponentSetter]);

View file

@ -1,5 +1,5 @@
import { useState, useEffect } from 'react';
import type { TFunction } from 'i18next';
import { useEffect, useState } from 'react';
import { IErrorInWhichComponent } from './useForm';
export function useWikiCreationProgress(

View file

@ -2,10 +2,10 @@
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
import { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { ConditionalExcept } from 'type-fest';
import { callWikiInitialization } from './useCallWikiInitialization';
import { IErrorInWhichComponent, IWikiWorkspaceForm, workspaceConfigFromForm } from './useForm';
import { updateErrorInWhichComponentSetterByErrorMessage } from './useIndicator';
import { ConditionalExcept } from 'type-fest';
export function useValidateNewWiki(
isCreateMainWorkspace: boolean,

View file

@ -2,42 +2,42 @@
/* eslint-disable unicorn/no-useless-undefined */
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
/* eslint-disable unicorn/no-null */
import React, { useCallback } from 'react';
import styled, { css } from 'styled-components';
import { useTranslation } from 'react-i18next';
import { Helmet } from 'react-helmet';
import {
Tooltip,
Button as ButtonRaw,
TextField as TextFieldRaw,
Divider,
Link,
List as ListRaw,
ListItem as ListItemRaw,
ListItemText as ListItemTextRaw,
ListItemSecondaryAction,
Switch,
Typography,
Link,
ListItemText as ListItemTextRaw,
Paper,
Switch,
TextField as TextFieldRaw,
Tooltip,
Typography,
} from '@material-ui/core';
import { Autocomplete } from '@material-ui/lab';
import React, { useCallback } from 'react';
import { Helmet } from 'react-helmet';
import { useTranslation } from 'react-i18next';
import styled, { css } from 'styled-components';
import defaultIcon from '../../images/default-icon.png';
import type { ISubWikiPluginContent } from '@services/wiki/plugin/subWikiPlugin';
import { WindowNames, WindowMeta } from '@services/windows/WindowProperties';
import { usePromiseValue } from '@/helpers/useServiceValue';
import type { ISubWikiPluginContent } from '@services/wiki/plugin/subWikiPlugin';
import { WindowMeta, WindowNames } from '@services/windows/WindowProperties';
import { useWorkspaceObservable } from '@services/workspaces/hooks';
import { useForm } from './useForm';
import { IWorkspace } from '@services/workspaces/interface';
import { useForm } from './useForm';
import { useRestartSnackbar } from '@/components/RestartSnackbar';
import { defaultServerIP } from '@/constants/urls';
import { SupportedStorageServices } from '@services/types';
import { SyncedWikiDescription } from '../AddWorkspace/Description';
import { TokenForm } from '@/components/TokenForm';
import { GitRepoUrlForm } from '../AddWorkspace/GitRepoUrlForm';
import { isEqual } from 'lodash';
import { defaultServerIP } from '@/constants/urls';
import { useActualIp } from '@services/native/hooks';
import { SupportedStorageServices } from '@services/types';
import { isEqual } from 'lodash';
import { SyncedWikiDescription } from '../AddWorkspace/Description';
import { GitRepoUrlForm } from '../AddWorkspace/GitRepoUrlForm';
const Root = styled(Paper)`
height: 100%;
@ -88,7 +88,7 @@ const AvatarRight = styled.div`
`;
/**
* border: theme.palette.type === 'dark' ? 'none': '1px solid rgba(0, 0, 0, 0.12)';
* */
*/
const Avatar = styled.div<{ transparentBackground?: boolean }>`
height: 85px;
width: 85px;
@ -103,14 +103,14 @@ const Avatar = styled.div<{ transparentBackground?: boolean }>`
overflow: hidden;
${({ transparentBackground }) => {
if (transparentBackground === true) {
return css`
if (transparentBackground === true) {
return css`
background: transparent;
border: none;
border-radius: 0;
`;
}
}}
}
}}
`;
const AvatarPicture = styled.img`
height: 100%;
@ -218,7 +218,7 @@ export default function EditWorkspace(): JSX.Element {
const isCreateSyncedWorkspace = storageService !== SupportedStorageServices.local;
return (
<Root>
<div id="test" data-usage="For spectron automating testing" />
<div id='test' data-usage='For spectron automating testing' />
{RestartSnackbar}
<Helmet>
<title>
@ -227,21 +227,25 @@ export default function EditWorkspace(): JSX.Element {
</Helmet>
<FlexGrow>
<TextField
id="outlined-full-width"
id='outlined-full-width'
label={t('EditWorkspace.Name')}
helperText={t('EditWorkspace.NameDescription')}
placeholder="Optional"
placeholder='Optional'
value={name}
onChange={(event) => workspaceSetter({ ...workspace, name: event.target.value })}
onChange={(event) => {
workspaceSetter({ ...workspace, name: event.target.value });
}}
/>
<TextField
id="outlined-full-width"
id='outlined-full-width'
label={t('EditWorkspace.Path')}
helperText={t('EditWorkspace.PathDescription')}
placeholder="Optional"
placeholder='Optional'
disabled
value={wikiFolderLocation}
onChange={(event) => workspaceSetter({ ...workspace, wikiFolderLocation: event.target.value })}
onChange={(event) => {
workspaceSetter({ ...workspace, wikiFolderLocation: event.target.value });
}}
/>
<TextField
helperText={t('AddWorkspace.WorkspaceUserNameDetail')}
@ -257,17 +261,22 @@ export default function EditWorkspace(): JSX.Element {
{!isSubWiki && (
<>
<TextField
id="outlined-full-width"
id='outlined-full-width'
label={t('EditWorkspace.Port')}
helperText={
<span>
{t('EditWorkspace.URL')}{' '}
<Link onClick={async () => actualIP && (await window.service.native.open(actualIP))} style={{ cursor: 'pointer' }}>
<Link
onClick={async () => {
actualIP && (await window.service.native.open(actualIP));
}}
style={{ cursor: 'pointer' }}
>
{actualIP}
</Link>
</span>
}
placeholder="Optional"
placeholder='Optional'
value={port}
onChange={async (event) => {
if (!Number.isNaN(Number.parseInt(event.target.value))) {
@ -282,7 +291,7 @@ export default function EditWorkspace(): JSX.Element {
/>
{rememberLastPageVisited && (
<TextField
id="outlined-full-width"
id='outlined-full-width'
label={t('EditWorkspace.LastVisitState')}
helperText={t('Preference.RememberLastVisitState')}
placeholder={actualIP}
@ -312,26 +321,32 @@ export default function EditWorkspace(): JSX.Element {
<AvatarFlex>
<AvatarLeft>
<Avatar transparentBackground={transparentBackground}>
<AvatarPicture alt="Icon" src={getValidIconPath(picturePath)} />
<AvatarPicture alt='Icon' src={getValidIconPath(picturePath)} />
</Avatar>
</AvatarLeft>
<AvatarRight>
<Tooltip title={wikiPictureExtensions.join(', ')} placement="top">
<Tooltip title={wikiPictureExtensions.join(', ')} placement='top'>
<PictureButton
variant="outlined"
size="small"
variant='outlined'
size='small'
onClick={async () => {
const filePaths = await window.service.native.pickFile([{ name: 'Images', extensions: wikiPictureExtensions }]);
if (filePaths.length > 0) {
await window.service.workspace.update(workspaceID, { picturePath: filePaths[0] });
}
}}>
}}
>
{t('EditWorkspace.SelectLocal')}
</PictureButton>
</Tooltip>
<Tooltip title={t('EditWorkspace.NoRevert') ?? ''} placement="bottom">
<PictureButton onClick={() => workspaceSetter({ ...workspace, picturePath: null })} disabled={!picturePath}>
<Tooltip title={t('EditWorkspace.NoRevert') ?? ''} placement='bottom'>
<PictureButton
onClick={() => {
workspaceSetter({ ...workspace, picturePath: null });
}}
disabled={!picturePath}
>
{t('EditWorkspace.ResetDefaultIcon')}
</PictureButton>
</Tooltip>
@ -371,10 +386,12 @@ export default function EditWorkspace(): JSX.Element {
<ListItemText primary={t('EditWorkspace.SyncOnInterval')} secondary={t('EditWorkspace.SyncOnIntervalDescription')} />
<ListItemSecondaryAction>
<Switch
edge="end"
color="primary"
edge='end'
color='primary'
checked={syncOnInterval}
onChange={(event) => workspaceSetter({ ...workspace, syncOnInterval: event.target.checked })}
onChange={(event) => {
workspaceSetter({ ...workspace, syncOnInterval: event.target.checked });
}}
/>
</ListItemSecondaryAction>
</ListItem>
@ -382,10 +399,12 @@ export default function EditWorkspace(): JSX.Element {
<ListItemText primary={t('EditWorkspace.SyncOnStartup')} secondary={t('EditWorkspace.SyncOnStartupDescription')} />
<ListItemSecondaryAction>
<Switch
edge="end"
color="primary"
edge='end'
color='primary'
checked={syncOnStartup}
onChange={(event) => workspaceSetter({ ...workspace, syncOnStartup: event.target.checked })}
onChange={(event) => {
workspaceSetter({ ...workspace, syncOnStartup: event.target.checked });
}}
/>
</ListItemSecondaryAction>
</ListItem>
@ -401,10 +420,12 @@ export default function EditWorkspace(): JSX.Element {
<ListItemText primary={t('EditWorkspace.BackupOnInterval')} secondary={t('EditWorkspace.BackupOnIntervalDescription')} />
<ListItemSecondaryAction>
<Switch
edge="end"
color="primary"
edge='end'
color='primary'
checked={backupOnInterval}
onChange={(event) => workspaceSetter({ ...workspace, backupOnInterval: event.target.checked })}
onChange={(event) => {
workspaceSetter({ ...workspace, backupOnInterval: event.target.checked });
}}
/>
</ListItemSecondaryAction>
</ListItem>
@ -418,10 +439,12 @@ export default function EditWorkspace(): JSX.Element {
<ListItemText primary={t('EditWorkspace.HibernateTitle')} secondary={t('EditWorkspace.HibernateDescription')} />
<ListItemSecondaryAction>
<Switch
edge="end"
color="primary"
edge='end'
color='primary'
checked={hibernateWhenUnused}
onChange={(event) => workspaceSetter({ ...workspace, hibernateWhenUnused: event.target.checked })}
onChange={(event) => {
workspaceSetter({ ...workspace, hibernateWhenUnused: event.target.checked });
}}
/>
</ListItemSecondaryAction>
</ListItem>
@ -429,10 +452,12 @@ export default function EditWorkspace(): JSX.Element {
<ListItemText primary={t('EditWorkspace.DisableNotificationTitle')} secondary={t('EditWorkspace.DisableNotification')} />
<ListItemSecondaryAction>
<Switch
edge="end"
color="primary"
edge='end'
color='primary'
checked={disableNotifications}
onChange={(event) => workspaceSetter({ ...workspace, disableNotifications: event.target.checked })}
onChange={(event) => {
workspaceSetter({ ...workspace, disableNotifications: event.target.checked });
}}
/>
</ListItemSecondaryAction>
</ListItem>
@ -440,10 +465,12 @@ export default function EditWorkspace(): JSX.Element {
<ListItemText primary={t('EditWorkspace.DisableAudioTitle')} secondary={t('EditWorkspace.DisableAudio')} />
<ListItemSecondaryAction>
<Switch
edge="end"
color="primary"
edge='end'
color='primary'
checked={disableAudio}
onChange={(event) => workspaceSetter({ ...workspace, disableAudio: event.target.checked })}
onChange={(event) => {
workspaceSetter({ ...workspace, disableAudio: event.target.checked });
}}
/>
</ListItemSecondaryAction>
</ListItem>
@ -452,10 +479,10 @@ export default function EditWorkspace(): JSX.Element {
</FlexGrow>
{!isEqual(workspace, originalWorkspace) && (
<div>
<Button color="primary" variant="contained" disableElevation onClick={requestSaveAndRestart}>
<Button color='primary' variant='contained' disableElevation onClick={requestSaveAndRestart}>
{t('EditWorkspace.Save')}
</Button>
<Button variant="contained" disableElevation onClick={() => void window.remote.closeCurrentWindow()}>
<Button variant='contained' disableElevation onClick={() => void window.remote.closeCurrentWindow()}>
{t('EditWorkspace.Cancel')}
</Button>
</div>

View file

@ -1,6 +1,6 @@
import usePreviousValue from 'beautiful-react-hooks/usePreviousValue';
import { useState, useEffect, useCallback } from 'react';
import { IWorkspace } from '@services/workspaces/interface';
import usePreviousValue from 'beautiful-react-hooks/usePreviousValue';
import { useCallback, useEffect, useState } from 'react';
export function useForm(
originalWorkspace?: IWorkspace,

View file

@ -1,11 +1,11 @@
/* eslint-disable unicorn/no-null */
import { Accordion, AccordionDetails, AccordionSummary, Button, Typography } from '@material-ui/core';
import { useCallback } from 'react';
import { Button, Accordion, AccordionSummary, AccordionDetails, Typography } from '@material-ui/core';
import { Trans, useTranslation } from 'react-i18next';
import styled from 'styled-components';
import { usePromiseValue } from '@/helpers/useServiceValue';
import { IWorkspaceWithMetadata, IWorkspaceMetaData } from '@services/workspaces/interface';
import { IWorkspaceMetaData, IWorkspaceWithMetadata } from '@services/workspaces/interface';
import { ReportErrorButton } from '../AddWorkspace/FormComponents';
const HelperTextsList = styled.ul`
@ -38,15 +38,20 @@ export function WikiErrorMessages(props: IWikiErrorMessagesProps): JSX.Element {
<WikiErrorMessagesContainer>
<Accordion>
<AccordionSummary>
<Typography align="left" variant="h5">
<Typography align='left' variant='h5'>
{t('Error.WikiRuntimeError')} {t('ClickForDetails')}
</Typography>
</AccordionSummary>
<AccordionDetails>
<Typography align="left" variant="body2">
<Typography align='left' variant='body2'>
{t('Error.WikiRuntimeErrorDescription')}
</Typography>
<Button variant="outlined" onClick={async () => await window.service.native.open(wikiLogs.filePath, true)}>
<Button
variant='outlined'
onClick={async () => {
await window.service.native.open(wikiLogs.filePath, true);
}}
>
{t('Preference.OpenLogFolder')}
</Button>
@ -89,22 +94,22 @@ export function ViewLoadErrorMessages(props: IViewLoadErrorMessagesProps): JSX.E
return (
<WikiErrorMessagesContainer>
<Typography align="left" variant="h5">
<Typography align='left' variant='h5'>
{t('AddWorkspace.WikiNotStarted')}
</Typography>
<Typography align="left" variant="body2">
<Typography align='left' variant='body2'>
{props.activeWorkspaceMetadata.didFailLoadErrorMessage}
</Typography>
<br />
<Trans t={t} i18nKey="AddWorkspace.MainPageReloadTip">
<Typography align="left" variant="body2" component="div">
<Trans t={t} i18nKey='AddWorkspace.MainPageReloadTip'>
<Typography align='left' variant='body2' component='div'>
<>
Try:
<HelperTextsList>
<li>
Click{' '}
<b onClick={requestReload} onKeyPress={requestReload} role="button" tabIndex={0} style={{ cursor: 'pointer' }}>
<b onClick={requestReload} onKeyPress={requestReload} role='button' tabIndex={0} style={{ cursor: 'pointer' }}>
Reload
</b>{' '}
button below or press <b>CMD_or_Ctrl + R</b> to reload the page.
@ -112,11 +117,16 @@ export function ViewLoadErrorMessages(props: IViewLoadErrorMessagesProps): JSX.E
<li>
Check the{' '}
<b
onClick={async () => await window.service.native.open(await window.service.context.get('LOG_FOLDER'), true)}
onKeyPress={async () => await window.service.native.open(await window.service.context.get('LOG_FOLDER'), true)}
role="button"
onClick={async () => {
await window.service.native.open(await window.service.context.get('LOG_FOLDER'), true);
}}
onKeyPress={async () => {
await window.service.native.open(await window.service.context.get('LOG_FOLDER'), true);
}}
role='button'
tabIndex={0}
style={{ cursor: 'pointer' }}>
style={{ cursor: 'pointer' }}
>
Log Folder
</b>{' '}
to see what happened.
@ -128,12 +138,10 @@ export function ViewLoadErrorMessages(props: IViewLoadErrorMessagesProps): JSX.E
</Trans>
<ButtonGroup>
<Button variant="outlined" onClick={requestReload}>
<Button variant='outlined' onClick={requestReload}>
{t('AddWorkspace.Reload')}
</Button>
{typeof props.activeWorkspaceMetadata.didFailLoadErrorMessage === 'string' && (
<ReportErrorButton message={props.activeWorkspaceMetadata.didFailLoadErrorMessage} />
)}
{typeof props.activeWorkspaceMetadata.didFailLoadErrorMessage === 'string' && <ReportErrorButton message={props.activeWorkspaceMetadata.didFailLoadErrorMessage} />}
</ButtonGroup>
</WikiErrorMessagesContainer>
);

View file

@ -1,11 +1,11 @@
import { Trans, useTranslation } from 'react-i18next';
import styled from 'styled-components';
import { WindowNames } from '@services/windows/WindowProperties';
import { IPreferences } from '@services/preferences/interface';
import { WindowNames } from '@services/windows/WindowProperties';
import arrowWhite from '@/images/arrow-white.png';
import arrowBlack from '@/images/arrow-black.png';
import arrowWhite from '@/images/arrow-white.png';
const Arrow = styled.div<{ image: string }>`
height: 202px;
@ -64,31 +64,37 @@ export interface IProps {
export function NewUserMessage(props: IProps): JSX.Element {
const { t } = useTranslation();
return (
<AddWorkspaceGuideInfoContainer onClick={async () => await window.service.window.open(WindowNames.addWorkspace)}>
{props.sidebar ? (
<>
<Arrow image={props.themeSource === 'dark' ? arrowWhite : arrowBlack} />
<TipWithSidebar id="new-user-tip">
<Trans t={t} i18nKey="AddWorkspace.MainPageTipWithSidebar">
<Tip2Text>Click</Tip2Text>
<Avatar>+</Avatar>
<Tip2Text>to get started!</Tip2Text>
</Trans>
</TipWithSidebar>
</>
) : (
<TipWithoutSidebar id="new-user-tip">
<Tip2Text>
<Trans t={t} i18nKey="AddWorkspace.MainPageTipWithoutSidebar">
<span>Click </span>
<strong>Workspaces &gt; Add Workspace</strong>
<span>Or </span>
<strong>Click Here</strong>
<span> to get started!</span>
</Trans>
</Tip2Text>
</TipWithoutSidebar>
)}
<AddWorkspaceGuideInfoContainer
onClick={async () => {
await window.service.window.open(WindowNames.addWorkspace);
}}
>
{props.sidebar
? (
<>
<Arrow image={props.themeSource === 'dark' ? arrowWhite : arrowBlack} />
<TipWithSidebar id='new-user-tip'>
<Trans t={t} i18nKey='AddWorkspace.MainPageTipWithSidebar'>
<Tip2Text>Click</Tip2Text>
<Avatar>+</Avatar>
<Tip2Text>to get started!</Tip2Text>
</Trans>
</TipWithSidebar>
</>
)
: (
<TipWithoutSidebar id='new-user-tip'>
<Tip2Text>
<Trans t={t} i18nKey='AddWorkspace.MainPageTipWithoutSidebar'>
<span>Click</span>
<strong>Workspaces &gt; Add Workspace</strong>
<span>Or</span>
<strong>Click Here</strong>
<span>to get started!</span>
</Trans>
</Tip2Text>
</TipWithoutSidebar>
)}
</AddWorkspaceGuideInfoContainer>
);
}

View file

@ -1,34 +1,34 @@
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
/* eslint-disable @typescript-eslint/promise-function-async */
import React, { useState } from 'react';
import { Helmet } from 'react-helmet';
import { useTranslation } from 'react-i18next';
import styled, { css } from 'styled-components';
import is, { isNot } from 'typescript-styled-is';
import { useTranslation } from 'react-i18next';
import { Helmet } from 'react-helmet';
import SimpleBar from 'simplebar-react';
import 'simplebar/dist/simplebar.min.css';
import { Typography, Tooltip, IconButton as IconButtonRaw } from '@material-ui/core';
import { IconButton as IconButtonRaw, Tooltip, Typography } from '@material-ui/core';
import { Settings as SettingsIcon, Upgrade as UpgradeIcon } from '@material-ui/icons';
import { WindowNames } from '@services/windows/WindowProperties';
import { IUpdaterStatus } from '@services/updater/interface';
import { WindowNames } from '@services/windows/WindowProperties';
import { usePromiseValue } from '@/helpers/useServiceValue';
import { useUpdaterObservable } from '@services/updater/hooks';
import FindInPage from '../../components/FindInPage';
import { latestStableUpdateUrl } from '@/constants/urls';
import FindInPage from '../../components/FindInPage';
import { WorkspaceSelector, SortableWorkspaceSelectorList } from '@/components/WorkspaceIconAndSelector';
import { useWorkspacesListObservable } from '@services/workspaces/hooks';
import { usePreferenceObservable } from '@services/preferences/hooks';
import { CommandPaletteIcon } from '@/components/icon/CommandPaletteSVG';
import { SortableWorkspaceSelectorList, WorkspaceSelector } from '@/components/WorkspaceIconAndSelector';
import { usePreferenceObservable } from '@services/preferences/hooks';
import { useWorkspacesListObservable } from '@services/workspaces/hooks';
import { Languages } from '../Preferences/sections/Languages';
import { TiddlyWiki } from '../Preferences/sections/TiddlyWiki';
import { ViewLoadErrorMessages, WikiErrorMessages } from './ErrorMessage';
import { NewUserMessage } from './NewUserMessage';
import { WikiErrorMessages, ViewLoadErrorMessages } from './ErrorMessage';
import { useAutoCreateFirstWorkspace } from './useAutoCreateFirstWorkspace';
const OuterRoot = styled.div`
@ -91,11 +91,11 @@ const SidebarTop = styled.div<{ titleBar?: boolean }>`
flex: 1;
width: 100%;
${({ titleBar }) =>
titleBar === true
? css`
titleBar === true
? css`
padding-top: 0;
`
: css`
: css`
padding-top: 30px;
`}
`;
@ -167,13 +167,12 @@ export default function Main(): JSX.Element {
if (preferences === undefined) return <div>{t('Loading')}</div>;
const { sidebar, themeSource, sidebarShortcutHints, hideSideBarIcon } = preferences;
const hasError =
typeof activeWorkspaceMetadata?.didFailLoadErrorMessage === 'string' &&
const hasError = typeof activeWorkspaceMetadata?.didFailLoadErrorMessage === 'string' &&
activeWorkspaceMetadata?.didFailLoadErrorMessage.length > 0 &&
activeWorkspaceMetadata?.isLoading === false;
return (
<OuterRoot>
<div id="test" data-usage="For spectron automating testing" />
<div id='test' data-usage='For spectron automating testing' />
<Helmet>
<title>{t('Menu.TidGi')}</title>
</Helmet>
@ -181,13 +180,11 @@ export default function Main(): JSX.Element {
{sidebar && (
<SidebarContainer>
<SidebarTop titleBar={titleBar}>
{workspacesList === undefined ? (
<div>{t('Loading')}</div>
) : (
<SortableWorkspaceSelectorList sidebarShortcutHints={sidebarShortcutHints} workspacesList={workspacesList} hideSideBarIcon={hideSideBarIcon} />
)}
{workspacesList === undefined
? <div>{t('Loading')}</div>
: <SortableWorkspaceSelectorList sidebarShortcutHints={sidebarShortcutHints} workspacesList={workspacesList} hideSideBarIcon={hideSideBarIcon} />}
<WorkspaceSelector
id="add"
id='add'
hideSideBarIcon={hideSideBarIcon}
index={workspacesList?.length ?? 0}
showSidebarShortcutHints={sidebarShortcutHints}
@ -196,7 +193,7 @@ export default function Main(): JSX.Element {
{workspacesList === undefined ||
(workspacesList.length === 0 && (
<WorkspaceSelector
id="guide"
id='guide'
hideSideBarIcon={hideSideBarIcon}
index={workspacesList?.length ? workspacesList.length ?? 0 + 1 : 1}
active={activeWorkspace?.id === undefined}
@ -209,10 +206,13 @@ export default function Main(): JSX.Element {
{(workspacesList?.length ?? 0) > 0 && (
<>
<IconButton
id="open-command-palette-button"
id='open-command-palette-button'
aria-label={t('SideBar.CommandPalette')}
onClick={async () => await window.service.wiki.requestWikiSendActionMessage('open-command-palette')}>
<Tooltip title={<span>{t('SideBar.CommandPalette')}</span>} placement="top">
onClick={async () => {
await window.service.wiki.requestWikiSendActionMessage('open-command-palette');
}}
>
<Tooltip title={<span>{t('SideBar.CommandPalette')}</span>} placement='top'>
<CommandPaletteIcon />
</Tooltip>
</IconButton>
@ -220,19 +220,25 @@ export default function Main(): JSX.Element {
)}
{updaterMetaData?.status === IUpdaterStatus.updateAvailable && (
<IconButton
id="update-available"
id='update-available'
aria-label={t('SideBar.UpdateAvailable')}
onClick={async () => await window.service.native.open(updaterMetaData.info?.latestReleasePageUrl ?? latestStableUpdateUrl)}>
<Tooltip title={<span>{t('SideBar.UpdateAvailable')}</span>} placement="top">
onClick={async () => {
await window.service.native.open(updaterMetaData.info?.latestReleasePageUrl ?? latestStableUpdateUrl);
}}
>
<Tooltip title={<span>{t('SideBar.UpdateAvailable')}</span>} placement='top'>
<UpgradeIcon />
</Tooltip>
</IconButton>
)}
<IconButton
id="open-preferences-button"
id='open-preferences-button'
aria-label={t('SideBar.Preferences')}
onClick={async () => await window.service.window.open(WindowNames.preferences)}>
<Tooltip title={<span>{t('SideBar.Preferences')}</span>} placement="top">
onClick={async () => {
await window.service.window.open(WindowNames.preferences);
}}
>
<Tooltip title={<span>{t('SideBar.Preferences')}</span>} placement='top'>
<SettingsIcon />
</Tooltip>
</IconButton>
@ -247,9 +253,9 @@ export default function Main(): JSX.Element {
<ViewLoadErrorMessages activeWorkspace={activeWorkspace} activeWorkspaceMetadata={activeWorkspaceMetadata} />
)}
{Array.isArray(workspacesList) && workspacesList.length > 0 && activeWorkspaceMetadata?.isLoading === true && (
<Typography color="textSecondary">{t('Loading')}</Typography>
<Typography color='textSecondary'>{t('Loading')}</Typography>
)}
{wikiCreationMessage && <Typography color="textSecondary">{wikiCreationMessage}</Typography>}
{wikiCreationMessage && <Typography color='textSecondary'>{wikiCreationMessage}</Typography>}
{Array.isArray(workspacesList) && workspacesList.length === 0 && <NewUserMessage sidebar={sidebar} themeSource={themeSource} />}
</InnerContentRoot>
<Languages languageSelectorOnly />

View file

@ -1,10 +1,10 @@
/* eslint-disable @typescript-eslint/promise-function-async */
import { useEffect, useState } from 'react';
import { IWorkspaceWithMetadata } from '@services/workspaces/interface';
import { INewWikiRequiredFormData, useNewWiki } from '../AddWorkspace/useNewWiki';
import { SupportedStorageServices } from '@services/types';
import { useWikiWorkspaceForm } from '../AddWorkspace/useForm';
import { usePromiseValue } from '@/helpers/useServiceValue';
import { SupportedStorageServices } from '@services/types';
import { IWorkspaceWithMetadata } from '@services/workspaces/interface';
import { useEffect, useState } from 'react';
import { useWikiWorkspaceForm } from '../AddWorkspace/useForm';
import { INewWikiRequiredFormData, useNewWiki } from '../AddWorkspace/useNewWiki';
export function useAutoCreateFirstWorkspace(workspacesList: IWorkspaceWithMetadata[] | undefined, wikiCreationMessageSetter: (m: string) => void): void {
const form = useWikiWorkspaceForm();

View file

@ -2,18 +2,18 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
/* eslint-disable unicorn/no-useless-undefined */
import React, { useState } from 'react';
import styled from 'styled-components';
import { useTranslation } from 'react-i18next';
import { Helmet } from 'react-helmet';
import { useTranslation } from 'react-i18next';
import styled from 'styled-components';
import ListSubheader from '@material-ui/core/ListSubheader';
import Container from '@material-ui/core/Container';
import Divider from '@material-ui/core/Divider';
import ListRaw from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText';
import Divider from '@material-ui/core/Divider';
import TextField from '@material-ui/core/TextField';
import ListSubheader from '@material-ui/core/ListSubheader';
import MenuItem from '@material-ui/core/MenuItem';
import Container from '@material-ui/core/Container';
import TextField from '@material-ui/core/TextField';
import ChevronRightIcon from '@material-ui/icons/ChevronRight';
@ -23,12 +23,12 @@ import { WindowNames } from '@services/windows/WindowProperties';
import PopUpMenuItem from '@/components/PopUpMenuItem';
// https://www.sketchappsources.com/free-source/2501-iphone-app-background-sketch-freebie-resource.html
import nightBackgroundPng from '../../images/night-background.png';
import { usePreferenceObservable } from '@services/preferences/hooks';
import { useNotificationInfoObservable } from '@services/notifications/hooks';
import { quickShortcuts } from './quickShortcuts';
import { PreferenceSections } from '@services/preferences/interface';
import { formatDate } from '@services/libs/formatDate';
import { useNotificationInfoObservable } from '@services/notifications/hooks';
import { usePreferenceObservable } from '@services/preferences/hooks';
import { PreferenceSections } from '@services/preferences/interface';
import nightBackgroundPng from '../../images/night-background.png';
import { quickShortcuts } from './quickShortcuts';
const Root = styled(Container)`
width: 100%;
@ -90,7 +90,7 @@ export default function Notifications(): JSX.Element {
</PausingHeader>
<ListItem button>
<ListItemText
primary="Resume notifications"
primary='Resume notifications'
onClick={async () => {
if (pauseNotificationsInfo === undefined) {
return;
@ -114,19 +114,31 @@ export default function Notifications(): JSX.Element {
<>
<Divider />
<PopUpMenuItem
id="adjustTime"
id='adjustTime'
buttonElement={
<ListItem button>
<ListItemText primary="Adjust time" />
<ChevronRightIcon color="action" />
<ListItemText primary='Adjust time' />
<ChevronRightIcon color='action' />
</ListItem>
}>
}
>
{quickShortcuts.map((shortcut) => (
<MenuItem dense key={shortcut.name} onClick={() => pauseNotification(shortcut.calcDate())}>
<MenuItem
dense
key={shortcut.name}
onClick={() => {
pauseNotification(shortcut.calcDate());
}}
>
{shortcut.name}
</MenuItem>
))}
<MenuItem dense onClick={() => showDateTimePickerSetter(true)}>
<MenuItem
dense
onClick={() => {
showDateTimePickerSetter(true);
}}
>
Custom...
</MenuItem>
</PopUpMenuItem>
@ -147,19 +159,30 @@ export default function Notifications(): JSX.Element {
}
return (
<List subheader={<ListSubheader component="div">Pause notifications</ListSubheader>}>
<List subheader={<ListSubheader component='div'>Pause notifications</ListSubheader>}>
{quickShortcuts.map((shortcut) => (
<ListItem button key={shortcut.name} onClick={() => pauseNotification(shortcut.calcDate())}>
<ListItem
button
key={shortcut.name}
onClick={() => {
pauseNotification(shortcut.calcDate());
}}
>
<ListItemText primary={shortcut.name} />
</ListItem>
))}
<ListItem button onClick={() => showDateTimePickerSetter(true)}>
<ListItemText primary="Custom..." />
<ListItem
button
onClick={() => {
showDateTimePickerSetter(true);
}}
>
<ListItemText primary='Custom...' />
</ListItem>
<Divider />
<ListItem button>
<ListItemText
primary="Pause notifications by schedule..."
primary='Pause notifications by schedule...'
onClick={async () => {
await window.service.window.open(WindowNames.preferences, { gotoTab: PreferenceSections.notifications });
void window.remote.closeCurrentWindow();
@ -172,7 +195,7 @@ export default function Notifications(): JSX.Element {
return (
<Root>
<div id="test" data-usage="For spectron automating testing" />
<div id='test' data-usage='For spectron automating testing' />
<Helmet>
<title>{t('ContextMenu.Notifications')}</title>
</Helmet>
@ -184,10 +207,14 @@ export default function Notifications(): JSX.Element {
if (tilDate === null) return;
pauseNotification(tilDate);
}}
label="Custom"
label='Custom'
open={showDateTimePicker}
onOpen={() => showDateTimePickerSetter(true)}
onClose={() => showDateTimePickerSetter(false)}
onOpen={() => {
showDateTimePickerSetter(true);
}}
onClose={() => {
showDateTimePickerSetter(false);
}}
disablePast
showTodayButton
/>

View file

@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { addMinutes, addHours, addDays, addWeeks } from 'date-fns';
import { addDays, addHours, addMinutes, addWeeks } from 'date-fns';
export const quickShortcuts = [
{

View file

@ -1,12 +1,5 @@
import { InputLabel as InputLabelRaw, ListItem as ListItemRaw, ListItemText as ListItemTextRaw, Paper as PaperRaw, TextField as TextFieldRaw, Typography } from '@material-ui/core';
import styled, { keyframes } from 'styled-components';
import {
Paper as PaperRaw,
Typography,
TextField as TextFieldRaw,
ListItem as ListItemRaw,
ListItemText as ListItemTextRaw,
InputLabel as InputLabelRaw,
} from '@material-ui/core';
export const Paper = styled(PaperRaw)`
margin-top: 5px;

View file

@ -1,9 +1,9 @@
import { Divider as DividerRaw, List, ListItem, ListItemIcon as ListItemIconRaw, ListItemText } from '@material-ui/core';
import React from 'react';
import styled, { keyframes } from 'styled-components';
import { Divider as DividerRaw, List, ListItem, ListItemIcon as ListItemIconRaw, ListItemText } from '@material-ui/core';
import { ISectionProps } from './useSections';
import { PreferenceSections } from '@services/preferences/interface';
import { ISectionProps } from './useSections';
const SideBar = styled.div`
position: fixed;

View file

@ -1,27 +1,27 @@
import React, { useEffect } from 'react';
import styled, { css } from 'styled-components';
import { Helmet } from 'react-helmet';
import { useTranslation } from 'react-i18next';
import styled from 'styled-components';
import { useRestartSnackbar } from '@/components/RestartSnackbar';
import { IPossibleWindowMeta, WindowMeta, WindowNames } from '@services/windows/WindowProperties';
import { usePreferenceSections } from './useSections';
import { SectionSideBar } from './SectionsSideBar';
import { TiddlyWiki } from './sections/TiddlyWiki';
import { Sync } from './sections/Sync';
import { General } from './sections/General';
import { System } from './sections/System';
import { Notifications } from './sections/Notifications';
import { Languages } from './sections/Languages';
import { Downloads } from './sections/Downloads';
import { Network } from './sections/Network';
import { PrivacyAndSecurity } from './sections/PrivacyAndSecurity';
import { DeveloperTools } from './sections/DeveloperTools';
import { Performance } from './sections/Performance';
import { Updates } from './sections/Updates';
import { Downloads } from './sections/Downloads';
import { FriendLinks } from './sections/FriendLinks';
import { General } from './sections/General';
import { Languages } from './sections/Languages';
import { Miscellaneous } from './sections/Miscellaneous';
import { Network } from './sections/Network';
import { Notifications } from './sections/Notifications';
import { Performance } from './sections/Performance';
import { PrivacyAndSecurity } from './sections/PrivacyAndSecurity';
import { Sync } from './sections/Sync';
import { System } from './sections/System';
import { TiddlyWiki } from './sections/TiddlyWiki';
import { Updates } from './sections/Updates';
import { SectionSideBar } from './SectionsSideBar';
import { usePreferenceSections } from './useSections';
const Root = styled.div`
padding: 20px;
@ -47,7 +47,7 @@ export default function Preferences(): JSX.Element {
return (
<Root>
<div id="test" data-usage="For spectron automating testing" />
<div id='test' data-usage='For spectron automating testing' />
{RestartSnackbar}
<Helmet>

View file

@ -1,11 +1,11 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Divider, List } from '@material-ui/core';
import ChevronRightIcon from '@material-ui/icons/ChevronRight';
import React from 'react';
import { useTranslation } from 'react-i18next';
import type { ISectionProps } from '../useSections';
import { Paper, SectionTitle, ListItem, ListItemText } from '../PreferenceComponents';
import { usePromiseValue } from '@/helpers/useServiceValue';
import { ListItem, ListItemText, Paper, SectionTitle } from '../PreferenceComponents';
import type { ISectionProps } from '../useSections';
export function DeveloperTools(props: ISectionProps): JSX.Element {
const { t } = useTranslation();
@ -24,9 +24,7 @@ export function DeveloperTools(props: ISectionProps): JSX.Element {
<SectionTitle ref={props.sections.developers.ref}>{t('Preference.DeveloperTools')}</SectionTitle>
<Paper elevation={0}>
<List dense disablePadding>
{LOG_FOLDER === undefined || SETTINGS_FOLDER === undefined ? (
<ListItem>{t('Loading')}</ListItem>
) : (
{LOG_FOLDER === undefined || SETTINGS_FOLDER === undefined ? <ListItem>{t('Loading')}</ListItem> : (
<>
<ListItem
button
@ -34,9 +32,10 @@ export function DeveloperTools(props: ISectionProps): JSX.Element {
if (LOG_FOLDER !== undefined) {
void window.service.native.open(LOG_FOLDER, true);
}
}}>
}}
>
<ListItemText primary={t('Preference.OpenLogFolder')} secondary={t('Preference.OpenLogFolderDetail')} />
<ChevronRightIcon color="action" />
<ChevronRightIcon color='action' />
</ListItem>
<Divider />
<ListItem
@ -45,14 +44,20 @@ export function DeveloperTools(props: ISectionProps): JSX.Element {
if (SETTINGS_FOLDER !== undefined) {
void window.service.native.open(SETTINGS_FOLDER, true);
}
}}>
}}
>
<ListItemText primary={t('Preference.OpenMetaDataFolder')} secondary={t('Preference.OpenMetaDataFolderDetail')} />
<ChevronRightIcon color="action" />
<ChevronRightIcon color='action' />
</ListItem>
<Divider />
<ListItem button onClick={async () => await window.service.preference.resetWithConfirm()}>
<ListItem
button
onClick={async () => {
await window.service.preference.resetWithConfirm();
}}
>
<ListItemText primary={t('Preference.RestorePreferences')} />
<ChevronRightIcon color="action" />
<ChevronRightIcon color='action' />
</ListItem>
</>
)}

View file

@ -1,11 +1,11 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Divider, List, ListItemSecondaryAction, Switch } from '@material-ui/core';
import ChevronRightIcon from '@material-ui/icons/ChevronRight';
import React from 'react';
import { useTranslation } from 'react-i18next';
import type { ISectionProps } from '../useSections';
import { Paper, SectionTitle, ListItem, ListItemText } from '../PreferenceComponents';
import { usePreferenceObservable } from '@services/preferences/hooks';
import { ListItem, ListItemText, Paper, SectionTitle } from '../PreferenceComponents';
import type { ISectionProps } from '../useSections';
export function Downloads(props: Required<ISectionProps>): JSX.Element {
const { t } = useTranslation();
@ -17,9 +17,7 @@ export function Downloads(props: Required<ISectionProps>): JSX.Element {
<SectionTitle ref={props.sections.downloads.ref}>{t('Preference.Downloads')}</SectionTitle>
<Paper elevation={0}>
<List dense disablePadding>
{preference === undefined ? (
<ListItem>{t('Loading')}</ListItem>
) : (
{preference === undefined ? <ListItem>{t('Loading')}</ListItem> : (
<>
<ListItem
button
@ -34,17 +32,18 @@ export function Downloads(props: Required<ISectionProps>): JSX.Element {
.catch((error: Error) => {
console.log(error); // eslint-disable-line no-console
});
}}>
}}
>
<ListItemText primary={t('Preference.DownloadLocation')} secondary={preference.downloadPath} />
<ChevronRightIcon color="action" />
<ChevronRightIcon color='action' />
</ListItem>
<Divider />
<ListItem>
<ListItemText primary={t('Preference.AskDownloadLocation')} />
<ListItemSecondaryAction>
<Switch
edge="end"
color="primary"
edge='end'
color='primary'
checked={preference.askForDownloadPath}
onChange={async (event) => {
await window.service.preference.set('askForDownloadPath', event.target.checked);

View file

@ -1,14 +1,14 @@
import React from 'react';
import styled from 'styled-components';
import { useTranslation } from 'react-i18next';
import { Divider, List } from '@material-ui/core';
import ChevronRightIcon from '@material-ui/icons/ChevronRight';
import React from 'react';
import { useTranslation } from 'react-i18next';
import styled from 'styled-components';
import { ListItem, ListItemText, Paper, SectionTitle } from '../PreferenceComponents';
import type { ISectionProps } from '../useSections';
import { Paper, SectionTitle, ListItem, ListItemText } from '../PreferenceComponents';
import webcatalogLogo from '@/images/webcatalog-logo.svg';
import translatiumLogo from '@/images/translatium-logo.svg';
import webcatalogLogo from '@/images/webcatalog-logo.svg';
const Logo = styled.img`
height: 28px;
@ -22,19 +22,34 @@ export function FriendLinks(props: ISectionProps): JSX.Element {
<SectionTitle ref={props.sections.friendLinks.ref}>{t('Preference.FriendLinks')}</SectionTitle>
<Paper elevation={0}>
<List dense disablePadding>
<ListItem button onClick={async () => await window.service.native.open('https://github.com/webcatalog/webcatalog-engine')}>
<ListItem
button
onClick={async () => {
await window.service.native.open('https://github.com/webcatalog/webcatalog-engine');
}}
>
<ListItemText secondary={t('Preference.WebCatalogEngineIntro')} />
<ChevronRightIcon color="action" />
<ChevronRightIcon color='action' />
</ListItem>
<Divider />
<ListItem button onClick={async () => await window.service.native.open('https://webcatalogapp.com?utm_source=tidgi_app')}>
<ListItem
button
onClick={async () => {
await window.service.native.open('https://webcatalogapp.com?utm_source=tidgi_app');
}}
>
<ListItemText primary={<Logo src={webcatalogLogo} alt={t('Preference.WebCatalog')} />} secondary={t('Preference.WebCatalogIntro')} />
<ChevronRightIcon color="action" />
<ChevronRightIcon color='action' />
</ListItem>
<Divider />
<ListItem button onClick={async () => await window.service.native.open('https://translatiumapp.com?utm_source=tidgi_app')}>
<ListItem
button
onClick={async () => {
await window.service.native.open('https://translatiumapp.com?utm_source=tidgi_app');
}}
>
<ListItemText primary={<Logo src={translatiumLogo} alt={t('Preference.Translatium')} />} secondary={t('Preference.TranslatiumIntro')} />
<ChevronRightIcon color="action" />
<ChevronRightIcon color='action' />
</ListItem>
</List>
</Paper>

View file

@ -1,14 +1,14 @@
import { Divider, List, ListItemSecondaryAction, MenuItem, Switch } from '@material-ui/core';
import ChevronRightIcon from '@material-ui/icons/ChevronRight';
import React from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { Divider, Switch, List, ListItemSecondaryAction, MenuItem } from '@material-ui/core';
import ChevronRightIcon from '@material-ui/icons/ChevronRight';
import type { ISectionProps } from '../useSections';
import { Paper, SectionTitle, ListItem, ListItemText } from '../PreferenceComponents';
import { usePreferenceObservable } from '@services/preferences/hooks';
import PopUpMenuItem from '@/components/PopUpMenuItem';
import { usePromiseValue } from '@/helpers/useServiceValue';
import { usePreferenceObservable } from '@services/preferences/hooks';
import { IPreferences } from '@services/preferences/interface';
import { ListItem, ListItemText, Paper, SectionTitle } from '../PreferenceComponents';
import type { ISectionProps } from '../useSections';
const getThemeString = (theme: IPreferences['themeSource']): string => {
if (theme === 'light') return 'Light';
@ -27,16 +27,14 @@ export function General(props: Required<ISectionProps>): JSX.Element {
<SectionTitle ref={props.sections.general.ref}>{t('Preference.General')}</SectionTitle>
<Paper elevation={0}>
<List dense disablePadding>
{preference === undefined || platform === undefined ? (
<ListItem>{t('Loading')}</ListItem>
) : (
{preference === undefined || platform === undefined ? <ListItem>{t('Loading')}</ListItem> : (
<>
<ListItem>
<ListItemText primary={t('Preference.RememberLastVisitState')} />
<ListItemSecondaryAction>
<Switch
edge="end"
color="primary"
edge='end'
color='primary'
checked={preference.rememberLastPageVisited}
onChange={async (event) => {
await window.service.preference.set('rememberLastPageVisited', event.target.checked);
@ -47,20 +45,36 @@ export function General(props: Required<ISectionProps>): JSX.Element {
</ListItem>
<Divider />
<PopUpMenuItem
id="theme"
id='theme'
buttonElement={
<ListItem button>
<ListItemText primary={t('Preference.Theme')} secondary={getThemeString(preference.themeSource)} />
<ChevronRightIcon color="action" />
<ChevronRightIcon color='action' />
</ListItem>
}>
<MenuItem dense onClick={async () => await window.service.preference.set('themeSource', 'system')}>
}
>
<MenuItem
dense
onClick={async () => {
await window.service.preference.set('themeSource', 'system');
}}
>
{t('Preference.SystemDefaultTheme')}
</MenuItem>
<MenuItem dense onClick={async () => await window.service.preference.set('themeSource', 'light')}>
<MenuItem
dense
onClick={async () => {
await window.service.preference.set('themeSource', 'light');
}}
>
{t('Preference.LightTheme')}
</MenuItem>
<MenuItem dense onClick={async () => await window.service.preference.set('themeSource', 'dark')}>
<MenuItem
dense
onClick={async () => {
await window.service.preference.set('themeSource', 'dark');
}}
>
{t('Preference.DarkTheme')}
</MenuItem>
</PopUpMenuItem>
@ -69,8 +83,8 @@ export function General(props: Required<ISectionProps>): JSX.Element {
<ListItemText primary={t('Preference.ShowSideBar')} secondary={t('Preference.ShowSideBarDetail')} />
<ListItemSecondaryAction>
<Switch
edge="end"
color="primary"
edge='end'
color='primary'
checked={preference.sidebar}
onChange={async (event) => {
await window.service.preference.set('sidebar', event.target.checked);
@ -83,8 +97,8 @@ export function General(props: Required<ISectionProps>): JSX.Element {
<ListItemText primary={t('Preference.HideSideBarIcon')} secondary={t('Preference.ShowSideBarDetail')} />
<ListItemSecondaryAction>
<Switch
edge="end"
color="primary"
edge='end'
color='primary'
checked={preference.hideSideBarIcon}
onChange={async (event) => {
await window.service.preference.set('hideSideBarIcon', event.target.checked);
@ -97,8 +111,8 @@ export function General(props: Required<ISectionProps>): JSX.Element {
<ListItemText primary={t('Preference.ShowSideBarShortcut')} />
<ListItemSecondaryAction>
<Switch
edge="end"
color="primary"
edge='end'
color='primary'
checked={preference.sidebarShortcutHints}
onChange={async (event) => {
await window.service.preference.set('sidebarShortcutHints', event.target.checked);
@ -113,8 +127,8 @@ export function General(props: Required<ISectionProps>): JSX.Element {
<ListItemText primary={t('Preference.ShowTitleBar')} secondary={t('Preference.ShowTitleBarDetail')} />
<ListItemSecondaryAction>
<Switch
edge="end"
color="primary"
edge='end'
color='primary'
checked={preference.titleBar}
onChange={async (event) => {
await window.service.preference.set('titleBar', event.target.checked);
@ -134,8 +148,8 @@ export function General(props: Required<ISectionProps>): JSX.Element {
<ListItemText primary={t('Preference.HideMenuBar')} secondary={t('Preference.HideMenuBarDetail')} />
<ListItemSecondaryAction>
<Switch
edge="end"
color="primary"
edge='end'
color='primary'
checked={preference.hideMenuBar}
onChange={async (event) => {
await window.service.preference.set('hideMenuBar', event.target.checked);
@ -151,8 +165,8 @@ export function General(props: Required<ISectionProps>): JSX.Element {
<ListItemText primary={t('Preference.AlwaysOnTop')} secondary={t('Preference.AlwaysOnTopDetail')} />
<ListItemSecondaryAction>
<Switch
edge="end"
color="primary"
edge='end'
color='primary'
checked={preference.alwaysOnTop}
onChange={async (event) => {
await window.service.preference.set('alwaysOnTop', event.target.checked);
@ -165,12 +179,12 @@ export function General(props: Required<ISectionProps>): JSX.Element {
<ListItem>
<ListItemText
primary={platform === 'win32' ? t('Preference.AttachToTaskbar') : t('Preference.AttachToMenuBar')}
secondary={platform !== 'linux' ? t('Preference.AttachToMenuBarTip') : undefined}
secondary={platform === 'linux' ? undefined : t('Preference.AttachToMenuBarTip')}
/>
<ListItemSecondaryAction>
<Switch
edge="end"
color="primary"
edge='end'
color='primary'
checked={preference.attachToMenubar}
onChange={async (event) => {
await window.service.preference.set('attachToMenubar', event.target.checked);
@ -183,8 +197,8 @@ export function General(props: Required<ISectionProps>): JSX.Element {
<ListItemText primary={t('Preference.MenubarAlwaysOnTop')} secondary={t('Preference.MenubarAlwaysOnTopDetail')} />
<ListItemSecondaryAction>
<Switch
edge="end"
color="primary"
edge='end'
color='primary'
checked={preference.menuBarAlwaysOnTop}
onChange={async (event) => {
await window.service.preference.set('menuBarAlwaysOnTop', event.target.checked);
@ -200,7 +214,7 @@ export function General(props: Required<ISectionProps>): JSX.Element {
<ListItemText
primary={t('Preference.SwipeWithThreeFingersToNavigate')}
secondary={
<Trans t={t} i18nKey="Preference.SwipeWithThreeFingersToNavigateDescription">
<Trans t={t} i18nKey='Preference.SwipeWithThreeFingersToNavigateDescription'>
Navigate between pages with 3-finger gestures. Swipe left to go back or swipe right to go forward.
<br />
To enable it, you also need to change
@ -214,8 +228,8 @@ export function General(props: Required<ISectionProps>): JSX.Element {
/>
<ListItemSecondaryAction>
<Switch
edge="end"
color="primary"
edge='end'
color='primary'
checked={preference.swipeToNavigate}
onChange={async (event) => {
await window.service.preference.set('swipeToNavigate', event.target.checked);

View file

@ -1,13 +1,13 @@
import { useTranslation } from 'react-i18next';
import { Divider, List, ListItemSecondaryAction, MenuItem, Select, Switch } from '@material-ui/core';
import ChevronRightIcon from '@material-ui/icons/ChevronRight';
import { useTranslation } from 'react-i18next';
import type { ISectionProps } from '../useSections';
import { Paper, SectionTitle, ListItem, ListItemText, InputLabel } from '../PreferenceComponents';
import { usePreferenceObservable } from '@services/preferences/hooks';
import { WindowNames } from '@services/windows/WindowProperties';
import { hunspellLanguagesMap } from '@/constants/hunspellLanguages';
import { usePromiseValue } from '@/helpers/useServiceValue';
import { usePreferenceObservable } from '@services/preferences/hooks';
import { WindowNames } from '@services/windows/WindowProperties';
import { InputLabel, ListItem, ListItemText, Paper, SectionTitle } from '../PreferenceComponents';
import type { ISectionProps } from '../useSections';
export function Languages(props: Partial<ISectionProps> & { languageSelectorOnly?: boolean }): JSX.Element {
const { t } = useTranslation();
@ -24,23 +24,22 @@ export function Languages(props: Partial<ISectionProps> & { languageSelectorOnly
<SectionTitle ref={props.sections?.languages?.ref}>{t('Preference.Languages')}</SectionTitle>
<Paper elevation={0}>
<List dense disablePadding>
{preference === undefined || platform === undefined || supportedLanguagesMap === undefined || preference.language === undefined ? (
<ListItem>{t('Loading')}</ListItem>
) : (
{preference === undefined || platform === undefined || supportedLanguagesMap === undefined || preference.language === undefined ? <ListItem>{t('Loading')}</ListItem> : (
<>
<ListItem sx={{ justifyContent: 'space-between' }}>
<ListItemText primary={t('Preference.ChooseLanguage')} />
<InputLabel sx={{ flex: 1 }} id="demo-simple-select-label">
<InputLabel sx={{ flex: 1 }} id='demo-simple-select-label'>
{t('Preference.Languages')}
</InputLabel>
<Select
sx={{ flex: 2 }}
labelId="demo-simple-select-label"
labelId='demo-simple-select-label'
value={preference.language}
onChange={async (event) => {
await window.service.preference.set('language', event.target.value);
}}
autoWidth>
autoWidth
>
{Object.keys(supportedLanguagesMap).map((languageID) => (
<MenuItem value={languageID} key={languageID}>
{supportedLanguagesMap[languageID]}
@ -55,8 +54,8 @@ export function Languages(props: Partial<ISectionProps> & { languageSelectorOnly
<ListItemText primary={t('Preference.SpellCheck')} />
<ListItemSecondaryAction>
<Switch
edge="end"
color="primary"
edge='end'
color='primary'
checked={preference.spellcheck}
onChange={async (event) => {
await window.service.preference.set('spellcheck', event.target.checked);
@ -68,12 +67,17 @@ export function Languages(props: Partial<ISectionProps> & { languageSelectorOnly
{platform !== 'darwin' && (
<>
<Divider />
<ListItem button onClick={async () => await window.service.window.open(WindowNames.spellcheck)}>
<ListItem
button
onClick={async () => {
await window.service.window.open(WindowNames.spellcheck);
}}
>
<ListItemText
primary={t('Preference.SpellCheckLanguages')}
secondary={preference.spellcheckLanguages.map((code) => hunspellLanguagesMap[code]).join(' | ')}
/>
<ChevronRightIcon color="action" />
<ChevronRightIcon color='action' />
</ListItem>
</>
)}

View file

@ -1,10 +1,10 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Divider, List } from '@material-ui/core';
import ChevronRightIcon from '@material-ui/icons/ChevronRight';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { ListItem, ListItemText, Paper, SectionTitle } from '../PreferenceComponents';
import type { ISectionProps } from '../useSections';
import { Paper, SectionTitle, ListItem, ListItemText } from '../PreferenceComponents';
import { WindowNames } from '@services/windows/WindowProperties';
@ -16,24 +16,44 @@ export function Miscellaneous(props: ISectionProps): JSX.Element {
<SectionTitle ref={props.sections.misc.ref}>{t('Preference.Miscellaneous')}</SectionTitle>
<Paper elevation={0}>
<List dense disablePadding>
<ListItem button onClick={async () => await window.service.window.open(WindowNames.about)}>
<ListItem
button
onClick={async () => {
await window.service.window.open(WindowNames.about);
}}
>
<ListItemText primary={t('ContextMenu.About')} />
<ChevronRightIcon color="action" />
<ChevronRightIcon color='action' />
</ListItem>
<Divider />
<ListItem button onClick={async () => await window.service.native.open('https://github.com/tiddly-gittly/TidGi-desktop/')}>
<ListItem
button
onClick={async () => {
await window.service.native.open('https://github.com/tiddly-gittly/TidGi-desktop/');
}}
>
<ListItemText primary={t('Preference.WebSite')} />
<ChevronRightIcon color="action" />
<ChevronRightIcon color='action' />
</ListItem>
<Divider />
<ListItem button onClick={async () => await window.service.native.open('https://github.com/tiddly-gittly/TidGi-desktop/issues')}>
<ListItem
button
onClick={async () => {
await window.service.native.open('https://github.com/tiddly-gittly/TidGi-desktop/issues');
}}
>
<ListItemText primary={t('Preference.Support')} />
<ChevronRightIcon color="action" />
<ChevronRightIcon color='action' />
</ListItem>
<Divider />
<ListItem button onClick={() => window.service.native.quit()}>
<ListItem
button
onClick={() => {
window.service.native.quit();
}}
>
<ListItemText primary={t('ContextMenu.Quit')} />
<ChevronRightIcon color="action" />
<ChevronRightIcon color='action' />
</ListItem>
</List>
</Paper>

View file

@ -1,9 +1,9 @@
import { useTranslation } from 'react-i18next';
import { List } from '@material-ui/core';
import { useTranslation } from 'react-i18next';
import type { ISectionProps } from '../useSections';
import { Paper, SectionTitle, ListItem } from '../PreferenceComponents';
import { usePreferenceObservable } from '@services/preferences/hooks';
import { ListItem, Paper, SectionTitle } from '../PreferenceComponents';
import type { ISectionProps } from '../useSections';
export function Network(props: ISectionProps): JSX.Element {
const { t } = useTranslation();
@ -15,9 +15,7 @@ export function Network(props: ISectionProps): JSX.Element {
<SectionTitle ref={props.sections.network.ref}>{t('Preference.Network')}</SectionTitle>
<Paper elevation={0}>
<List dense disablePadding>
{preference === undefined ? (
<ListItem>{t('Loading')}</ListItem>
) : (
{preference === undefined ? <ListItem>{t('Loading')}</ListItem> : (
<>
<ListItem></ListItem>
</>

View file

@ -1,15 +1,15 @@
import { Trans, useTranslation } from 'react-i18next';
import semver from 'semver';
import TimePicker from '@material-ui/lab/TimePicker';
import { Divider, List, ListItemSecondaryAction, Switch } from '@material-ui/core';
import ChevronRightIcon from '@material-ui/icons/ChevronRight';
import TimePicker from '@material-ui/lab/TimePicker';
import type { ISectionProps } from '../useSections';
import { Link, ListItem, ListItemVertical, ListItemText, Paper, SectionTitle, TextField, TimePickerContainer } from '../PreferenceComponents';
import { usePromiseValue } from '@/helpers/useServiceValue';
import { usePreferenceObservable } from '@services/preferences/hooks';
import { WindowNames } from '@services/windows/WindowProperties';
import { usePromiseValue } from '@/helpers/useServiceValue';
import { Link, ListItem, ListItemText, ListItemVertical, Paper, SectionTitle, TextField, TimePickerContainer } from '../PreferenceComponents';
import type { ISectionProps } from '../useSections';
export function Notifications(props: Required<ISectionProps>): JSX.Element {
const { t } = useTranslation();
@ -29,42 +29,57 @@ export function Notifications(props: Required<ISectionProps>): JSX.Element {
<SectionTitle ref={props.sections.notifications.ref}>{t('Preference.Notifications')}</SectionTitle>
<Paper elevation={0}>
<List dense disablePadding>
{preference === undefined || platform === undefined ? (
<ListItem>{t('Loading')}</ListItem>
) : (
{preference === undefined || platform === undefined ? <ListItem>{t('Loading')}</ListItem> : (
<>
<ListItem button onClick={async () => await window.service.window.open(WindowNames.notifications)}>
<ListItem
button
onClick={async () => {
await window.service.window.open(WindowNames.notifications);
}}
>
<ListItemText primary={t('Preference.NotificationsDetail')} />
<ChevronRightIcon color="action" />
<ChevronRightIcon color='action' />
</ListItem>
<Divider />
<ListItemVertical>
<ListItemText primary={t('Preference.NotificationsDisableSchedule')} />
<TimePickerContainer>
<TimePicker
label="from"
label='from'
renderInput={(timeProps) => <TextField {...timeProps} />}
value={new Date(preference.pauseNotificationsByScheduleFrom)}
onChange={async (d) => await window.service.preference.set('pauseNotificationsByScheduleFrom', (d ?? '').toString())}
onClose={async () => await window.service.window.updateWindowMeta(WindowNames.preferences, { preventClosingWindow: false })}
onOpen={async () => await window.service.window.updateWindowMeta(WindowNames.preferences, { preventClosingWindow: true })}
onChange={async (d) => {
await window.service.preference.set('pauseNotificationsByScheduleFrom', (d ?? '').toString());
}}
onClose={async () => {
await window.service.window.updateWindowMeta(WindowNames.preferences, { preventClosingWindow: false });
}}
onOpen={async () => {
await window.service.window.updateWindowMeta(WindowNames.preferences, { preventClosingWindow: true });
}}
disabled={!preference.pauseNotificationsBySchedule}
/>
<TimePicker
label="to"
label='to'
renderInput={(timeProps) => <TextField {...timeProps} />}
value={new Date(preference.pauseNotificationsByScheduleTo)}
onChange={async (d) => await window.service.preference.set('pauseNotificationsByScheduleTo', (d ?? '').toString())}
onClose={async () => await window.service.window.updateWindowMeta(WindowNames.preferences, { preventClosingWindow: false })}
onOpen={async () => await window.service.window.updateWindowMeta(WindowNames.preferences, { preventClosingWindow: true })}
onChange={async (d) => {
await window.service.preference.set('pauseNotificationsByScheduleTo', (d ?? '').toString());
}}
onClose={async () => {
await window.service.window.updateWindowMeta(WindowNames.preferences, { preventClosingWindow: false });
}}
onOpen={async () => {
await window.service.window.updateWindowMeta(WindowNames.preferences, { preventClosingWindow: true });
}}
disabled={!preference.pauseNotificationsBySchedule}
/>
</TimePickerContainer>
({window.Intl.DateTimeFormat().resolvedOptions().timeZone})
<ListItemSecondaryAction>
<Switch
edge="end"
color="primary"
edge='end'
color='primary'
checked={preference.pauseNotificationsBySchedule}
onChange={async (event) => {
await window.service.preference.set('pauseNotificationsBySchedule', event.target.checked);
@ -77,8 +92,8 @@ export function Notifications(props: Required<ISectionProps>): JSX.Element {
<ListItemText primary={t('Preference.NotificationsMuteAudio')} />
<ListItemSecondaryAction>
<Switch
edge="end"
color="primary"
edge='end'
color='primary'
checked={preference.pauseNotificationsMuteAudio}
onChange={async (event) => {
await window.service.preference.set('pauseNotificationsMuteAudio', event.target.checked);
@ -88,11 +103,11 @@ export function Notifications(props: Required<ISectionProps>): JSX.Element {
</ListItem>
<Divider />
<ListItem>
<ListItemText primary="Show unread count badge" />
<ListItemText primary='Show unread count badge' />
<ListItemSecondaryAction>
<Switch
edge="end"
color="primary"
edge='end'
color='primary'
checked={preference.unreadCountBadge}
onChange={async (event) => {
await window.service.preference.set('unreadCountBadge', event.target.checked);
@ -109,14 +124,15 @@ export function Notifications(props: Required<ISectionProps>): JSX.Element {
title: t('Preference.TestNotification'),
body: t('Preference.ItIsWorking'),
});
}}>
}}
>
<ListItemText
primary={t('Preference.TestNotification')}
secondary={(() => {
// only show this message on macOS Catalina 10.15 & above
if (platform === 'darwin' && oSVersion !== undefined && semver.gte(oSVersion, '10.15.0')) {
return (
<Trans t={t} i18nKey="Preference.TestNotificationDescription">
<Trans t={t} i18nKey='Preference.TestNotificationDescription'>
<span>
If notifications dont show up, make sure you enable notifications in
<b>macOS Preferences Notifications TidGi</b>.
@ -126,25 +142,25 @@ export function Notifications(props: Required<ISectionProps>): JSX.Element {
}
})()}
/>
<ChevronRightIcon color="action" />
<ChevronRightIcon color='action' />
</ListItem>
<Divider />
<ListItem>
<ListItemText
secondary={
<Trans t={t} i18nKey="Preference.HowToEnableNotifications">
<Trans t={t} i18nKey='Preference.HowToEnableNotifications'>
<span>
TidGi supports notifications out of the box. But for some cases, to receive notifications, you will need to manually configure
additional web app settings.
TidGi supports notifications out of the box. But for some cases, to receive notifications, you will need to manually configure additional web app settings.
</span>
<Link
onClick={async () =>
await window.service.native.open('https://github.com/atomery/webcatalog/wiki/How-to-Enable-Notifications-in-Web-Apps')
}
onClick={async () => {
await window.service.native.open('https://github.com/atomery/webcatalog/wiki/How-to-Enable-Notifications-in-Web-Apps');
}}
onKeyDown={(event) => {
if (event.key !== 'Enter') return;
void window.service.native.open('https://github.com/atomery/webcatalog/wiki/How-to-Enable-Notifications-in-Web-Apps');
}}>
}}
>
Learn more
</Link>
<span>.</span>

View file

@ -1,10 +1,10 @@
import { Divider, List, ListItemSecondaryAction, Switch } from '@material-ui/core';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Divider, List, ListItemSecondaryAction, Switch } from '@material-ui/core';
import type { ISectionProps } from '../useSections';
import { Paper, SectionTitle, ListItem, ListItemText } from '../PreferenceComponents';
import { usePreferenceObservable } from '@services/preferences/hooks';
import { ListItem, ListItemText, Paper, SectionTitle } from '../PreferenceComponents';
import type { ISectionProps } from '../useSections';
export function Performance(props: Required<ISectionProps>): JSX.Element {
const { t } = useTranslation();
@ -16,16 +16,14 @@ export function Performance(props: Required<ISectionProps>): JSX.Element {
<SectionTitle ref={props.sections.performance.ref}>{t('Preference.Performance')}</SectionTitle>
<Paper elevation={0}>
<List dense disablePadding>
{preference === undefined ? (
<ListItem>{t('Loading')}</ListItem>
) : (
{preference === undefined ? <ListItem>{t('Loading')}</ListItem> : (
<>
<ListItem>
<ListItemText primary={t('Preference.HibernateAllUnusedWorkspaces')} secondary={t('Preference.HibernateAllUnusedWorkspacesDescription')} />
<ListItemSecondaryAction>
<Switch
edge="end"
color="primary"
edge='end'
color='primary'
checked={preference.hibernateUnusedWorkspacesAtLaunch}
onChange={async (event) => {
await window.service.preference.set('hibernateUnusedWorkspacesAtLaunch', event.target.checked);
@ -39,8 +37,8 @@ export function Performance(props: Required<ISectionProps>): JSX.Element {
<ListItemText primary={t('Preference.hardwareAcceleration')} />
<ListItemSecondaryAction>
<Switch
edge="end"
color="primary"
edge='end'
color='primary'
checked={preference.useHardwareAcceleration}
onChange={async (event) => {
await window.service.preference.set('useHardwareAcceleration', event.target.checked);

View file

@ -1,11 +1,11 @@
import React from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { Divider, List, ListItemSecondaryAction, Switch } from '@material-ui/core';
import ChevronRightIcon from '@material-ui/icons/ChevronRight';
import React from 'react';
import { Trans, useTranslation } from 'react-i18next';
import type { ISectionProps } from '../useSections';
import { Link, Paper, SectionTitle, ListItem, ListItemText } from '../PreferenceComponents';
import { usePreferenceObservable } from '@services/preferences/hooks';
import { Link, ListItem, ListItemText, Paper, SectionTitle } from '../PreferenceComponents';
import type { ISectionProps } from '../useSections';
export function PrivacyAndSecurity(props: Required<ISectionProps>): JSX.Element {
const { t } = useTranslation();
@ -17,16 +17,14 @@ export function PrivacyAndSecurity(props: Required<ISectionProps>): JSX.Element
<SectionTitle ref={props.sections.privacy.ref}>{t('Preference.PrivacyAndSecurity')}</SectionTitle>
<Paper elevation={0}>
<List dense disablePadding>
{preference === undefined ? (
<ListItem>{t('Loading')}</ListItem>
) : (
{preference === undefined ? <ListItem>{t('Loading')}</ListItem> : (
<>
<ListItem>
<ListItemText primary={t('Preference.ShareBrowsingData')} />
<ListItemSecondaryAction>
<Switch
edge="end"
color="primary"
edge='end'
color='primary'
checked={preference.shareWorkspaceBrowsingData}
onChange={async (event) => {
await window.service.preference.set('shareWorkspaceBrowsingData', event.target.checked);
@ -40,16 +38,17 @@ export function PrivacyAndSecurity(props: Required<ISectionProps>): JSX.Element
<ListItemText
primary={t('Preference.IgnoreCertificateErrors')}
secondary={
<Trans t={t} i18nKey="Preference.IgnoreCertificateErrorsDescription">
<span>Not recommended. </span>
<Trans t={t} i18nKey='Preference.IgnoreCertificateErrorsDescription'>
<span>Not recommended.</span>
<Link
onClick={async () =>
await window.service.native.open('https://groups.google.com/a/chromium.org/d/msg/security-dev/mB2KJv_mMzM/ddMteO9RjXEJ')
}
onClick={async () => {
await window.service.native.open('https://groups.google.com/a/chromium.org/d/msg/security-dev/mB2KJv_mMzM/ddMteO9RjXEJ');
}}
onKeyDown={(event) => {
if (event.key !== 'Enter') return;
void window.service.native.open('https://groups.google.com/a/chromium.org/d/msg/security-dev/mB2KJv_mMzM/ddMteO9RjXEJ');
}}>
}}
>
Learn more
</Link>
.
@ -58,8 +57,8 @@ export function PrivacyAndSecurity(props: Required<ISectionProps>): JSX.Element
/>
<ListItemSecondaryAction>
<Switch
edge="end"
color="primary"
edge='end'
color='primary'
checked={preference.ignoreCertificateErrors}
onChange={async (event) => {
await window.service.preference.set('ignoreCertificateErrors', event.target.checked);
@ -69,15 +68,23 @@ export function PrivacyAndSecurity(props: Required<ISectionProps>): JSX.Element
</ListItemSecondaryAction>
</ListItem>
<Divider />
<ListItem button onClick={async () => await window.service.workspaceView.clearBrowsingDataWithConfirm()}>
<ListItem
button
onClick={async () => {
await window.service.workspaceView.clearBrowsingDataWithConfirm();
}}
>
<ListItemText primary={t('Preference.ClearBrowsingData')} secondary={t('Preference.ClearBrowsingDataDescription')} />
<ChevronRightIcon color="action" />
<ChevronRightIcon color='action' />
</ListItem>
<Divider />
<ListItem
button
onClick={async () => await window.service.native.open('https://github.com/tiddly-gittly/TidGi-Desktop/blob/master/PrivacyPolicy.md')}>
<ListItemText primary="Privacy Policy" />
onClick={async () => {
await window.service.native.open('https://github.com/tiddly-gittly/TidGi-Desktop/blob/master/PrivacyPolicy.md');
}}
>
<ListItemText primary='Privacy Policy' />
</ListItem>
</>
)}

View file

@ -1,18 +1,18 @@
import { Divider, List, ListItemSecondaryAction, Switch } from '@material-ui/core';
import TimePicker from '@material-ui/lab/TimePicker';
import fromUnixTime from 'date-fns/fromUnixTime';
import setDate from 'date-fns/setDate';
import setMonth from 'date-fns/setMonth';
import setYear from 'date-fns/setYear';
import React from 'react';
import { useTranslation } from 'react-i18next';
import fromUnixTime from 'date-fns/fromUnixTime';
import setYear from 'date-fns/setYear';
import setMonth from 'date-fns/setMonth';
import setDate from 'date-fns/setDate';
import TimePicker from '@material-ui/lab/TimePicker';
import { Divider, List, ListItemSecondaryAction, Switch } from '@material-ui/core';
import { TokenForm } from '../../../components/TokenForm';
import type { ISectionProps } from '../useSections';
import { Paper, SectionTitle, TextField, TimePickerContainer, ListItem, ListItemText } from '../PreferenceComponents';
import { usePreferenceObservable } from '@services/preferences/hooks';
import { WindowNames } from '@services/windows/WindowProperties';
import { ListItem, ListItemText, Paper, SectionTitle, TextField, TimePickerContainer } from '../PreferenceComponents';
import type { ISectionProps } from '../useSections';
export function Sync(props: Required<ISectionProps>): JSX.Element {
const { t } = useTranslation();
@ -24,9 +24,7 @@ export function Sync(props: Required<ISectionProps>): JSX.Element {
<SectionTitle ref={props.sections.sync.ref}>{t('Preference.Sync')}</SectionTitle>
<Paper elevation={0}>
<List dense disablePadding>
{preference === undefined ? (
<ListItem>{t('Loading')}</ListItem>
) : (
{preference === undefined ? <ListItem>{t('Loading')}</ListItem> : (
<>
<ListItem>
<TokenForm />
@ -36,8 +34,8 @@ export function Sync(props: Required<ISectionProps>): JSX.Element {
<ListItemText primary={`${t('Preference.SyncBeforeShutdown')} (Mac/Linux)`} secondary={t('Preference.SyncBeforeShutdownDescription')} />
<ListItemSecondaryAction>
<Switch
edge="end"
color="primary"
edge='end'
color='primary'
checked={preference.syncBeforeShutdown}
onChange={async (event) => {
await window.service.preference.set('syncBeforeShutdown', event.target.checked);
@ -50,8 +48,8 @@ export function Sync(props: Required<ISectionProps>): JSX.Element {
<ListItemText primary={`${t('Preference.SyncOnlyWhenNoDraft')}`} secondary={t('Preference.SyncOnlyWhenNoDraftDescription')} />
<ListItemSecondaryAction>
<Switch
edge="end"
color="primary"
edge='end'
color='primary'
checked={preference.syncOnlyWhenNoDraft}
onChange={async (event) => {
await window.service.preference.set('syncOnlyWhenNoDraft', event.target.checked);
@ -65,10 +63,10 @@ export function Sync(props: Required<ISectionProps>): JSX.Element {
<TimePickerContainer>
<TimePicker
ampm={false}
openTo="hours"
openTo='hours'
views={['hours', 'minutes', 'seconds']}
inputFormat="HH:mm:ss"
mask="__:__:__"
inputFormat='HH:mm:ss'
mask='__:__:__'
renderInput={(timeProps) => <TextField {...timeProps} />}
value={fromUnixTime(preference.syncDebounceInterval / 1000 + new Date().getTimezoneOffset() * 60)}
onChange={async (date) => {
@ -78,8 +76,12 @@ export function Sync(props: Required<ISectionProps>): JSX.Element {
await window.service.preference.set('syncDebounceInterval', utcTime);
props.requestRestartCountDown();
}}
onClose={async () => await window.service.window.updateWindowMeta(WindowNames.preferences, { preventClosingWindow: false })}
onOpen={async () => await window.service.window.updateWindowMeta(WindowNames.preferences, { preventClosingWindow: true })}
onClose={async () => {
await window.service.window.updateWindowMeta(WindowNames.preferences, { preventClosingWindow: false });
}}
onOpen={async () => {
await window.service.window.updateWindowMeta(WindowNames.preferences, { preventClosingWindow: true });
}}
/>
</TimePickerContainer>
</ListItem>

View file

@ -1,12 +1,12 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { List, MenuItem } from '@material-ui/core';
import ChevronRightIcon from '@material-ui/icons/ChevronRight';
import React from 'react';
import { useTranslation } from 'react-i18next';
import PopUpMenuItem from '@/components/PopUpMenuItem';
import { getOpenAtLoginString, useSystemPreferenceObservable } from '@services/systemPreferences/hooks';
import { ListItem, ListItemText, Paper, SectionTitle } from '../PreferenceComponents';
import type { ISectionProps } from '../useSections';
import { Paper, SectionTitle, ListItem, ListItemText } from '../PreferenceComponents';
export function System(props: ISectionProps): JSX.Element {
const { t } = useTranslation();
@ -18,25 +18,39 @@ export function System(props: ISectionProps): JSX.Element {
<SectionTitle ref={props.sections.system.ref}>{t('Preference.System')}</SectionTitle>
<Paper elevation={0}>
<List dense disablePadding>
{systemPreference === undefined ? (
<ListItem>{t('Loading')}</ListItem>
) : (
{systemPreference === undefined ? <ListItem>{t('Loading')}</ListItem> : (
<>
<PopUpMenuItem
id="openAtLogin"
id='openAtLogin'
buttonElement={
<ListItem button>
<ListItemText primary={t('Preference.OpenAtLogin')} secondary={getOpenAtLoginString(systemPreference.openAtLogin)} />
<ChevronRightIcon color="action" />
<ChevronRightIcon color='action' />
</ListItem>
}>
<MenuItem dense onClick={async () => await window.service.systemPreference.setSystemPreference('openAtLogin', 'yes')}>
}
>
<MenuItem
dense
onClick={async () => {
await window.service.systemPreference.setSystemPreference('openAtLogin', 'yes');
}}
>
{t('Yes')}
</MenuItem>
<MenuItem dense onClick={async () => await window.service.systemPreference.setSystemPreference('openAtLogin', 'yes-hidden')}>
<MenuItem
dense
onClick={async () => {
await window.service.systemPreference.setSystemPreference('openAtLogin', 'yes-hidden');
}}
>
{t('Preference.OpenAtLoginMinimized')}
</MenuItem>
<MenuItem dense onClick={async () => await window.service.systemPreference.setSystemPreference('openAtLogin', 'no')}>
<MenuItem
dense
onClick={async () => {
await window.service.systemPreference.setSystemPreference('openAtLogin', 'no');
}}
>
{t('No')}
</MenuItem>
</PopUpMenuItem>

View file

@ -1,12 +1,12 @@
import useDebouncedCallback from 'beautiful-react-hooks/useDebouncedCallback';
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import useDebouncedCallback from 'beautiful-react-hooks/useDebouncedCallback';
import { List } from '@material-ui/core';
import type { ISectionProps } from '../useSections';
import { useUserInfoObservable } from '@services/auth/hooks';
import { ListItemText, ListItemVertical, Paper, SectionTitle, TextField } from '../PreferenceComponents';
import type { ISectionProps } from '../useSections';
export function TiddlyWiki(props: Partial<ISectionProps>): JSX.Element {
const { t } = useTranslation();
@ -28,9 +28,7 @@ export function TiddlyWiki(props: Partial<ISectionProps>): JSX.Element {
<SectionTitle ref={props.sections?.wiki?.ref}>{t('Preference.TiddlyWiki')}</SectionTitle>
<Paper elevation={0}>
<List dense disablePadding>
{userInfo === undefined ? (
<ListItemVertical>{t('Loading')}</ListItemVertical>
) : (
{userInfo === undefined ? <ListItemVertical>{t('Loading')}</ListItemVertical> : (
<ListItemVertical>
<ListItemText primary={t('Preference.WikiMetaData')} secondary={t('Preference.WikiMetaDataDescription')} />
<TextField

View file

@ -1,13 +1,13 @@
import { useTranslation } from 'react-i18next';
import { Divider, List, ListItemSecondaryAction, Switch } from '@material-ui/core';
import ChevronRightIcon from '@material-ui/icons/ChevronRight';
import { useTranslation } from 'react-i18next';
import type { ISectionProps } from '../useSections';
import { Paper, SectionTitle, ListItem, ListItemText } from '../PreferenceComponents';
import { usePreferenceObservable } from '@services/preferences/hooks';
import { useUpdaterObservable, getUpdaterMessage } from '@services/updater/hooks';
import { IUpdaterStatus } from '@services/updater/interface';
import { latestStableUpdateUrl } from '@/constants/urls';
import { usePreferenceObservable } from '@services/preferences/hooks';
import { getUpdaterMessage, useUpdaterObservable } from '@services/updater/hooks';
import { IUpdaterStatus } from '@services/updater/interface';
import { ListItem, ListItemText, Paper, SectionTitle } from '../PreferenceComponents';
import type { ISectionProps } from '../useSections';
export function Updates(props: Required<ISectionProps>): JSX.Element {
const { t } = useTranslation();
@ -20,33 +20,34 @@ export function Updates(props: Required<ISectionProps>): JSX.Element {
<SectionTitle ref={props.sections.updates.ref}>{t('Preference.Network')}</SectionTitle>
<Paper elevation={0}>
<List dense disablePadding>
{preference === undefined || updaterMetaData === undefined ? (
<ListItem>{t('Loading')}</ListItem>
) : (
{preference === undefined || updaterMetaData === undefined ? <ListItem>{t('Loading')}</ListItem> : (
<>
<ListItem
button
onClick={
updaterMetaData.status === IUpdaterStatus.updateAvailable
? async () => await window.service.native.open(updaterMetaData.info?.latestReleasePageUrl ?? latestStableUpdateUrl)
: async () => await window.service.updater.checkForUpdates()
}
disabled={updaterMetaData.status === IUpdaterStatus.checkingForUpdate || updaterMetaData.status === IUpdaterStatus.downloadProgress}>
onClick={updaterMetaData.status === IUpdaterStatus.updateAvailable
? async () => {
await window.service.native.open(updaterMetaData.info?.latestReleasePageUrl ?? latestStableUpdateUrl);
}
: async () => {
await window.service.updater.checkForUpdates();
}}
disabled={updaterMetaData.status === IUpdaterStatus.checkingForUpdate || updaterMetaData.status === IUpdaterStatus.downloadProgress}
>
{updaterMetaData.status !== undefined && (
<ListItemText
primary={t(`Updater.${updaterMetaData.status}`)}
secondary={getUpdaterMessage(updaterMetaData.status, updaterMetaData.info, t)}
/>
)}
<ChevronRightIcon color="action" />
<ChevronRightIcon color='action' />
</ListItem>
<Divider />
<ListItem>
<ListItemText primary={t('Preference.ReceivePreReleaseUpdates')} />
<ListItemSecondaryAction>
<Switch
edge="end"
color="primary"
edge='end'
color='primary'
checked={preference.allowPrerelease}
onChange={async (event) => {
await window.service.preference.set('allowPrerelease', event.target.checked);

View file

@ -1,10 +1,14 @@
import { useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { SvgIconTypeMap } from '@material-ui/core';
import { OverridableComponent } from '@material-ui/core/OverridableComponent';
import BuildIcon from '@material-ui/icons/Build';
import CloudDownloadIcon from '@material-ui/icons/CloudDownload';
import CodeIcon from '@material-ui/icons/Code';
import GitHubIcon from '@material-ui/icons/GitHub';
import LanguageIcon from '@material-ui/icons/Language';
import MenuBookIcon from '@material-ui/icons/MenuBook';
import MoreHorizIcon from '@material-ui/icons/MoreHoriz';
import NotificationsIcon from '@material-ui/icons/Notifications';
import PowerIcon from '@material-ui/icons/Power';
@ -13,10 +17,6 @@ import SecurityIcon from '@material-ui/icons/Security';
import StorefrontIcon from '@material-ui/icons/Storefront';
import SystemUpdateAltIcon from '@material-ui/icons/SystemUpdateAlt';
import WidgetsIcon from '@material-ui/icons/Widgets';
import GitHubIcon from '@material-ui/icons/GitHub';
import MenuBookIcon from '@material-ui/icons/MenuBook';
import { OverridableComponent } from '@material-ui/core/OverridableComponent';
import { SvgIconTypeMap } from '@material-ui/core';
import { PreferenceSections } from '@services/preferences/interface';

View file

@ -1,7 +1,7 @@
import React from 'react';
import styled from 'styled-components';
import { useTranslation } from 'react-i18next';
import { Helmet } from 'react-helmet';
import { useTranslation } from 'react-i18next';
import styled from 'styled-components';
import ButtonRaw from '@material-ui/core/Button';
import Checkbox from '@material-ui/core/Checkbox';
@ -10,8 +10,8 @@ import ListItem from '@material-ui/core/ListItem';
import ListItemIcon from '@material-ui/core/ListItemIcon';
import ListItemText from '@material-ui/core/ListItemText';
import { hunspellLanguagesMap, HunspellLanguages } from '../../constants/hunspellLanguages';
import { usePreferenceObservable } from '@services/preferences/hooks';
import { HunspellLanguages, hunspellLanguagesMap } from '../../constants/hunspellLanguages';
const Root = styled.div`
display: flex;
@ -53,7 +53,7 @@ export default function SpellcheckLanguages(): JSX.Element {
}
return (
<Root>
<div id="test" data-usage="For spectron automating testing" />
<div id='test' data-usage='For spectron automating testing' />
<Helmet>
<title>{t('Preference.SpellCheckLanguages')}</title>
</Helmet>
@ -72,10 +72,11 @@ export default function SpellcheckLanguages(): JSX.Element {
} else {
void window.service.preference.set('spellcheckLanguages', [...preference.spellcheckLanguages, code]);
}
}}>
}}
>
<ListItemIcon>
<Checkbox
edge="start"
edge='start'
checked={preference.spellcheckLanguages.includes(code)}
disabled={preference.spellcheckLanguages.length < 2 && preference.spellcheckLanguages.includes(code)}
/>
@ -85,10 +86,16 @@ export default function SpellcheckLanguages(): JSX.Element {
))}
</Top>
<Bottom>
<Button color="primary" disabled>
<Button color='primary' disabled>
This Page is Auto Saved
</Button>
<Button onClick={async () => await window.remote.closeCurrentWindow()}>Close</Button>
<Button
onClick={async () => {
await window.remote.closeCurrentWindow();
}}
>
Close
</Button>
</Bottom>
</Root>
);

View file

@ -1,29 +1,36 @@
import React from 'react';
import { WindowNames } from '@services/windows/WindowProperties';
import React from 'react';
import Main from './Main';
import AboutPage from './About';
import { AddWorkspace as DialogAddWorkspace } from './AddWorkspace';
import EditWorkspace from './EditWorkspace';
import Main from './Main';
import DialogNotifications from './Notifications';
import DialogPreferences from './Preferences';
import SpellcheckLanguages from './SpellcheckLanguages';
export function Pages(): JSX.Element {
switch (window.meta.windowName) {
case WindowNames.about:
case WindowNames.about: {
return <AboutPage />;
case WindowNames.addWorkspace:
}
case WindowNames.addWorkspace: {
return <DialogAddWorkspace />;
case WindowNames.editWorkspace:
}
case WindowNames.editWorkspace: {
return <EditWorkspace />;
case WindowNames.notifications:
}
case WindowNames.notifications: {
return <DialogNotifications />;
case WindowNames.preferences:
}
case WindowNames.preferences: {
return <DialogPreferences />;
case WindowNames.spellcheck:
}
case WindowNames.spellcheck: {
return <SpellcheckLanguages />;
default:
}
default: {
return <Main />;
}
}
}

View file

@ -1,8 +1,8 @@
// on production build, if we try to redirect to http://localhost:3012 , we will reach chrome-error://chromewebdata/ , but we can easily get back
// this happens when we are redirected by OAuth login
import { context, window as windowService } from './services';
import { windowName } from './browserViewMetaData';
import { WindowNames } from '@services/windows/WindowProperties';
import { windowName } from './browserViewMetaData';
import { context, window as windowService } from './services';
const CHECK_LOADED_INTERVAL = 500;
let CHROME_ERROR_PATH: string | undefined;

View file

@ -1,6 +1,6 @@
import { contextBridge, ipcRenderer } from 'electron';
import { MetaDataChannel } from '@/constants/channels';
import { WindowNames, WindowMeta } from '@services/windows/WindowProperties';
import { WindowMeta, WindowNames } from '@services/windows/WindowProperties';
import { contextBridge, ipcRenderer } from 'electron';
const metaDataArguments = process.argv
.filter((item) => item.startsWith(MetaDataChannel.browserViewMetaData))

View file

@ -1,5 +1,5 @@
import { contextBridge, ipcRenderer } from 'electron';
import { preloadBindings } from '@services/libs/i18n/preloadBindings';
import { contextBridge, ipcRenderer } from 'electron';
const i18n = {
i18nextElectronBackend: preloadBindings(ipcRenderer),

View file

@ -1,5 +1,5 @@
import { contextBridge, ipcRenderer } from 'electron';
import { WikiChannel } from '@/constants/channels';
import { contextBridge, ipcRenderer } from 'electron';
export const logMethods = {
/**
@ -7,7 +7,7 @@ export const logMethods = {
* @param messageSetter can be wikiCreationMessageSetter from a useEffect
* @returns
*/
registerWikiCreationMessage: (messageSetter: (message: string) => void): (() => void) => {
registerWikiCreationMessage: (messageSetter: (message: string) => void): () => void => {
const handleNextMessage = (_event: Electron.IpcRendererEvent, message: string): void => {
messageSetter(message);
};

View file

@ -1,10 +1,10 @@
import { contextBridge, ipcRenderer, MenuItemConstructorOptions, webFrame } from 'electron';
import { ViewChannel, WindowChannel } from '@/constants/channels';
import { rendererMenuItemProxy } from '@services/menu/contextMenu/rendererMenuItemProxy';
import { IOnContextMenuInfo } from '@services/menu/interface';
import { contextBridge, ipcRenderer, MenuItemConstructorOptions, webFrame } from 'electron';
import * as service from './services';
import { windowName } from './browserViewMetaData';
import * as service from './services';
export const remoteMethods = {
buildContextMenuAndPopup: async (menus: MenuItemConstructorOptions[], parameters: IOnContextMenuInfo): Promise<() => void> => {

View file

@ -7,9 +7,9 @@
import { createProxy } from 'electron-ipc-cat/client';
import { AsyncifyProxy } from 'electron-ipc-cat/common';
import { IAuthenticationService, AuthenticationServiceIPCDescriptor } from '@services/auth/interface';
import { IContextService, ContextServiceIPCDescriptor } from '@services/context/interface';
import { IGitService, GitServiceIPCDescriptor } from '@services/git/interface';
import { AuthenticationServiceIPCDescriptor, IAuthenticationService } from '@services/auth/interface';
import { ContextServiceIPCDescriptor, IContextService } from '@services/context/interface';
import { GitServiceIPCDescriptor, IGitService } from '@services/git/interface';
import { IMenuService, MenuServiceIPCDescriptor } from '@services/menu/interface';
import { INativeService, NativeServiceIPCDescriptor } from '@services/native/interface';
import { INotificationService, NotificationServiceIPCDescriptor } from '@services/notifications/interface';

View file

@ -1,6 +1,6 @@
import 'reflect-metadata';
import { ipcRenderer } from 'electron';
import { IServicesWithoutObservables, IServicesWithOnlyObservables } from 'electron-ipc-cat/common';
import { IServicesWithOnlyObservables, IServicesWithoutObservables } from 'electron-ipc-cat/common';
import './common/test';
import './common/i18n';

View file

@ -1,10 +1,10 @@
/* eslint-disable @typescript-eslint/no-misused-promises */
import { Channels, WorkspaceChannel } from '@/constants/channels';
import { webFrame } from 'electron';
import { WorkspaceChannel, Channels } from '@/constants/channels';
import './wikiOperation';
import { preference, workspace, workspaceView, menu } from './common/services';
import { IPossibleWindowMeta, WindowMeta, WindowNames } from '@services/windows/WindowProperties';
import { browserViewMetaData, windowName } from './common/browserViewMetaData';
import { menu, preference, workspace, workspaceView } from './common/services';
let handled = false;
const handleLoaded = (event: string): void => {
@ -64,11 +64,15 @@ async function executeJavaScriptInBrowserView(): Promise<void> {
if (windowName === WindowNames.view) {
// try to load as soon as dom is loaded
document.addEventListener('DOMContentLoaded', () => handleLoaded('document.on("DOMContentLoaded")'));
document.addEventListener('DOMContentLoaded', () => {
handleLoaded('document.on("DOMContentLoaded")');
});
// if user navigates between the same website
// DOMContentLoaded might not be triggered so double check with 'onload'
// https://github.com/atomery/webcatalog/issues/797
window.addEventListener('load', () => handleLoaded('window.on("onload")'));
window.addEventListener('load', () => {
handleLoaded('window.on("onload")');
});
window.addEventListener('message', async (event?: MessageEvent<{ type?: Channels; workspaceID?: string } | undefined>) => {
// set workspace to active when its notification is clicked
if (event?.data?.type === WorkspaceChannel.focusWorkspace) {

View file

@ -7,8 +7,8 @@
*
* You can use wrapped method in `services/wiki/index.ts` 's `wikiOperation()` instead. Available operations are registered in `src/services/wiki/wikiOperations.ts`
*/
import { ipcRenderer, webFrame } from 'electron';
import { WikiChannel } from '@/constants/channels';
import { ipcRenderer, webFrame } from 'electron';
export const wikiOperations = {
[WikiChannel.setState]: async (stateKey: string, content: string) => {
@ -26,13 +26,12 @@ export const wikiOperations = {
* @param script js statement to be executed, nothing will be returned
*/
async function executeTWJavaScriptWhenIdle(script: string, options?: { onlyWhenVisible?: boolean }): Promise<void> {
const executeHandlerCode =
options?.onlyWhenVisible === true
? `
const executeHandlerCode = options?.onlyWhenVisible === true
? `
if (document.visibilityState === 'visible') {
handler();
}`
: `handler();`;
: `handler();`;
await webFrame.executeJavaScript(`
new Promise((resolve, reject) => {
const handler = () => {
@ -98,8 +97,9 @@ ipcRenderer.on(WikiChannel.syncProgress, async (event, message: string) => {
{ onlyWhenVisible: true },
);
});
ipcRenderer.on(WikiChannel.setState, (_event: Electron.IpcRendererEvent, ...rest: Parameters<typeof wikiOperations[WikiChannel.setState]>) =>
wikiOperations[WikiChannel.setState](...rest),
ipcRenderer.on(
WikiChannel.setState,
(_event: Electron.IpcRendererEvent, ...rest: Parameters<typeof wikiOperations[WikiChannel.setState]>) => wikiOperations[WikiChannel.setState](...rest),
);
ipcRenderer.on(WikiChannel.generalNotification, async (event, message: string) => {
await executeTWJavaScriptWhenIdle(`

View file

@ -1,23 +1,23 @@
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
/* eslint-disable promise/always-return */
import React from 'react';
import i18n from 'i18next';
import { ThemeProvider } from 'styled-components';
import React from 'react';
import { createRoot } from 'react-dom/client';
import { ThemeProvider } from 'styled-components';
import CssBaseline from '@material-ui/core/CssBaseline';
import StyledEngineProvider from '@material-ui/core/StyledEngineProvider';
import DateFnsUtils from '@material-ui/lab/AdapterDateFns';
import LocalizationProvider from '@material-ui/lab/LocalizationProvider';
import CssBaseline from '@material-ui/core/CssBaseline';
import { I18nextProvider } from 'react-i18next';
import 'typeface-roboto/index.css';
import { useThemeObservable } from '@services/theme/hooks';
import { darkTheme, lightTheme } from '@services/theme/defaultTheme';
import { useThemeObservable } from '@services/theme/hooks';
import { initI18N } from './i18n';
import 'electron-ipc-cat/fixContextIsolation';
import { Pages } from './pages';
import { RootStyle } from './components/RootStyle';
import { Pages } from './pages';
function App(): JSX.Element {
const theme = useThemeObservable();
@ -41,7 +41,7 @@ function App(): JSX.Element {
}
void window.remote.setVisualZoomLevelLimits(1, 1);
const container = document.getElementById('app');
const container = document.querySelector('#app');
const root = createRoot(container!);
root.render(<App />);

View file

@ -1,8 +1,8 @@
import { useState, useEffect } from 'react';
import useObservable from 'beautiful-react-hooks/useObservable';
import { IUserInfos } from './interface';
import { SupportedStorageServices } from '@services/types';
import { IGitUserInfos } from '@services/git/interface';
import { SupportedStorageServices } from '@services/types';
import useObservable from 'beautiful-react-hooks/useObservable';
import { useEffect, useState } from 'react';
import { IUserInfos } from './interface';
export function useUserInfoObservable(): IUserInfos | undefined {
const [userInfo, userInfoSetter] = useState<IUserInfos | undefined>();

View file

@ -1,12 +1,12 @@
/* eslint-disable @typescript-eslint/require-await */
/* eslint-disable unicorn/no-null */
import { debounce } from 'lodash';
import { injectable } from 'inversify';
import settings from 'electron-settings';
import { IAuthingUserInfo, SupportedStorageServices } from '@services/types';
import { IAuthenticationService, IUserInfos, ServiceBranchTypes, ServiceEmailTypes, ServiceTokenTypes, ServiceUserNameTypes } from './interface';
import { BehaviorSubject } from 'rxjs';
import { IGitUserInfos } from '@services/git/interface';
import { IAuthingUserInfo, SupportedStorageServices } from '@services/types';
import settings from 'electron-settings';
import { injectable } from 'inversify';
import { debounce } from 'lodash';
import { BehaviorSubject } from 'rxjs';
import { IAuthenticationService, IUserInfos, ServiceBranchTypes, ServiceEmailTypes, ServiceTokenTypes, ServiceUserNameTypes } from './interface';
const defaultUserInfos = {
userName: '',

View file

@ -1,9 +1,9 @@
/* eslint-disable unicorn/no-null */
import { ProxyPropertyType } from 'electron-ipc-cat/common';
import { AuthenticationChannel } from '@/constants/channels';
import { BehaviorSubject } from 'rxjs';
import { IGitUserInfos } from '@services/git/interface';
import { SupportedStorageServices } from '@services/types';
import { ProxyPropertyType } from 'electron-ipc-cat/common';
import { BehaviorSubject } from 'rxjs';
export type ServiceTokenTypes = `${SupportedStorageServices}-token`;
export const getServiceTokenTypes = (serviceType: SupportedStorageServices): ServiceTokenTypes => `${serviceType}-token`;
@ -25,13 +25,15 @@ export const getServiceBranchTypes = (serviceType: SupportedStorageServices): Se
/** Git push: Git commit message branch, you may use different branch for different storage service */
type BranchRecord = Record<ServiceBranchTypes, string>;
export type IUserInfos = {
/** Default UserName in TiddlyWiki, each wiki can have different username, but fallback to this if not specific on */
userName: string;
} & Partial<TokenRecord> &
Partial<UserNameRecord> &
Partial<EmailRecord> &
Partial<BranchRecord>;
export type IUserInfos =
& {
/** Default UserName in TiddlyWiki, each wiki can have different username, but fallback to this if not specific on */
userName: string;
}
& Partial<TokenRecord>
& Partial<UserNameRecord>
& Partial<EmailRecord>
& Partial<BranchRecord>;
/**
* Handle login to Github GitLab Coding.net

View file

@ -1,14 +1,14 @@
/* eslint-disable @typescript-eslint/require-await */
import { app, net } from 'electron';
import process from 'process';
import os from 'os';
import { isElectronDevelopment } from '@/constants/isElectronDevelopment';
import { app, net } from 'electron';
import { injectable } from 'inversify';
import os from 'os';
import process from 'process';
import { IContextService, IContext, IPaths, IConstants } from './interface';
import * as paths from '@/constants/paths';
import * as appPaths from '@/constants/appPaths';
import { tiddlywikiLanguagesMap, supportedLanguagesMap } from '@/constants/languages';
import { supportedLanguagesMap, tiddlywikiLanguagesMap } from '@/constants/languages';
import * as paths from '@/constants/paths';
import { IConstants, IContext, IContextService, IPaths } from './interface';
@injectable()
export class ContextService implements IContextService {

View file

@ -1,5 +1,5 @@
import { ProxyPropertyType } from 'electron-ipc-cat/common';
import { ContextChannel } from '@/constants/channels';
import { ProxyPropertyType } from 'electron-ipc-cat/common';
export interface IPaths {
CHROME_ERROR_PATH: string;

View file

@ -1,12 +1,12 @@
/* eslint-disable @typescript-eslint/no-misused-promises */
import 'source-map-support/register';
import { expose } from 'threads/worker';
import { Observable } from 'rxjs';
import { clone, commitAndSync, GitStep, ILoggerContext, initGit, getModifiedFileList, getRemoteUrl, SyncParameterMissingError } from 'git-sync-js';
import type { ICommitAndSyncConfigs, IGitLogMessage, IGitUserInfos } from './interface';
import { defaultGitInfo } from './defaultGitInfo';
import { WikiChannel } from '@/constants/channels';
import type { IWorkspace } from '@services/workspaces/interface';
import { clone, commitAndSync, getModifiedFileList, getRemoteUrl, GitStep, ILoggerContext, initGit, SyncParameterMissingError } from 'git-sync-js';
import { Observable } from 'rxjs';
import { expose } from 'threads/worker';
import { defaultGitInfo } from './defaultGitInfo';
import type { ICommitAndSyncConfigs, IGitLogMessage, IGitUserInfos } from './interface';
function initWikiGit(wikiFolderPath: string, syncImmediately?: boolean, remoteUrl?: string, userInfo?: IGitUserInfos): Observable<IGitLogMessage> {
return new Observable<IGitLogMessage>((observer) => {
@ -25,10 +25,12 @@ function initWikiGit(wikiFolderPath: string, syncImmediately?: boolean, remoteUr
userInfo,
defaultGitInfo,
logger: {
debug: (message: string, context: ILoggerContext): unknown =>
observer.next({ message, level: 'debug', meta: { callerFunction: 'initWikiGit', ...context } }),
warn: (message: string, context: ILoggerContext): unknown =>
observer.next({ message, level: 'warn', meta: { callerFunction: 'initWikiGit', ...context } }),
debug: (message: string, context: ILoggerContext): void => {
observer.next({ message, level: 'debug', meta: { callerFunction: 'initWikiGit', ...context } });
},
warn: (message: string, context: ILoggerContext): void => {
observer.next({ message, level: 'warn', meta: { callerFunction: 'initWikiGit', ...context } });
},
info: (message: GitStep, context: ILoggerContext): void => {
observer.next({ message, level: 'info', meta: { handler: WikiChannel.createProgress, callerFunction: 'initWikiGit', ...context } });
},
@ -41,10 +43,12 @@ function initWikiGit(wikiFolderPath: string, syncImmediately?: boolean, remoteUr
userInfo,
defaultGitInfo,
logger: {
debug: (message: string, context: ILoggerContext): unknown =>
observer.next({ message, level: 'debug', meta: { callerFunction: 'initWikiGit', ...context } }),
warn: (message: string, context: ILoggerContext): unknown =>
observer.next({ message, level: 'warn', meta: { callerFunction: 'initWikiGit', ...context } }),
debug: (message: string, context: ILoggerContext): void => {
observer.next({ message, level: 'debug', meta: { callerFunction: 'initWikiGit', ...context } });
},
warn: (message: string, context: ILoggerContext): void => {
observer.next({ message, level: 'warn', meta: { callerFunction: 'initWikiGit', ...context } });
},
info: (message: GitStep, context: ILoggerContext): void => {
observer.next({ message, level: 'info', meta: { handler: WikiChannel.createProgress, callerFunction: 'initWikiGit', ...context } });
},
@ -52,14 +56,17 @@ function initWikiGit(wikiFolderPath: string, syncImmediately?: boolean, remoteUr
});
}
void task.then(
() => observer.complete(),
(error) => observer.error(error),
() => {
observer.complete();
},
(error) => {
observer.error(error);
},
);
});
}
/**
*
* @param {string} wikiFolderPath
* @param {string} remoteUrl
* @param {{ login: string, email: string, accessToken: string }} userInfo
@ -71,18 +78,24 @@ function commitAndSyncWiki(workspace: IWorkspace, configs: ICommitAndSyncConfigs
...configs,
defaultGitInfo,
logger: {
debug: (message: string, context: ILoggerContext): unknown =>
observer.next({ message, level: 'debug', meta: { callerFunction: 'commitAndSync', ...context } }),
warn: (message: string, context: ILoggerContext): unknown =>
observer.next({ message, level: 'warn', meta: { callerFunction: 'commitAndSync', ...context } }),
debug: (message: string, context: ILoggerContext): void => {
observer.next({ message, level: 'debug', meta: { callerFunction: 'commitAndSync', ...context } });
},
warn: (message: string, context: ILoggerContext): void => {
observer.next({ message, level: 'warn', meta: { callerFunction: 'commitAndSync', ...context } });
},
info: (message: GitStep, context: ILoggerContext): void => {
observer.next({ message, level: 'info', meta: { handler: WikiChannel.syncProgress, id: workspace.id, callerFunction: 'commitAndSync', ...context } });
},
},
filesToIgnore: ['.DS_Store'],
}).then(
() => observer.complete(),
(error) => observer.error(error),
() => {
observer.complete();
},
(error) => {
observer.error(error);
},
);
});
}
@ -95,15 +108,23 @@ function cloneWiki(repoFolderPath: string, remoteUrl: string, userInfo: IGitUser
userInfo,
defaultGitInfo,
logger: {
debug: (message: string, context: ILoggerContext): unknown => observer.next({ message, level: 'debug', meta: { callerFunction: 'clone', ...context } }),
warn: (message: string, context: ILoggerContext): unknown => observer.next({ message, level: 'warn', meta: { callerFunction: 'clone', ...context } }),
debug: (message: string, context: ILoggerContext): void => {
observer.next({ message, level: 'debug', meta: { callerFunction: 'clone', ...context } });
},
warn: (message: string, context: ILoggerContext): void => {
observer.next({ message, level: 'warn', meta: { callerFunction: 'clone', ...context } });
},
info: (message: GitStep, context: ILoggerContext): void => {
observer.next({ message, level: 'info', meta: { handler: WikiChannel.createProgress, callerFunction: 'clone', ...context } });
},
},
}).then(
() => observer.complete(),
(error) => observer.error(error),
() => {
observer.complete();
},
(error) => {
observer.error(error);
},
);
});
}

View file

@ -1,51 +1,60 @@
import { ipcMain, dialog, net, shell } from 'electron';
import { injectable, inject } from 'inversify';
import { compact } from 'lodash';
import { dialog, ipcMain, net, shell } from 'electron';
import {
AssumeSyncError,
CantSyncGitNotInitializedError,
CantSyncInSpecialGitStateAutoFixFailed,
getRemoteName,
getRemoteUrl,
GitPullPushError,
GitStep,
ModifiedFileList,
SyncParameterMissingError,
SyncScriptIsInDeadLoopError,
getRemoteName,
getRemoteUrl,
} from 'git-sync-js';
import { spawn, Worker, ModuleThread } from 'threads';
import { inject, injectable } from 'inversify';
import { compact } from 'lodash';
import { ModuleThread, spawn, Worker } from 'threads';
import { WikiChannel } from '@/constants/channels';
import type { IAuthenticationService, ServiceBranchTypes } from '@services/auth/interface';
import { i18n } from '@services/libs/i18n';
import { logger } from '@services/libs/log';
import type { INativeService } from '@services/native/interface';
import type { IPreferenceService } from '@services/preferences/interface';
import serviceIdentifier from '@services/serviceIdentifier';
import type { IViewService } from '@services/view/interface';
import type { IPreferenceService } from '@services/preferences/interface';
import type { IWindowService } from '@services/windows/interface';
import type { INativeService } from '@services/native/interface';
import type { IAuthenticationService, ServiceBranchTypes } from '@services/auth/interface';
import type { IWikiService } from '@services/wiki/interface';
import { logger } from '@services/libs/log';
import { i18n } from '@services/libs/i18n';
import { ICommitAndSyncConfigs, IGitLogMessage, IGitService, IGitUserInfos } from './interface';
import { WikiChannel } from '@/constants/channels';
import { GitWorker } from './gitWorker';
import type { IWindowService } from '@services/windows/interface';
import { Observer } from 'rxjs';
import { GitWorker } from './gitWorker';
import { ICommitAndSyncConfigs, IGitLogMessage, IGitService, IGitUserInfos } from './interface';
import { LOCAL_GIT_DIRECTORY } from '@/constants/appPaths';
import { githubDesktopUrl } from '@/constants/urls';
import { lazyInject } from '@services/container';
import { WindowNames } from '@services/windows/WindowProperties';
import { IWorkspace } from '@services/workspaces/interface';
// @ts-expect-error it don't want .ts
// eslint-disable-next-line import/no-webpack-loader-syntax
import workerURL from 'threads-plugin/dist/loader?name=gitWorker!./gitWorker.ts';
import { LOCAL_GIT_DIRECTORY } from '@/constants/appPaths';
import { WindowNames } from '@services/windows/WindowProperties';
import { lazyInject } from '@services/container';
import { githubDesktopUrl } from '@/constants/urls';
import { IWorkspace } from '@services/workspaces/interface';
import { stepWithChanges } from './stepWithChanges';
@injectable()
export class Git implements IGitService {
@lazyInject(serviceIdentifier.Authentication) private readonly authService!: IAuthenticationService;
@lazyInject(serviceIdentifier.Wiki) private readonly wikiService!: IWikiService;
@lazyInject(serviceIdentifier.Window) private readonly windowService!: IWindowService;
@lazyInject(serviceIdentifier.View) private readonly viewService!: IViewService;
@lazyInject(serviceIdentifier.NativeService) private readonly nativeService!: INativeService;
@lazyInject(serviceIdentifier.Authentication)
private readonly authService!: IAuthenticationService;
@lazyInject(serviceIdentifier.Wiki)
private readonly wikiService!: IWikiService;
@lazyInject(serviceIdentifier.Window)
private readonly windowService!: IWindowService;
@lazyInject(serviceIdentifier.View)
private readonly viewService!: IViewService;
@lazyInject(serviceIdentifier.NativeService)
private readonly nativeService!: INativeService;
private gitWorker?: ModuleThread<GitWorker>;
@ -73,7 +82,6 @@ export class Git implements IGitService {
}
/**
*
* @param {string} githubRepoName similar to "linonetwo/wiki", string after "https://com/"
*/
public async updateGitInfoTiddler(workspace: IWorkspace, githubRepoName: string): Promise<void> {
@ -91,7 +99,9 @@ export class Git implements IGitService {
browserView.webContents.send(WikiChannel.addTiddler, '$:/GitHub/Repo', githubRepoName, {
type: 'text/vnd.tiddlywiki',
});
ipcMain.once(WikiChannel.addTiddlerDone, () => resolve());
ipcMain.once(WikiChannel.addTiddlerDone, () => {
resolve();
});
});
}
}
@ -236,7 +246,9 @@ export class Git implements IGitService {
this.translateAndLogErrorMessage(error as Error);
reject(error as Error);
},
complete: () => resolve(),
complete: () => {
resolve();
},
});
private createFailedDialog(message: string, wikiFolderPath: string): void {
@ -269,7 +281,7 @@ export class Git implements IGitService {
public async initWikiGit(wikiFolderPath: string, isSyncedWiki?: boolean, isMainWiki?: boolean, remoteUrl?: string, userInfo?: IGitUserInfos): Promise<void> {
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
const syncImmediately = !!isSyncedWiki && !!isMainWiki;
return await new Promise<void>((resolve, reject) => {
await new Promise<void>((resolve, reject) => {
this.gitWorker
?.initWikiGit(wikiFolderPath, syncImmediately && net.isOnline(), remoteUrl, userInfo)
.subscribe(this.getWorkerMessageObserver(resolve, reject));
@ -292,7 +304,9 @@ export class Git implements IGitService {
hasChanges = true;
}
},
complete: () => resolve(hasChanges),
complete: () => {
resolve(hasChanges);
},
});
return true;
});
@ -306,7 +320,7 @@ export class Git implements IGitService {
if (!net.isOnline()) {
return;
}
return await new Promise<void>((resolve, reject) => {
await new Promise<void>((resolve, reject) => {
this.gitWorker?.cloneWiki(repoFolderPath, remoteUrl, userInfo).subscribe(this.getWorkerMessageObserver(resolve, reject));
});
}

View file

@ -1,7 +1,7 @@
import { ProxyPropertyType } from 'electron-ipc-cat/common';
import { GitChannel } from '@/constants/channels';
import { ModifiedFileList } from 'git-sync-js';
import type { IWorkspace } from '@services/workspaces/interface';
import { ProxyPropertyType } from 'electron-ipc-cat/common';
import { ModifiedFileList } from 'git-sync-js';
export interface IGitUserInfos extends IGitUserInfosWithoutToken {
/** Github Login: token */

View file

@ -3,8 +3,8 @@
*/
import { registerProxy } from 'electron-ipc-cat/server';
import serviceIdentifier from '@services/serviceIdentifier';
import { container } from '@services/container';
import serviceIdentifier from '@services/serviceIdentifier';
import { Authentication } from '@services/auth';
import { ContextService } from '@services/context';
@ -45,10 +45,10 @@ import type { IUpdaterService } from '@services/updater/interface';
import { UpdaterServiceIPCDescriptor } from '@services/updater/interface';
import type { IViewService } from '@services/view/interface';
import { ViewServiceIPCDescriptor } from '@services/view/interface';
import type { IWikiGitWorkspaceService } from '@services/wikiGitWorkspace/interface';
import { WikiGitWorkspaceServiceIPCDescriptor } from '@services/wikiGitWorkspace/interface';
import type { IWikiService } from '@services/wiki/interface';
import { WikiServiceIPCDescriptor } from '@services/wiki/interface';
import type { IWikiGitWorkspaceService } from '@services/wikiGitWorkspace/interface';
import { WikiGitWorkspaceServiceIPCDescriptor } from '@services/wikiGitWorkspace/interface';
import type { IWindowService } from '@services/windows/interface';
import { WindowServiceIPCDescriptor } from '@services/windows/interface';
import type { IWorkspaceService } from '@services/workspaces/interface';

View file

@ -1,4 +1,4 @@
import { format, isTomorrow, isToday } from 'date-fns';
import { format, isToday, isTomorrow } from 'date-fns';
export const formatDate = (date: Date): string => {
if (isToday(date)) {

View file

@ -1,5 +1,5 @@
import { ipcMain, BrowserView, BrowserWindow } from 'electron';
import { Channels } from '@/constants/channels';
import { BrowserView, BrowserWindow, ipcMain } from 'electron';
/**
* Get data from a BrowserView
@ -11,6 +11,8 @@ export default async function getFromRenderer<T>(channel: Channels, viewToGetDat
const ipcToken = String(Math.random());
viewToGetData.webContents.send(channel, { ipcToken });
return await new Promise((resolve) => {
ipcMain.once(`${channel}-${ipcToken}`, (_event, data: T) => resolve(data));
ipcMain.once(`${channel}-${ipcToken}`, (_event, data: T) => {
resolve(data);
});
});
}

View file

@ -21,15 +21,15 @@ export default async function getViewBounds(
return {
x,
y: y + FIND_IN_PAGE_HEIGHT,
height: height !== undefined ? height : contentSize[1] - FIND_IN_PAGE_HEIGHT,
width: width !== undefined ? width : contentSize[0] - x,
height: height === undefined ? contentSize[1] - FIND_IN_PAGE_HEIGHT : height,
width: width === undefined ? contentSize[0] - x : width,
};
}
return {
x,
y,
height: height !== undefined ? height : contentSize[1],
width: width !== undefined ? width : contentSize[0] - x,
height: height === undefined ? contentSize[1] : height,
width: width === undefined ? contentSize[0] - x : width,
};
}

View file

@ -1,17 +1,17 @@
import path from 'path';
import { isElectronDevelopment } from '@/constants/isElectronDevelopment';
import i18next, { TFuncKey, TOptions } from 'i18next';
import Backend from 'i18next-fs-backend';
import { isElectronDevelopment } from '@/constants/isElectronDevelopment';
import path from 'path';
import { LOCALIZATION_FOLDER } from '@/constants/paths';
import { clearMainBindings, mainBindings } from './i18nMainBindings';
import changeToDefaultLanguage from './useDefaultLanguage';
import { mainBindings, clearMainBindings } from './i18nMainBindings';
// Workaround for https://github.com/isaachinman/next-i18next/issues/1781
declare module 'i18next' {
interface TFunction {
// eslint-disable-next-line @typescript-eslint/prefer-function-type
<TKeys extends TFuncKey = string, TInterpolationMap extends object = { [key: string]: any }>(
<TKeys extends TFuncKey = string, TInterpolationMap extends object = Record<string, any>>(
key: TKeys,
options?: TOptions<TInterpolationMap> | string,
): string;

View file

@ -1,12 +1,12 @@
/* eslint-disable unicorn/prevent-abbreviations */
import { IpcRenderer, IpcRendererEvent } from 'electron';
import { I18NChannels } from '@/constants/channels';
import { IpcRenderer, IpcRendererEvent } from 'electron';
import { IReadWriteFileRequest } from './types';
/** This is the code that will go into the preload.js file
* in order to set up the contextBridge api
*/
export const preloadBindings = function (ipcRenderer: IpcRenderer): {
export const preloadBindings = function(ipcRenderer: IpcRenderer): {
onLanguageChange: (callback: (language: { lng: string }) => unknown) => void;
onReceive: (channel: I18NChannels, callback: (readWriteFileArgs: IReadWriteFileRequest) => void) => void;
send: (channel: I18NChannels, readWriteFileArgs: IReadWriteFileRequest) => Promise<void>;
@ -22,7 +22,9 @@ export const preloadBindings = function (ipcRenderer: IpcRenderer): {
const validChannels = [I18NChannels.readFileResponse, I18NChannels.writeFileResponse];
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes "sender"
ipcRenderer.on(channel, (_event: IpcRendererEvent, arguments_: IReadWriteFileRequest) => callback(arguments_));
ipcRenderer.on(channel, (_event: IpcRendererEvent, arguments_: IReadWriteFileRequest) => {
callback(arguments_);
});
}
},
onLanguageChange: (callback: (language: { lng: string }) => unknown) => {

View file

@ -1,11 +1,11 @@
import type { IWindowService } from '@services/windows/interface';
import type { IViewService } from '@services/view/interface';
import type { IMenuService } from '@services/menu/interface';
import type { IWikiService } from '@services/wiki/interface';
import { I18NChannels } from '@/constants/channels';
import { supportedLanguagesMap, tiddlywikiLanguagesMap } from '@/constants/languages';
import { container } from '@services/container';
import type { IMenuService } from '@services/menu/interface';
import serviceIdentifier from '@services/serviceIdentifier';
import { tiddlywikiLanguagesMap, supportedLanguagesMap } from '@/constants/languages';
import type { IViewService } from '@services/view/interface';
import type { IWikiService } from '@services/wiki/interface';
import type { IWindowService } from '@services/windows/interface';
import { logger } from '../log';
import { i18n } from '.';
@ -31,7 +31,14 @@ export async function requestChangeLanguage(newLanguage: string): Promise<void>
// change tiddlywiki language
new Promise<unknown>((resolve, reject) => {
const tiddlywikiLanguageName = tiddlywikiLanguagesMap[newLanguage];
if (tiddlywikiLanguageName !== undefined) {
if (tiddlywikiLanguageName === undefined) {
const errorMessage = `When click language menu "${newLanguage}", there is no corresponding tiddlywiki language registered`;
logger.error(errorMessage, {
supportedLanguagesMap,
tiddlywikiLanguagesMap,
});
reject(new Error(errorMessage));
} else {
if (viewCount === 0) {
return;
}
@ -40,13 +47,6 @@ export async function requestChangeLanguage(newLanguage: string): Promise<void>
tasks.push(wikiService.setWikiLanguage(view, workspaceID, tiddlywikiLanguageName));
});
void Promise.all(tasks).then(resolve, reject);
} else {
const errorMessage = `When click language menu "${newLanguage}", there is no corresponding tiddlywiki language registered`;
logger.error(errorMessage, {
supportedLanguagesMap,
tiddlywikiLanguagesMap,
});
reject(new Error(errorMessage));
}
}),
// update menu

View file

@ -1,5 +1,5 @@
import winston, { format } from 'winston';
import { LOG_FOLDER } from '@/constants/appPaths';
import winston, { format } from 'winston';
import RendererTransport from './rendererTransport';
import 'winston-daily-rotate-file';
@ -20,39 +20,39 @@ export type ILogLevels = keyof typeof levels;
const logger = (
process.env.NODE_ENV === 'test'
? Object.assign(console, {
emerg: console.error.bind(console),
alert: console.error.bind(console),
crit: console.error.bind(console),
warning: console.warn.bind(console),
notice: console.log.bind(console),
debug: console.log.bind(console),
})
emerg: console.error.bind(console),
alert: console.error.bind(console),
crit: console.error.bind(console),
warning: console.warn.bind(console),
notice: console.log.bind(console),
debug: console.log.bind(console),
})
: winston.createLogger({
levels,
transports: [
new winston.transports.Console(),
new winston.transports.DailyRotateFile({
filename: 'TidGi-%DATE%.log',
datePattern: 'YYYY-MM-DD',
zippedArchive: false,
maxSize: '20mb',
maxFiles: '14d',
dirname: LOG_FOLDER,
level: 'debug',
}),
new RendererTransport(),
],
exceptionHandlers: [
new winston.transports.DailyRotateFile({
filename: 'TidGi-Exception-%DATE%.log',
datePattern: 'YYYY-MM-DD',
zippedArchive: false,
maxSize: '20mb',
maxFiles: '14d',
dirname: LOG_FOLDER,
}),
],
format: format.combine(format.timestamp(), format.json()),
})
levels,
transports: [
new winston.transports.Console(),
new winston.transports.DailyRotateFile({
filename: 'TidGi-%DATE%.log',
datePattern: 'YYYY-MM-DD',
zippedArchive: false,
maxSize: '20mb',
maxFiles: '14d',
dirname: LOG_FOLDER,
level: 'debug',
}),
new RendererTransport(),
],
exceptionHandlers: [
new winston.transports.DailyRotateFile({
filename: 'TidGi-Exception-%DATE%.log',
datePattern: 'YYYY-MM-DD',
zippedArchive: false,
maxSize: '20mb',
maxFiles: '14d',
dirname: LOG_FOLDER,
}),
],
format: format.combine(format.timestamp(), format.json()),
})
) as winston.Logger;
export { logger };

Some files were not shown because too many files have changed in this diff Show more