Change code to double-quotes.

master
Tom Hacohen 4 years ago
parent 1327279816
commit 9ec5d2a708

@ -65,7 +65,7 @@ module.exports = {
"react/no-unknown-property": ["error"], "react/no-unknown-property": ["error"],
"quotes": "off", "quotes": "off",
"@typescript-eslint/quotes": ["error", "single", { "allowTemplateLiterals": true, "avoidEscape": true }], "@typescript-eslint/quotes": ["error", "double", { "allowTemplateLiterals": true, "avoidEscape": true }],
"semi": "off", "semi": "off",
"@typescript-eslint/semi": ["error", "always", { "omitLastInOneLineBlock": true }], "@typescript-eslint/semi": ["error", "always", { "omitLastInOneLineBlock": true }],
"comma-dangle": ["error", { "comma-dangle": ["error", {

@ -1,15 +1,15 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import * as ReactDOM from 'react-dom'; import * as ReactDOM from "react-dom";
import { Provider } from 'react-redux'; import { Provider } from "react-redux";
import App from './App'; import App from "./App";
import { store } from './store'; import { store } from "./store";
it('renders without crashing', () => { it("renders without crashing", () => {
const div = document.createElement('div'); const div = document.createElement("div");
ReactDOM.render( ReactDOM.render(
<Provider store={store}> <Provider store={store}>
<App /> <App />

@ -1,93 +1,93 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import { List as ImmutableList } from 'immutable'; import { List as ImmutableList } from "immutable";
import { connect } from 'react-redux'; import { connect } from "react-redux";
import { withRouter } from 'react-router'; import { withRouter } from "react-router";
import { BrowserRouter } from 'react-router-dom'; import { BrowserRouter } from "react-router-dom";
import { MuiThemeProvider as ThemeProvider, createMuiTheme } from '@material-ui/core/styles'; // v1.x import { MuiThemeProvider as ThemeProvider, createMuiTheme } from "@material-ui/core/styles"; // v1.x
import amber from '@material-ui/core/colors/amber'; import amber from "@material-ui/core/colors/amber";
import lightBlue from '@material-ui/core/colors/lightBlue'; import lightBlue from "@material-ui/core/colors/lightBlue";
import AppBar from '@material-ui/core/AppBar'; import AppBar from "@material-ui/core/AppBar";
import Toolbar from '@material-ui/core/Toolbar'; import Toolbar from "@material-ui/core/Toolbar";
import Drawer from '@material-ui/core/Drawer'; import Drawer from "@material-ui/core/Drawer";
import IconButton from '@material-ui/core/IconButton'; import IconButton from "@material-ui/core/IconButton";
import Badge from '@material-ui/core/Badge'; import Badge from "@material-ui/core/Badge";
import NavigationMenu from '@material-ui/icons/Menu'; import NavigationMenu from "@material-ui/icons/Menu";
import NavigationBack from '@material-ui/icons/ArrowBack'; import NavigationBack from "@material-ui/icons/ArrowBack";
import NavigationRefresh from '@material-ui/icons/Refresh'; import NavigationRefresh from "@material-ui/icons/Refresh";
import ErrorsIcon from '@material-ui/icons/Error'; import ErrorsIcon from "@material-ui/icons/Error";
import 'react-virtualized/styles.css'; // only needs to be imported once import "react-virtualized/styles.css"; // only needs to be imported once
import './App.css'; import "./App.css";
import ConfirmationDialog from './widgets/ConfirmationDialog'; import ConfirmationDialog from "./widgets/ConfirmationDialog";
import PrettyError from './widgets/PrettyError'; import PrettyError from "./widgets/PrettyError";
import { List, ListItem } from './widgets/List'; import { List, ListItem } from "./widgets/List";
import withSpin from './widgets/withSpin'; import withSpin from "./widgets/withSpin";
import ErrorBoundary from './components/ErrorBoundary'; import ErrorBoundary from "./components/ErrorBoundary";
import SideMenu from './SideMenu'; import SideMenu from "./SideMenu";
import LoginGate from './LoginGate'; import LoginGate from "./LoginGate";
import { RouteResolver } from './routes'; import { RouteResolver } from "./routes";
import * as store from './store'; import * as store from "./store";
import * as actions from './store/actions'; import * as actions from "./store/actions";
import { credentialsSelector } from './login'; import { credentialsSelector } from "./login";
import { History } from 'history'; import { History } from "history";
export const routeResolver = new RouteResolver({ export const routeResolver = new RouteResolver({
home: '', home: "",
pim: { pim: {
contacts: { contacts: {
_id: { _id: {
_base: ':itemUid', _base: ":itemUid",
edit: 'edit', edit: "edit",
log: 'log', log: "log",
}, },
new: 'new', new: "new",
}, },
events: { events: {
_id: { _id: {
_base: ':itemUid', _base: ":itemUid",
edit: 'edit', edit: "edit",
duplicate: 'duplicate', duplicate: "duplicate",
log: 'log', log: "log",
}, },
new: 'new', new: "new",
}, },
tasks: { tasks: {
_id: { _id: {
_base: ':itemUid', _base: ":itemUid",
edit: 'edit', edit: "edit",
log: 'log', log: "log",
}, },
new: 'new', new: "new",
}, },
}, },
journals: { journals: {
_id: { _id: {
_base: ':journalUid', _base: ":journalUid",
edit: 'edit', edit: "edit",
items: { items: {
_id: { _id: {
_base: ':itemUid', _base: ":itemUid",
}, },
}, },
entries: { entries: {
_id: { _id: {
_base: ':entryUid', _base: ":entryUid",
}, },
}, },
members: { members: {
}, },
}, },
new: 'new', new: "new",
import: 'import', import: "import",
}, },
settings: { settings: {
}, },
@ -132,7 +132,7 @@ const AppBarWitHistory = withRouter(
} }
</div> </div>
<div style={{ flexGrow: 1, fontSize: '1.25em' }} id="appbar-title" /> <div style={{ flexGrow: 1, fontSize: "1.25em" }} id="appbar-title" />
<div style={{ marginRight: -12 }} id="appbar-buttons"> <div style={{ marginRight: -12 }} id="appbar-buttons">
{iconElementRight} {iconElementRight}
@ -145,8 +145,8 @@ const AppBarWitHistory = withRouter(
private canGoBack() { private canGoBack() {
return ( return (
(this.props.history!.length > 1) && (this.props.history!.length > 1) &&
(this.props.history!.location.pathname !== routeResolver.getRoute('pim')) && (this.props.history!.location.pathname !== routeResolver.getRoute("pim")) &&
(this.props.history!.location.pathname !== routeResolver.getRoute('home')) (this.props.history!.location.pathname !== routeResolver.getRoute("home"))
); );
} }
@ -191,13 +191,13 @@ class App extends React.PureComponent {
const muiTheme = createMuiTheme({ const muiTheme = createMuiTheme({
palette: { palette: {
type: darkMode ? 'dark' : undefined, type: darkMode ? "dark" : undefined,
primary: amber, primary: amber,
secondary: { secondary: {
light: lightBlue.A200, light: lightBlue.A200,
main: lightBlue.A400, main: lightBlue.A400,
dark: lightBlue.A700, dark: lightBlue.A700,
contrastText: '#fff', contrastText: "#fff",
}, },
}, },
}); });
@ -213,7 +213,7 @@ class App extends React.PureComponent {
return ( return (
<ThemeProvider theme={muiTheme}> <ThemeProvider theme={muiTheme}>
<BrowserRouter> <BrowserRouter>
<div style={styles.main} className={darkMode ? 'theme-dark' : 'theme-light'}> <div style={styles.main} className={darkMode ? "theme-dark" : "theme-light"}>
<AppBarWitHistory <AppBarWitHistory
toggleDrawerIcon={<IconButton onClick={this.toggleDrawer}><NavigationMenu /></IconButton>} toggleDrawerIcon={<IconButton onClick={this.toggleDrawer}><NavigationMenu /></IconButton>}
iconElementRight={ iconElementRight={
@ -245,7 +245,7 @@ class App extends React.PureComponent {
{errors.map((error, index) => ( {errors.map((error, index) => (
<ListItem <ListItem
key={index} key={index}
style={{ height: 'unset' }} style={{ height: "unset" }}
onClick={() => (window as any).navigator.clipboard.writeText(`${error.message}\n\n${error.stack}`)} onClick={() => (window as any).navigator.clipboard.writeText(`${error.message}\n\n${error.stack}`)}
> >
<PrettyError error={error} /> <PrettyError error={error} />

@ -1,14 +1,14 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import * as EteSync from 'etesync'; import * as EteSync from "etesync";
import Button from '@material-ui/core/Button'; import Button from "@material-ui/core/Button";
import TextField from '@material-ui/core/TextField'; import TextField from "@material-ui/core/TextField";
import Container from './widgets/Container'; import Container from "./widgets/Container";
import { useSelector } from 'react-redux'; import { useSelector } from "react-redux";
import { StoreState, CredentialsData, UserInfoData, EntriesListData } from './store'; import { StoreState, CredentialsData, UserInfoData, EntriesListData } from "./store";
interface PropsType { interface PropsType {
etesync: CredentialsData; etesync: CredentialsData;
@ -16,9 +16,9 @@ interface PropsType {
} }
export default function Debug(props: PropsType) { export default function Debug(props: PropsType) {
const [stateJournalUid, setJournalUid] = React.useState(''); const [stateJournalUid, setJournalUid] = React.useState("");
const [entriesUids, setEntriesUids] = React.useState(''); const [entriesUids, setEntriesUids] = React.useState("");
const [result, setResult] = React.useState(''); const [result, setResult] = React.useState("");
const journals = useSelector((state: StoreState) => state.cache.journals!); const journals = useSelector((state: StoreState) => state.cache.journals!);
const journalEntries = useSelector((state: StoreState) => state.cache.entries); const journalEntries = useSelector((state: StoreState) => state.cache.entries);
@ -32,7 +32,7 @@ export default function Debug(props: PropsType) {
<Container> <Container>
<div> <div>
<TextField <TextField
style={{ width: '100%' }} style={{ width: "100%" }}
type="text" type="text"
label="Journal UID" label="Journal UID"
value={stateJournalUid} value={stateJournalUid}
@ -41,7 +41,7 @@ export default function Debug(props: PropsType) {
</div> </div>
<div> <div>
<TextField <TextField
style={{ width: '100%' }} style={{ width: "100%" }}
type="text" type="text"
multiline multiline
label="Entry UIDs" label="Entry UIDs"
@ -60,13 +60,13 @@ export default function Debug(props: PropsType) {
const journalUid = stateJournalUid.trim(); const journalUid = stateJournalUid.trim();
const journal = journals.get(journalUid); const journal = journals.get(journalUid);
if (!journal) { if (!journal) {
setResult('Error: journal uid not found.'); setResult("Error: journal uid not found.");
return; return;
} }
const wantedEntries = {}; const wantedEntries = {};
const wantAll = (entriesUids.trim() === 'all'); const wantAll = (entriesUids.trim() === "all");
entriesUids.split('\n').forEach((ent) => wantedEntries[ent.trim()] = true); entriesUids.split("\n").forEach((ent) => wantedEntries[ent.trim()] = true);
const cryptoManager = journal.getCryptoManager(derived, keyPair); const cryptoManager = journal.getCryptoManager(derived, keyPair);
let prevUid: string | null = null; let prevUid: string | null = null;
@ -79,7 +79,7 @@ export default function Debug(props: PropsType) {
return (wantAll || wantedEntries[entry.uid]) ? syncEntry : undefined; return (wantAll || wantedEntries[entry.uid]) ? syncEntry : undefined;
}).filter((x) => x !== undefined); }).filter((x) => x !== undefined);
setResult(syncEntries.map((ent) => ent?.content).join('\n\n')); setResult(syncEntries.map((ent) => ent?.content).join("\n\n"));
}} }}
> >
Decrypt Decrypt

@ -1,29 +1,29 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import Button from '@material-ui/core/Button'; import Button from "@material-ui/core/Button";
import Dialog from '@material-ui/core/Dialog'; import Dialog from "@material-ui/core/Dialog";
import DialogActions from '@material-ui/core/DialogActions'; import DialogActions from "@material-ui/core/DialogActions";
import DialogContent from '@material-ui/core/DialogContent'; import DialogContent from "@material-ui/core/DialogContent";
import DialogContentText from '@material-ui/core/DialogContentText'; import DialogContentText from "@material-ui/core/DialogContentText";
import DialogTitle from '@material-ui/core/DialogTitle'; import DialogTitle from "@material-ui/core/DialogTitle";
import Dropzone from 'react-dropzone'; import Dropzone from "react-dropzone";
import LoadingIndicator from '../widgets/LoadingIndicator'; import LoadingIndicator from "../widgets/LoadingIndicator";
import { SyncInfoJournal } from '../SyncGate'; import { SyncInfoJournal } from "../SyncGate";
import { store, CredentialsData, UserInfoData } from '../store'; import { store, CredentialsData, UserInfoData } from "../store";
import { addEntries } from '../store/actions'; import { addEntries } from "../store/actions";
import { createJournalEntry } from '../etesync-helpers'; import { createJournalEntry } from "../etesync-helpers";
import * as EteSync from 'etesync'; import * as EteSync from "etesync";
import * as uuid from 'uuid'; import * as uuid from "uuid";
import * as ICAL from 'ical.js'; import * as ICAL from "ical.js";
import { ContactType, EventType, TaskType, PimType } from '../pim-types'; import { ContactType, EventType, TaskType, PimType } from "../pim-types";
interface PropsType { interface PropsType {
etesync: CredentialsData; etesync: CredentialsData;
@ -55,14 +55,14 @@ class ImportDialog extends React.Component<PropsType> {
let acceptTypes; let acceptTypes;
let dropFunction; let dropFunction;
if (collectionInfo.type === 'ADDRESS_BOOK') { if (collectionInfo.type === "ADDRESS_BOOK") {
acceptTypes = ['text/vcard', 'text/directory', 'text/x-vcard', '.vcf']; acceptTypes = ["text/vcard", "text/directory", "text/x-vcard", ".vcf"];
dropFunction = this.onFileDropContact; dropFunction = this.onFileDropContact;
} else if (collectionInfo.type === 'CALENDAR') { } else if (collectionInfo.type === "CALENDAR") {
acceptTypes = ['text/calendar', '.ics', '.ical']; acceptTypes = ["text/calendar", ".ics", ".ical"];
dropFunction = this.onFileDropEvent; dropFunction = this.onFileDropEvent;
} else if (collectionInfo.type === 'TASKS') { } else if (collectionInfo.type === "TASKS") {
acceptTypes = ['text/calendar', '.ics', '.ical']; acceptTypes = ["text/calendar", ".ics", ".ical"];
dropFunction = this.onFileDropTask; dropFunction = this.onFileDropTask;
} }
@ -75,7 +75,7 @@ class ImportDialog extends React.Component<PropsType> {
<DialogTitle>Import entries from file?</DialogTitle> <DialogTitle>Import entries from file?</DialogTitle>
<DialogContent> <DialogContent>
{loading ? {loading ?
<LoadingIndicator style={{ display: 'block', margin: 'auto' }} /> <LoadingIndicator style={{ display: "block", margin: "auto" }} />
: :
<Dropzone <Dropzone
onDrop={dropFunction} onDrop={dropFunction}
@ -111,13 +111,13 @@ class ImportDialog extends React.Component<PropsType> {
reader.onabort = () => { reader.onabort = () => {
this.setState({ loading: false }); this.setState({ loading: false });
console.error('Import Aborted'); console.error("Import Aborted");
alert('file reading was aborted'); alert("file reading was aborted");
}; };
reader.onerror = (e) => { reader.onerror = (e) => {
this.setState({ loading: false }); this.setState({ loading: false });
console.error(e); console.error(e);
alert('file reading has failed'); alert("file reading has failed");
}; };
reader.onload = async () => { reader.onload = async () => {
try { try {
@ -144,7 +144,7 @@ class ImportDialog extends React.Component<PropsType> {
); );
} catch (e) { } catch (e) {
console.error(e); console.error(e);
alert('An error has occurred, please contact developers.'); alert("An error has occurred, please contact developers.");
throw e; throw e;
} finally { } finally {
if (this.props.onClose) { if (this.props.onClose) {
@ -160,8 +160,8 @@ class ImportDialog extends React.Component<PropsType> {
reader.readAsText(file); reader.readAsText(file);
}); });
} else { } else {
alert('Failed importing file. Is the file type supported?'); alert("Failed importing file. Is the file type supported?");
console.log('Failed importing files. Rejected:', rejectedFiles); console.log("Failed importing files. Rejected:", rejectedFiles);
} }
} }
@ -183,7 +183,7 @@ class ImportDialog extends React.Component<PropsType> {
private onFileDropEvent(acceptedFiles: File[], rejectedFiles: File[]) { private onFileDropEvent(acceptedFiles: File[], rejectedFiles: File[]) {
const itemsCreator = (fileText: string) => { const itemsCreator = (fileText: string) => {
const calendarComp = new ICAL.Component(ICAL.parse(fileText)); const calendarComp = new ICAL.Component(ICAL.parse(fileText));
return calendarComp.getAllSubcomponents('vevent').map((comp) => { return calendarComp.getAllSubcomponents("vevent").map((comp) => {
const ret = new EventType(comp); const ret = new EventType(comp);
if (!ret.uid) { if (!ret.uid) {
ret.uid = uuid.v4(); ret.uid = uuid.v4();
@ -198,7 +198,7 @@ class ImportDialog extends React.Component<PropsType> {
private onFileDropTask(acceptedFiles: File[], rejectedFiles: File[]) { private onFileDropTask(acceptedFiles: File[], rejectedFiles: File[]) {
const itemsCreator = (fileText: string) => { const itemsCreator = (fileText: string) => {
const calendarComp = new ICAL.Component(ICAL.parse(fileText)); const calendarComp = new ICAL.Component(ICAL.parse(fileText));
return calendarComp.getAllSubcomponents('vtodo').map((comp) => { return calendarComp.getAllSubcomponents("vtodo").map((comp) => {
const ret = new TaskType(comp); const ret = new TaskType(comp);
if (!ret.uid) { if (!ret.uid) {
ret.uid = uuid.v4(); ret.uid = uuid.v4();

@ -1,26 +1,26 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import IconButton from '@material-ui/core/IconButton'; import IconButton from "@material-ui/core/IconButton";
import IconEdit from '@material-ui/icons/Edit'; import IconEdit from "@material-ui/icons/Edit";
import IconMembers from '@material-ui/icons/People'; import IconMembers from "@material-ui/icons/People";
import IconImport from '@material-ui/icons/ImportExport'; import IconImport from "@material-ui/icons/ImportExport";
import AppBarOverride from '../widgets/AppBarOverride'; import AppBarOverride from "../widgets/AppBarOverride";
import Container from '../widgets/Container'; import Container from "../widgets/Container";
import JournalEntries from '../components/JournalEntries'; import JournalEntries from "../components/JournalEntries";
import ImportDialog from './ImportDialog'; import ImportDialog from "./ImportDialog";
import { SyncInfo, SyncInfoJournal } from '../SyncGate'; import { SyncInfo, SyncInfoJournal } from "../SyncGate";
import { Link } from 'react-router-dom'; import { Link } from "react-router-dom";
import { routeResolver } from '../App'; import { routeResolver } from "../App";
import { CredentialsData, UserInfoData } from '../store'; import { CredentialsData, UserInfoData } from "../store";
interface PropsType { interface PropsType {
etesync: CredentialsData; etesync: CredentialsData;
@ -61,14 +61,14 @@ class Journal extends React.Component<PropsType> {
<IconButton <IconButton
component={Link} component={Link}
title="Edit" title="Edit"
{...{ to: routeResolver.getRoute('journals._id.edit', { journalUid: journal.uid }) }} {...{ to: routeResolver.getRoute("journals._id.edit", { journalUid: journal.uid }) }}
> >
<IconEdit /> <IconEdit />
</IconButton> </IconButton>
<IconButton <IconButton
component={Link} component={Link}
title="Members" title="Members"
{...{ to: routeResolver.getRoute('journals._id.members', { journalUid: journal.uid }) }} {...{ to: routeResolver.getRoute("journals._id.members", { journalUid: journal.uid }) }}
> >
<IconMembers /> <IconMembers />
</IconButton> </IconButton>

@ -1,26 +1,26 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import Button from '@material-ui/core/Button'; import Button from "@material-ui/core/Button";
import TextField from '@material-ui/core/TextField'; import TextField from "@material-ui/core/TextField";
import Select from '@material-ui/core/Select'; import Select from "@material-ui/core/Select";
import MenuItem from '@material-ui/core/MenuItem'; import MenuItem from "@material-ui/core/MenuItem";
import FormControl from '@material-ui/core/FormControl'; import FormControl from "@material-ui/core/FormControl";
import InputLabel from '@material-ui/core/InputLabel'; import InputLabel from "@material-ui/core/InputLabel";
import IconDelete from '@material-ui/icons/Delete'; import IconDelete from "@material-ui/icons/Delete";
import IconCancel from '@material-ui/icons/Clear'; import IconCancel from "@material-ui/icons/Clear";
import IconSave from '@material-ui/icons/Save'; import IconSave from "@material-ui/icons/Save";
import * as colors from '@material-ui/core/colors'; import * as colors from "@material-ui/core/colors";
import AppBarOverride from '../widgets/AppBarOverride'; import AppBarOverride from "../widgets/AppBarOverride";
import Container from '../widgets/Container'; import Container from "../widgets/Container";
import ConfirmationDialog from '../widgets/ConfirmationDialog'; import ConfirmationDialog from "../widgets/ConfirmationDialog";
import * as EteSync from 'etesync'; import * as EteSync from "etesync";
import { SyncInfo } from '../SyncGate'; import { SyncInfo } from "../SyncGate";
import ColorPicker from '../widgets/ColorPicker'; import ColorPicker from "../widgets/ColorPicker";
import { defaultColor, colorHtmlToInt, colorIntToHtml } from '../journal-processors'; import { defaultColor, colorHtmlToInt, colorIntToHtml } from "../journal-processors";
interface PropsType { interface PropsType {
syncInfo: SyncInfo; syncInfo: SyncInfo;
@ -39,7 +39,7 @@ export default function JournalEdit(props: PropsType) {
const [errors, setErrors] = React.useState<FormErrors>({}); const [errors, setErrors] = React.useState<FormErrors>({});
const [showDeleteDialog, setShowDeleteDialog] = React.useState(false); const [showDeleteDialog, setShowDeleteDialog] = React.useState(false);
const [info, setInfo] = React.useState<EteSync.CollectionInfo>(); const [info, setInfo] = React.useState<EteSync.CollectionInfo>();
const [selectedColor, setSelectedColor] = React.useState(''); const [selectedColor, setSelectedColor] = React.useState("");
React.useEffect(() => { React.useEffect(() => {
if (props.item !== undefined) { if (props.item !== undefined) {
@ -50,9 +50,9 @@ export default function JournalEdit(props: PropsType) {
} else { } else {
setInfo({ setInfo({
uid: EteSync.genUid(), uid: EteSync.genUid(),
type: 'ADDRESS_BOOK', type: "ADDRESS_BOOK",
displayName: '', displayName: "",
description: '', description: "",
} as EteSync.CollectionInfo); } as EteSync.CollectionInfo);
} }
}, [props.item]); }, [props.item]);
@ -64,7 +64,7 @@ export default function JournalEdit(props: PropsType) {
function onSubmit(e: React.FormEvent<any>) { function onSubmit(e: React.FormEvent<any>) {
e.preventDefault(); e.preventDefault();
const saveErrors: FormErrors = {}; const saveErrors: FormErrors = {};
const fieldRequired = 'This field is required!'; const fieldRequired = "This field is required!";
const { onSave } = props; const { onSave } = props;
@ -76,7 +76,7 @@ export default function JournalEdit(props: PropsType) {
} }
if (selectedColor && !color) { if (selectedColor && !color) {
saveErrors.color = 'Must be of the form #RRGGBB or #RRGGBBAA or empty'; saveErrors.color = "Must be of the form #RRGGBB or #RRGGBBAA or empty";
} }
if (Object.keys(saveErrors).length > 0) { if (Object.keys(saveErrors).length > 0) {
@ -94,28 +94,28 @@ export default function JournalEdit(props: PropsType) {
const { item, onDelete, onCancel } = props; const { item, onDelete, onCancel } = props;
const pageTitle = (item !== undefined) ? item.displayName : 'New Journal'; const pageTitle = (item !== undefined) ? item.displayName : "New Journal";
const styles = { const styles = {
fullWidth: { fullWidth: {
width: '100%', width: "100%",
}, },
submit: { submit: {
marginTop: 40, marginTop: 40,
marginBottom: 20, marginBottom: 20,
textAlign: 'right' as any, textAlign: "right" as any,
}, },
}; };
const journalTypes = { const journalTypes = {
ADDRESS_BOOK: 'Address Book', ADDRESS_BOOK: "Address Book",
CALENDAR: 'Calendar', CALENDAR: "Calendar",
TASKS: 'Task List', TASKS: "Task List",
}; };
let collectionColorBox: React.ReactNode; let collectionColorBox: React.ReactNode;
switch (info.type) { switch (info.type) {
case 'CALENDAR': case "CALENDAR":
case 'TASKS': case "TASKS":
collectionColorBox = ( collectionColorBox = (
<ColorPicker <ColorPicker
defaultColor={defaultColor} defaultColor={defaultColor}
@ -130,7 +130,7 @@ export default function JournalEdit(props: PropsType) {
return ( return (
<> <>
<AppBarOverride title={pageTitle} /> <AppBarOverride title={pageTitle} />
<Container style={{ maxWidth: '30rem' }}> <Container style={{ maxWidth: "30rem" }}>
<form onSubmit={onSubmit}> <form onSubmit={onSubmit}>
<FormControl disabled={props.item !== undefined} style={styles.fullWidth}> <FormControl disabled={props.item !== undefined} style={styles.fullWidth}>
<InputLabel> <InputLabel>
@ -180,7 +180,7 @@ export default function JournalEdit(props: PropsType) {
{props.item && {props.item &&
<Button <Button
variant="contained" variant="contained"
style={{ marginLeft: 15, backgroundColor: colors.red[500], color: 'white' }} style={{ marginLeft: 15, backgroundColor: colors.red[500], color: "white" }}
onClick={onDeleteRequest} onClick={onDeleteRequest}
> >
<IconDelete style={{ marginRight: 8 }} /> <IconDelete style={{ marginRight: 8 }} />

@ -1,20 +1,20 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import TextField from '@material-ui/core/TextField'; import TextField from "@material-ui/core/TextField";
import Checkbox from '@material-ui/core/Checkbox'; import Checkbox from "@material-ui/core/Checkbox";
import FormControlLabel from '@material-ui/core/FormControlLabel'; import FormControlLabel from "@material-ui/core/FormControlLabel";
import LoadingIndicator from '../widgets/LoadingIndicator'; import LoadingIndicator from "../widgets/LoadingIndicator";
import ConfirmationDialog from '../widgets/ConfirmationDialog'; import ConfirmationDialog from "../widgets/ConfirmationDialog";
import PrettyFingerprint from '../widgets/PrettyFingerprint'; import PrettyFingerprint from "../widgets/PrettyFingerprint";
import * as EteSync from 'etesync'; import * as EteSync from "etesync";
import { CredentialsData } from '../store'; import { CredentialsData } from "../store";
import { handleInputChange } from '../helpers'; import { handleInputChange } from "../helpers";
interface PropsType { interface PropsType {
etesync: CredentialsData; etesync: CredentialsData;
@ -25,8 +25,8 @@ interface PropsType {
class JournalMemberAddDialog extends React.PureComponent<PropsType> { class JournalMemberAddDialog extends React.PureComponent<PropsType> {
public state = { public state = {
addUser: '', addUser: "",
publicKey: '', publicKey: "",
readOnly: false, readOnly: false,
userChosen: false, userChosen: false,
error: undefined as Error | undefined, error: undefined as Error | undefined,
@ -75,7 +75,7 @@ class JournalMemberAddDialog extends React.PureComponent<PropsType> {
<p> <p>
Verify {addUser}'s security fingerprint to ensure the encryption is secure. Verify {addUser}'s security fingerprint to ensure the encryption is secure.
</p> </p>
<div style={{ textAlign: 'center' }}> <div style={{ textAlign: "center" }}>
<PrettyFingerprint publicKey={publicKey} /> <PrettyFingerprint publicKey={publicKey} />
</div> </div>
</ConfirmationDialog> </ConfirmationDialog>
@ -99,7 +99,7 @@ class JournalMemberAddDialog extends React.PureComponent<PropsType> {
name="addUser" name="addUser"
type="email" type="email"
placeholder="User email" placeholder="User email"
style={{ width: '100%' }} style={{ width: "100%" }}
value={addUser} value={addUser}
onChange={this.handleInputChange} onChange={this.handleInputChange}
/> />

@ -1,26 +1,26 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import sjcl from 'sjcl'; import sjcl from "sjcl";
import { List, ListItem } from '../widgets/List'; import { List, ListItem } from "../widgets/List";
import { Theme, withTheme } from '@material-ui/core/styles'; import { Theme, withTheme } from "@material-ui/core/styles";
import IconMemberAdd from '@material-ui/icons/PersonAdd'; import IconMemberAdd from "@material-ui/icons/PersonAdd";
import VisibilityIcon from '@material-ui/icons/Visibility'; import VisibilityIcon from "@material-ui/icons/Visibility";
import AppBarOverride from '../widgets/AppBarOverride'; import AppBarOverride from "../widgets/AppBarOverride";
import Container from '../widgets/Container'; import Container from "../widgets/Container";
import LoadingIndicator from '../widgets/LoadingIndicator'; import LoadingIndicator from "../widgets/LoadingIndicator";
import ConfirmationDialog from '../widgets/ConfirmationDialog'; import ConfirmationDialog from "../widgets/ConfirmationDialog";
import JournalMemberAddDialog from './JournalMemberAddDialog'; import JournalMemberAddDialog from "./JournalMemberAddDialog";
import * as EteSync from 'etesync'; import * as EteSync from "etesync";
import { CredentialsData, UserInfoData } from '../store'; import { CredentialsData, UserInfoData } from "../store";
import { SyncInfoJournal } from '../SyncGate'; import { SyncInfoJournal } from "../SyncGate";
interface PropsType { interface PropsType {
etesync: CredentialsData; etesync: CredentialsData;
@ -57,7 +57,7 @@ class JournalMembers extends React.PureComponent<PropsTypeInner> {
return ( return (
<> <>
<AppBarOverride title={`${info.displayName} - Members`} /> <AppBarOverride title={`${info.displayName} - Members`} />
<Container style={{ maxWidth: '30rem' }}> <Container style={{ maxWidth: "30rem" }}>
{members ? {members ?
<List> <List>
<ListItem rightIcon={<IconMemberAdd />} onClick={() => this.setState({ addMemberOpen: true })}> <ListItem rightIcon={<IconMemberAdd />} onClick={() => this.setState({ addMemberOpen: true })}>
@ -150,7 +150,7 @@ class JournalMembers extends React.PureComponent<PropsTypeInner> {
const creds = etesync.credentials; const creds = etesync.credentials;
const apiBase = etesync.serviceApiUrl; const apiBase = etesync.serviceApiUrl;
const journalMembersManager = new EteSync.JournalMembersManager(creds, apiBase, info.uid); const journalMembersManager = new EteSync.JournalMembersManager(creds, apiBase, info.uid);
journalMembersManager.delete({ user: revokeUser!, key: '' }).then(() => { journalMembersManager.delete({ user: revokeUser!, key: "" }).then(() => {
this.fetchMembers(); this.fetchMembers();
}); });
this.setState({ this.setState({

@ -1,27 +1,27 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import { History } from 'history'; import { History } from "history";
import { Link } from 'react-router-dom'; import { Link } from "react-router-dom";
import IconButton from '@material-ui/core/IconButton'; import IconButton from "@material-ui/core/IconButton";
import IconAdd from '@material-ui/icons/Add'; import IconAdd from "@material-ui/icons/Add";
import ContactsIcon from '@material-ui/icons/Contacts'; import ContactsIcon from "@material-ui/icons/Contacts";
import CalendarTodayIcon from '@material-ui/icons/CalendarToday'; import CalendarTodayIcon from "@material-ui/icons/CalendarToday";
import FormatListBulletedIcon from '@material-ui/icons/FormatListBulleted'; import FormatListBulletedIcon from "@material-ui/icons/FormatListBulleted";
import { List, ListItem } from '../widgets/List'; import { List, ListItem } from "../widgets/List";
import AppBarOverride from '../widgets/AppBarOverride'; import AppBarOverride from "../widgets/AppBarOverride";
import Container from '../widgets/Container'; import Container from "../widgets/Container";
import { routeResolver } from '../App'; import { routeResolver } from "../App";
import { JournalsData, UserInfoData, CredentialsData } from '../store'; import { JournalsData, UserInfoData, CredentialsData } from "../store";
import ColorBox from '../widgets/ColorBox'; import ColorBox from "../widgets/ColorBox";
import { colorIntToHtml } from '../journal-processors'; import { colorIntToHtml } from "../journal-processors";
interface PropsType { interface PropsType {
etesync: CredentialsData; etesync: CredentialsData;
@ -34,7 +34,7 @@ export default function JournalsList(props: PropsType) {
const derived = props.etesync.encryptionKey; const derived = props.etesync.encryptionKey;
function journalClicked(journalUid: string) { function journalClicked(journalUid: string) {
props.history.push(routeResolver.getRoute('journals._id', { journalUid })); props.history.push(routeResolver.getRoute("journals._id", { journalUid }));
} }
const journalMap = props.journals.reduce( const journalMap = props.journals.reduce(
@ -45,8 +45,8 @@ export default function JournalsList(props: PropsType) {
const info = journal.getInfo(cryptoManager); const info = journal.getInfo(cryptoManager);
let colorBox: React.ReactElement | undefined; let colorBox: React.ReactElement | undefined;
switch (info.type) { switch (info.type) {
case 'CALENDAR': case "CALENDAR":
case 'TASKS': case "TASKS":
colorBox = ( colorBox = (
<ColorBox size={24} color={colorIntToHtml(info.color)} /> <ColorBox size={24} color={colorIntToHtml(info.color)} />
); );
@ -74,7 +74,7 @@ export default function JournalsList(props: PropsType) {
<IconButton <IconButton
component={Link} component={Link}
title="New" title="New"
{...{ to: routeResolver.getRoute('journals.new') }} {...{ to: routeResolver.getRoute("journals.new") }}
> >
<IconAdd /> <IconAdd />
</IconButton> </IconButton>

@ -1,22 +1,22 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import ContactsIcon from '@material-ui/icons/Contacts'; import ContactsIcon from "@material-ui/icons/Contacts";
import CalendarTodayIcon from '@material-ui/icons/CalendarToday'; import CalendarTodayIcon from "@material-ui/icons/CalendarToday";
import FormatListBulletedIcon from '@material-ui/icons/FormatListBulleted'; import FormatListBulletedIcon from "@material-ui/icons/FormatListBulleted";
import { List, ListItem } from '../widgets/List'; import { List, ListItem } from "../widgets/List";
import AppBarOverride from '../widgets/AppBarOverride'; import AppBarOverride from "../widgets/AppBarOverride";
import Container from '../widgets/Container'; import Container from "../widgets/Container";
import { JournalsData, UserInfoData, CredentialsData } from '../store'; import { JournalsData, UserInfoData, CredentialsData } from "../store";
import ImportDialog from './ImportDialog'; import ImportDialog from "./ImportDialog";
import { SyncInfo, SyncInfoJournal } from '../SyncGate'; import { SyncInfo, SyncInfoJournal } from "../SyncGate";
import { colorIntToHtml } from '../journal-processors'; import { colorIntToHtml } from "../journal-processors";
import ColorBox from '../widgets/ColorBox'; import ColorBox from "../widgets/ColorBox";
interface PropsType { interface PropsType {
etesync: CredentialsData; etesync: CredentialsData;
@ -37,8 +37,8 @@ export default function JournalsList(props: PropsType) {
const info = journal.getInfo(cryptoManager); const info = journal.getInfo(cryptoManager);
let colorBox: React.ReactElement | undefined; let colorBox: React.ReactElement | undefined;
switch (info.type) { switch (info.type) {
case 'CALENDAR': case "CALENDAR":
case 'TASKS': case "TASKS":
colorBox = ( colorBox = (
<ColorBox size={24} color={colorIntToHtml(info.color)} /> <ColorBox size={24} color={colorIntToHtml(info.color)} />
); );

@ -1,23 +1,23 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import { Location, History } from 'history'; import { Location, History } from "history";
import { Route, Switch } from 'react-router'; import { Route, Switch } from "react-router";
import Journal from './Journal'; import Journal from "./Journal";
import JournalEdit from './JournalEdit'; import JournalEdit from "./JournalEdit";
import JournalMembers from './JournalMembers'; import JournalMembers from "./JournalMembers";
import JournalsList from './JournalsList'; import JournalsList from "./JournalsList";
import JournalsListImport from './JournalsListImport'; import JournalsListImport from "./JournalsListImport";
import { routeResolver } from '../App'; import { routeResolver } from "../App";
import { store, JournalsData, UserInfoData, CredentialsData } from '../store'; import { store, JournalsData, UserInfoData, CredentialsData } from "../store";
import { addJournal, deleteJournal, updateJournal } from '../store/actions'; import { addJournal, deleteJournal, updateJournal } from "../store/actions";
import { SyncInfo } from '../SyncGate'; import { SyncInfo } from "../SyncGate";
import * as EteSync from 'etesync'; import * as EteSync from "etesync";
class Journals extends React.PureComponent { class Journals extends React.PureComponent {
public props: { public props: {
@ -40,7 +40,7 @@ class Journals extends React.PureComponent {
return ( return (
<Switch> <Switch>
<Route <Route
path={routeResolver.getRoute('journals')} path={routeResolver.getRoute("journals")}
exact exact
render={({ history }) => ( render={({ history }) => (
<> <>
@ -54,7 +54,7 @@ class Journals extends React.PureComponent {
)} )}
/> />
<Route <Route
path={routeResolver.getRoute('journals.import')} path={routeResolver.getRoute("journals.import")}
exact exact
render={() => ( render={() => (
<> <>
@ -68,7 +68,7 @@ class Journals extends React.PureComponent {
)} )}
/> />
<Route <Route
path={routeResolver.getRoute('journals.new')} path={routeResolver.getRoute("journals.new")}
render={() => ( render={() => (
<JournalEdit <JournalEdit
syncInfo={this.props.syncInfo} syncInfo={this.props.syncInfo}
@ -79,7 +79,7 @@ class Journals extends React.PureComponent {
)} )}
/> />
<Route <Route
path={routeResolver.getRoute('journals._id')} path={routeResolver.getRoute("journals._id")}
render={({ match }) => { render={({ match }) => {
const journalUid = match.params.journalUid; const journalUid = match.params.journalUid;
@ -94,7 +94,7 @@ class Journals extends React.PureComponent {
return ( return (
<Switch> <Switch>
<Route <Route
path={routeResolver.getRoute('journals._id.edit')} path={routeResolver.getRoute("journals._id.edit")}
render={() => ( render={() => (
<JournalEdit <JournalEdit
syncInfo={this.props.syncInfo} syncInfo={this.props.syncInfo}
@ -106,7 +106,7 @@ class Journals extends React.PureComponent {
)} )}
/> />
<Route <Route
path={routeResolver.getRoute('journals._id.members')} path={routeResolver.getRoute("journals._id.members")}
render={() => ( render={() => (
<JournalMembers <JournalMembers
etesync={this.props.etesync} etesync={this.props.etesync}
@ -116,7 +116,7 @@ class Journals extends React.PureComponent {
)} )}
/> />
<Route <Route
path={routeResolver.getRoute('journals._id')} path={routeResolver.getRoute("journals._id")}
render={() => ( render={() => (
<Journal <Journal
etesync={this.props.etesync} etesync={this.props.etesync}
@ -169,7 +169,7 @@ class Journals extends React.PureComponent {
journal.setInfo(cryptoManager, info); journal.setInfo(cryptoManager, info);
store.dispatch<any>(deleteJournal(this.props.etesync, journal)).then(() => store.dispatch<any>(deleteJournal(this.props.etesync, journal)).then(() =>
this.props.history.push(routeResolver.getRoute('journals')) this.props.history.push(routeResolver.getRoute("journals"))
); );
} }

@ -1,15 +1,15 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import { Route, Switch, withRouter } from 'react-router'; import { Route, Switch, withRouter } from "react-router";
import * as EteSync from 'etesync'; import * as EteSync from "etesync";
import { routeResolver } from '../App'; import { routeResolver } from "../App";
import { History } from 'history'; import { History } from "history";
import { SyncInfo } from '../SyncGate'; import { SyncInfo } from "../SyncGate";
function objValues(obj: any) { function objValues(obj: any) {
return Object.keys(obj).map((x) => obj[x]); return Object.keys(obj).map((x) => obj[x]);
@ -33,7 +33,7 @@ export function journalView(JournalList: any, JournalItem: any) {
const uid = contact.uid; const uid = contact.uid;
this.props.history!.push( this.props.history!.push(
routeResolver.getRoute('journals._id.items._id', { journalUid: this.props.journal.uid, itemUid: encodeURIComponent(uid) })); routeResolver.getRoute("journals._id.items._id", { journalUid: this.props.journal.uid, itemUid: encodeURIComponent(uid) }));
} }
public render() { public render() {
@ -42,7 +42,7 @@ export function journalView(JournalList: any, JournalItem: any) {
return ( return (
<Switch> <Switch>
<Route <Route
path={routeResolver.getRoute('journals._id')} path={routeResolver.getRoute("journals._id")}
exact exact
render={() => ( render={() => (
<JournalList syncInfo={this.props.syncInfo} entries={objValues(items)} onItemClick={this.itemClicked} /> <JournalList syncInfo={this.props.syncInfo} entries={objValues(items)} onItemClick={this.itemClicked} />
@ -50,7 +50,7 @@ export function journalView(JournalList: any, JournalItem: any) {
} }
/> />
<Route <Route
path={routeResolver.getRoute('journals._id.items._id')} path={routeResolver.getRoute("journals._id.items._id")}
exact exact
render={({ match }) => { render={({ match }) => {

@ -1,26 +1,26 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import { Action } from 'redux-actions'; import { Action } from "redux-actions";
import Container from './widgets/Container'; import Container from "./widgets/Container";
import ExternalLink from './widgets/ExternalLink'; import ExternalLink from "./widgets/ExternalLink";
import SyncGate from './SyncGate'; import SyncGate from "./SyncGate";
import LoginForm from './components/LoginForm'; import LoginForm from "./components/LoginForm";
import EncryptionLoginForm from './components/EncryptionLoginForm'; import EncryptionLoginForm from "./components/EncryptionLoginForm";
import { store, StoreState, CredentialsDataRemote } from './store'; import { store, StoreState, CredentialsDataRemote } from "./store";
import { deriveKey, fetchCredentials, fetchUserInfo, logout } from './store/actions'; import { deriveKey, fetchCredentials, fetchUserInfo, logout } from "./store/actions";
import * as EteSync from 'etesync'; import * as EteSync from "etesync";
import * as C from './constants'; import * as C from "./constants";
import SignedPagesBadge from './images/signed-pages-badge.svg'; import SignedPagesBadge from "./images/signed-pages-badge.svg";
import LoadingIndicator from './widgets/LoadingIndicator'; import LoadingIndicator from "./widgets/LoadingIndicator";
import { useCredentials, useRemoteCredentials } from './login'; import { useCredentials, useRemoteCredentials } from "./login";
import { useSelector } from 'react-redux'; import { useSelector } from "react-redux";
function EncryptionPart(props: { credentials: CredentialsDataRemote }) { function EncryptionPart(props: { credentials: CredentialsDataRemote }) {
@ -60,7 +60,7 @@ function EncryptionPart(props: { credentials: CredentialsDataRemote }) {
try { try {
userInfo.verify(userInfoCryptoManager); userInfo.verify(userInfoCryptoManager);
} catch (e) { } catch (e) {
setError(new EteSync.EncryptionPasswordError('Wrong encryption password')); setError(new EteSync.EncryptionPasswordError("Wrong encryption password"));
return; return;
} }
} }
@ -70,7 +70,7 @@ function EncryptionPart(props: { credentials: CredentialsDataRemote }) {
const isNewUser = !userInfo; const isNewUser = !userInfo;
return ( return (
<Container style={{ maxWidth: '30rem' }}> <Container style={{ maxWidth: "30rem" }}>
<h2>Encryption Password</h2> <h2>Encryption Password</h2>
{(isNewUser) ? {(isNewUser) ?
<div> <div>
@ -115,17 +115,17 @@ export default function LoginGate() {
if (remoteCredentials === null) { if (remoteCredentials === null) {
const style = { const style = {
isSafe: { isSafe: {
textDecoration: 'none', textDecoration: "none",
display: 'block', display: "block",
}, },
divider: { divider: {
margin: '30px 0', margin: "30px 0",
color: '#00000025', color: "#00000025",
}, },
}; };
return ( return (
<Container style={{ maxWidth: '30rem' }}> <Container style={{ maxWidth: "30rem" }}>
<h2>Please Log In</h2> <h2>Please Log In</h2>
<LoginForm <LoginForm
onSubmit={onFormSubmit} onSubmit={onFormSubmit}
@ -140,7 +140,7 @@ export default function LoginGate() {
<li><ExternalLink style={style.isSafe} href={C.homePage}> <li><ExternalLink style={style.isSafe} href={C.homePage}>
The EteSync Website The EteSync Website
</ExternalLink></li> </ExternalLink></li>
<li><ExternalLink style={style.isSafe} href={C.faq + '#web-client'}> <li><ExternalLink style={style.isSafe} href={C.faq + "#web-client"}>
Is the web client safe to use? Is the web client safe to use?
</ExternalLink></li> </ExternalLink></li>
<li><ExternalLink style={style.isSafe} href={C.sourceCode}>Source code</ExternalLink></li> <li><ExternalLink style={style.isSafe} href={C.sourceCode}>Source code</ExternalLink></li>

@ -1,38 +1,38 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import Fab from '@material-ui/core/Fab'; import Fab from "@material-ui/core/Fab";
import ContentAdd from '@material-ui/icons/Add'; import ContentAdd from "@material-ui/icons/Add";
import Tab from '@material-ui/core/Tab'; import Tab from "@material-ui/core/Tab";
import Tabs from '@material-ui/core/Tabs'; import Tabs from "@material-ui/core/Tabs";
import { Theme, withTheme } from '@material-ui/core/styles'; import { Theme, withTheme } from "@material-ui/core/styles";
import * as ICAL from 'ical.js'; import * as ICAL from "ical.js";
import * as EteSync from 'etesync'; import * as EteSync from "etesync";
import { Location, History } from 'history'; import { Location, History } from "history";
import Container from '../widgets/Container'; import Container from "../widgets/Container";
import SearchableAddressBook from '../components/SearchableAddressBook'; import SearchableAddressBook from "../components/SearchableAddressBook";
import Calendar from '../components/Calendar'; import Calendar from "../components/Calendar";
import TaskList from '../components/Tasks/TaskList'; import TaskList from "../components/Tasks/TaskList";
import { EventType, ContactType, TaskType, PimType } from '../pim-types'; import { EventType, ContactType, TaskType, PimType } from "../pim-types";
import { routeResolver } from '../App'; import { routeResolver } from "../App";
import { historyPersistor } from '../persist-state-history'; import { historyPersistor } from "../persist-state-history";
import { SyncInfo } from '../SyncGate'; import { SyncInfo } from "../SyncGate";
import { UserInfoData, CredentialsData } from '../store'; import { UserInfoData, CredentialsData } from "../store";
const addressBookTitle = 'Address Book'; const addressBookTitle = "Address Book";
const calendarTitle = 'Calendar'; const calendarTitle = "Calendar";
const tasksTitle = 'Tasks'; const tasksTitle = "Tasks";
const PersistCalendar = historyPersistor('Calendar')(Calendar); const PersistCalendar = historyPersistor("Calendar")(Calendar);
interface PropsType { interface PropsType {
contacts: ContactType[]; contacts: ContactType[];
@ -68,7 +68,7 @@ class PimMain extends React.PureComponent<PropsType> {
const itemUid = `${(event as any).journalUid}|${encodeURIComponent(event.uid)}`; const itemUid = `${(event as any).journalUid}|${encodeURIComponent(event.uid)}`;
this.props.history!.push( this.props.history!.push(
routeResolver.getRoute('pim.events._id', { itemUid })); routeResolver.getRoute("pim.events._id", { itemUid }));
} }
public taskClicked(event: ICAL.Event) { public taskClicked(event: ICAL.Event) {
@ -76,7 +76,7 @@ class PimMain extends React.PureComponent<PropsType> {
const itemUid = `${(event as any).journalUid}|${encodeURIComponent(event.uid)}`; const itemUid = `${(event as any).journalUid}|${encodeURIComponent(event.uid)}`;
this.props.history!.push( this.props.history!.push(
routeResolver.getRoute('pim.tasks._id.edit', { itemUid })); routeResolver.getRoute("pim.tasks._id.edit", { itemUid }));
} }
public contactClicked(contact: ContactType) { public contactClicked(contact: ContactType) {
@ -84,12 +84,12 @@ class PimMain extends React.PureComponent<PropsType> {
const itemUid = `${(contact as any).journalUid}|${encodeURIComponent(contact.uid)}`; const itemUid = `${(contact as any).journalUid}|${encodeURIComponent(contact.uid)}`;
this.props.history!.push( this.props.history!.push(
routeResolver.getRoute('pim.contacts._id', { itemUid })); routeResolver.getRoute("pim.contacts._id", { itemUid }));
} }
public newEvent(start?: Date, end?: Date) { public newEvent(start?: Date, end?: Date) {
this.props.history!.push( this.props.history!.push(
routeResolver.getRoute('pim.events.new'), routeResolver.getRoute("pim.events.new"),
{ start, end } { start, end }
); );
} }
@ -97,13 +97,13 @@ class PimMain extends React.PureComponent<PropsType> {
public floatingButtonClicked() { public floatingButtonClicked() {
if (this.state.tab === 0) { if (this.state.tab === 0) {
this.props.history!.push( this.props.history!.push(
routeResolver.getRoute('pim.contacts.new') routeResolver.getRoute("pim.contacts.new")
); );
} else if (this.state.tab === 1) { } else if (this.state.tab === 1) {
this.newEvent(); this.newEvent();
} else if (this.state.tab === 2) { } else if (this.state.tab === 2) {
this.props.history!.push( this.props.history!.push(
routeResolver.getRoute('pim.tasks.new') routeResolver.getRoute("pim.tasks.new")
); );
} }
} }
@ -114,11 +114,11 @@ class PimMain extends React.PureComponent<PropsType> {
const style = { const style = {
floatingButton: { floatingButton: {
margin: 0, margin: 0,
top: 'auto', top: "auto",
right: 20, right: 20,
bottom: 20, bottom: 20,
left: 'auto', left: "auto",
position: 'fixed', position: "fixed",
} as any, } as any,
}; };
@ -176,4 +176,4 @@ class PimMain extends React.PureComponent<PropsType> {
} }
} }
export default withTheme(historyPersistor('PimMain')(PimMain)); export default withTheme(historyPersistor("PimMain")(PimMain));

@ -1,47 +1,47 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import { Route, Switch } from 'react-router'; import { Route, Switch } from "react-router";
import Button from '@material-ui/core/Button'; import Button from "@material-ui/core/Button";
import IconEdit from '@material-ui/icons/Edit'; import IconEdit from "@material-ui/icons/Edit";
import IconDuplicate from '@material-ui/icons/FileCopy'; import IconDuplicate from "@material-ui/icons/FileCopy";
import IconChangeHistory from '@material-ui/icons/ChangeHistory'; import IconChangeHistory from "@material-ui/icons/ChangeHistory";
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from "@material-ui/core/styles";
import { RouteComponentProps, withRouter } from 'react-router'; import { RouteComponentProps, withRouter } from "react-router";
import { Action } from 'redux-actions'; import { Action } from "redux-actions";
import * as EteSync from 'etesync'; import * as EteSync from "etesync";
import { createSelector } from 'reselect'; import { createSelector } from "reselect";
import { History } from 'history'; import { History } from "history";
import { PimType, ContactType, EventType, TaskType } from '../pim-types'; import { PimType, ContactType, EventType, TaskType } from "../pim-types";
import Container from '../widgets/Container'; import Container from "../widgets/Container";
import JournalEntries from '../components/JournalEntries'; import JournalEntries from "../components/JournalEntries";
import ContactEdit from '../components/ContactEdit'; import ContactEdit from "../components/ContactEdit";
import Contact from '../components/Contact'; import Contact from "../components/Contact";
import EventEdit from '../components/EventEdit'; import EventEdit from "../components/EventEdit";
import Event from '../components/Event'; import Event from "../components/Event";
import TaskEdit from '../components/Tasks/TaskEdit'; import TaskEdit from "../components/Tasks/TaskEdit";
import Task from '../components/Tasks/Task'; import Task from "../components/Tasks/Task";
import PimMain from './PimMain'; import PimMain from "./PimMain";
import { routeResolver } from '../App'; import { routeResolver } from "../App";
import { store, CredentialsData, UserInfoData } from '../store'; import { store, CredentialsData, UserInfoData } from "../store";
import { fetchEntries } from '../store/actions'; import { fetchEntries } from "../store/actions";
import { SyncInfo } from '../SyncGate'; import { SyncInfo } from "../SyncGate";
import { addJournalEntry } from '../etesync-helpers'; import { addJournalEntry } from "../etesync-helpers";
import { syncEntriesToItemMap, syncEntriesToEventItemMap, syncEntriesToTaskItemMap } from '../journal-processors'; import { syncEntriesToItemMap, syncEntriesToEventItemMap, syncEntriesToTaskItemMap } from "../journal-processors";
const itemsSelector = createSelector( const itemsSelector = createSelector(
(props: {syncInfo: SyncInfo}) => props.syncInfo, (props: {syncInfo: SyncInfo}) => props.syncInfo,
@ -58,13 +58,13 @@ const itemsSelector = createSelector(
const collectionInfo = syncJournal.collection; const collectionInfo = syncJournal.collection;
if (collectionInfo.type === 'ADDRESS_BOOK') { if (collectionInfo.type === "ADDRESS_BOOK") {
addressBookItems = syncEntriesToItemMap(collectionInfo, syncEntries, addressBookItems); addressBookItems = syncEntriesToItemMap(collectionInfo, syncEntries, addressBookItems);
collectionsAddressBook.push(collectionInfo); collectionsAddressBook.push(collectionInfo);
} else if (collectionInfo.type === 'CALENDAR') { } else if (collectionInfo.type === "CALENDAR") {
calendarItems = syncEntriesToEventItemMap(collectionInfo, syncEntries, calendarItems); calendarItems = syncEntriesToEventItemMap(collectionInfo, syncEntries, calendarItems);
collectionsCalendar.push(collectionInfo); collectionsCalendar.push(collectionInfo);
} else if (collectionInfo.type === 'TASKS') { } else if (collectionInfo.type === "TASKS") {
taskListItems = syncEntriesToTaskItemMap(collectionInfo, syncEntries, taskListItems); taskListItems = syncEntriesToTaskItemMap(collectionInfo, syncEntries, taskListItems);
collectionsTaskList.push(collectionInfo); collectionsTaskList.push(collectionInfo);
} }
@ -88,9 +88,9 @@ const ItemChangeLog = React.memo((props: any) => {
paramItemUid, paramItemUid,
} = props; } = props;
const tmp = paramItemUid.split('|'); const tmp = paramItemUid.split("|");
const journalUid = tmp.shift(); const journalUid = tmp.shift();
const uid = tmp.join('|'); const uid = tmp.join("|");
const journalItem = syncInfo.get(journalUid); const journalItem = syncInfo.get(journalUid);
return ( return (
@ -152,10 +152,10 @@ const CollectionRoutes = withStyles(styles)(withRouter(
return ( return (
<Switch> <Switch>
<Route <Route
path={routeResolver.getRoute(props.routePrefix + '.new')} path={routeResolver.getRoute(props.routePrefix + ".new")}
exact exact
render={() => ( render={() => (
<Container style={{ maxWidth: '30rem' }}> <Container style={{ maxWidth: "30rem" }}>
<ComponentEdit <ComponentEdit
key={props.routePrefix} key={props.routePrefix}
collections={props.collections} collections={props.collections}
@ -167,7 +167,7 @@ const CollectionRoutes = withStyles(styles)(withRouter(
)} )}
/> />
<Route <Route
path={routeResolver.getRoute(props.routePrefix + '._id.edit')} path={routeResolver.getRoute(props.routePrefix + "._id.edit")}
exact exact
render={({ match }) => { render={({ match }) => {
const itemUid = decodeURIComponent(match.params.itemUid); const itemUid = decodeURIComponent(match.params.itemUid);
@ -175,7 +175,7 @@ const CollectionRoutes = withStyles(styles)(withRouter(
return PageNotFound(); return PageNotFound();
} }
return ( return (
<Container style={{ maxWidth: '30rem' }}> <Container style={{ maxWidth: "30rem" }}>
{(itemUid in props.items) && {(itemUid in props.items) &&
<ComponentEdit <ComponentEdit
key={(props.items[itemUid] as any).journalUid} key={(props.items[itemUid] as any).journalUid}
@ -192,9 +192,9 @@ const CollectionRoutes = withStyles(styles)(withRouter(
); );
}} }}
/> />
{props.routePrefix === 'pim.events' && {props.routePrefix === "pim.events" &&
<Route <Route
path={routeResolver.getRoute(props.routePrefix + '._id.duplicate')} path={routeResolver.getRoute(props.routePrefix + "._id.duplicate")}
exact exact
render={({ match }) => { render={({ match }) => {
const itemUid = decodeURIComponent(match.params.itemUid); const itemUid = decodeURIComponent(match.params.itemUid);
@ -202,7 +202,7 @@ const CollectionRoutes = withStyles(styles)(withRouter(
return PageNotFound(); return PageNotFound();
} }
return ( return (
<Container style={{ maxWidth: '30rem' }}> <Container style={{ maxWidth: "30rem" }}>
{(itemUid in props.items) && {(itemUid in props.items) &&
<ComponentEdit <ComponentEdit
key={(props.items[itemUid] as any).journalUid} key={(props.items[itemUid] as any).journalUid}
@ -222,7 +222,7 @@ const CollectionRoutes = withStyles(styles)(withRouter(
/> />
} }
<Route <Route
path={routeResolver.getRoute(props.routePrefix + '._id.log')} path={routeResolver.getRoute(props.routePrefix + "._id.log")}
exact exact
render={({ match }) => { render={({ match }) => {
const paramItemUid = decodeURIComponent(match.params.itemUid); const paramItemUid = decodeURIComponent(match.params.itemUid);
@ -240,7 +240,7 @@ const CollectionRoutes = withStyles(styles)(withRouter(
}} }}
/> />
<Route <Route
path={routeResolver.getRoute(props.routePrefix + '._id')} path={routeResolver.getRoute(props.routePrefix + "._id")}
exact exact
render={({ match, history }) => { render={({ match, history }) => {
const itemUid = decodeURIComponent(match.params.itemUid); const itemUid = decodeURIComponent(match.params.itemUid);
@ -249,13 +249,13 @@ const CollectionRoutes = withStyles(styles)(withRouter(
} }
return ( return (
<Container> <Container>
<div style={{ textAlign: 'right', marginBottom: 15 }}> <div style={{ textAlign: "right", marginBottom: 15 }}>
<Button <Button
variant="contained" variant="contained"
className={classes.button} className={classes.button}
onClick={() => onClick={() =>
history.push(routeResolver.getRoute( history.push(routeResolver.getRoute(
props.routePrefix + '._id.log', props.routePrefix + "._id.log",
{ itemUid: match.params.itemUid })) { itemUid: match.params.itemUid }))
} }
> >
@ -271,7 +271,7 @@ const CollectionRoutes = withStyles(styles)(withRouter(
style={{ marginLeft: 15 }} style={{ marginLeft: 15 }}
onClick={() => onClick={() =>
history.push(routeResolver.getRoute( history.push(routeResolver.getRoute(
props.routePrefix + '._id.edit', props.routePrefix + "._id.edit",
{ itemUid: match.params.itemUid })) { itemUid: match.params.itemUid }))
} }
> >
@ -279,7 +279,7 @@ const CollectionRoutes = withStyles(styles)(withRouter(
Edit Edit
</Button> </Button>
{props.routePrefix === 'pim.events' && {props.routePrefix === "pim.events" &&
<Button <Button
color="secondary" color="secondary"
variant="contained" variant="contained"
@ -288,7 +288,7 @@ const CollectionRoutes = withStyles(styles)(withRouter(
style={{ marginLeft: 15 }} style={{ marginLeft: 15 }}
onClick={() => onClick={() =>
history.push(routeResolver.getRoute( history.push(routeResolver.getRoute(
props.routePrefix + '._id.duplicate', props.routePrefix + "._id.duplicate",
{ itemUid: match.params.itemUid })) { itemUid: match.params.itemUid }))
} }
> >
@ -385,7 +385,7 @@ class Pim extends React.PureComponent {
this.props.etesync, this.props.userInfo, journal, this.props.etesync, this.props.userInfo, journal,
prevUid, action, item.toIcal())); prevUid, action, item.toIcal()));
(deleteItem as any).then(() => { (deleteItem as any).then(() => {
this.props.history.push(routeResolver.getRoute('pim')); this.props.history.push(routeResolver.getRoute("pim"));
}); });
}); });
} }
@ -401,7 +401,7 @@ class Pim extends React.PureComponent {
return ( return (
<Switch> <Switch>
<Route <Route
path={routeResolver.getRoute('pim')} path={routeResolver.getRoute("pim")}
exact exact
render={({ history }) => ( render={({ history }) => (
<PimMain <PimMain
@ -418,7 +418,7 @@ class Pim extends React.PureComponent {
)} )}
/> />
<Route <Route
path={routeResolver.getRoute('pim.contacts')} path={routeResolver.getRoute("pim.contacts")}
render={() => ( render={() => (
<CollectionRoutes <CollectionRoutes
syncInfo={this.props.syncInfo} syncInfo={this.props.syncInfo}
@ -434,7 +434,7 @@ class Pim extends React.PureComponent {
)} )}
/> />
<Route <Route
path={routeResolver.getRoute('pim.events')} path={routeResolver.getRoute("pim.events")}
render={() => ( render={() => (
<CollectionRoutes <CollectionRoutes
syncInfo={this.props.syncInfo} syncInfo={this.props.syncInfo}
@ -450,7 +450,7 @@ class Pim extends React.PureComponent {
)} )}
/> />
<Route <Route
path={routeResolver.getRoute('pim.tasks')} path={routeResolver.getRoute("pim.tasks")}
render={() => ( render={() => (
<CollectionRoutes <CollectionRoutes
syncInfo={this.props.syncInfo} syncInfo={this.props.syncInfo}

@ -1,23 +1,23 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import { useSelector, useDispatch } from 'react-redux'; import { useSelector, useDispatch } from "react-redux";
import Select from '@material-ui/core/Select'; import Select from "@material-ui/core/Select";
import MenuItem from '@material-ui/core/MenuItem'; import MenuItem from "@material-ui/core/MenuItem";
import FormControl from '@material-ui/core/FormControl'; import FormControl from "@material-ui/core/FormControl";
import FormControlLabel from '@material-ui/core/FormControlLabel'; import FormControlLabel from "@material-ui/core/FormControlLabel";
import FormGroup from '@material-ui/core/FormGroup'; import FormGroup from "@material-ui/core/FormGroup";
import Switch from '@material-ui/core/Switch'; import Switch from "@material-ui/core/Switch";
import InputLabel from '@material-ui/core/InputLabel'; import InputLabel from "@material-ui/core/InputLabel";
import { StoreState } from '../store'; import { StoreState } from "../store";
import { setSettings } from '../store/actions'; import { setSettings } from "../store/actions";
import Container from '../widgets/Container'; import Container from "../widgets/Container";
import AppBarOverride from '../widgets/AppBarOverride'; import AppBarOverride from "../widgets/AppBarOverride";
import PrettyFingerprint from '../widgets/PrettyFingerprint'; import PrettyFingerprint from "../widgets/PrettyFingerprint";
function SecurityFingerprint() { function SecurityFingerprint() {
const userInfo = useSelector((state: StoreState) => state.cache.userInfo); const userInfo = useSelector((state: StoreState) => state.cache.userInfo);
@ -58,7 +58,7 @@ export default React.memo(function Settings() {
<h1>Security Fingerprint</h1> <h1>Security Fingerprint</h1>
<SecurityFingerprint /> <SecurityFingerprint />
<h1>Date & Time</h1> <h1>Date & Time</h1>
<FormControl style={{ width: '15em' }}> <FormControl style={{ width: "15em" }}>
<InputLabel> <InputLabel>
Locale Locale
</InputLabel> </InputLabel>

@ -1,28 +1,28 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import { connect } from 'react-redux'; import { connect } from "react-redux";
import { RouteComponentProps, withRouter } from 'react-router'; import { RouteComponentProps, withRouter } from "react-router";
import { List, ListItem, ListSubheader, ListDivider } from '../widgets/List'; import { List, ListItem, ListSubheader, ListDivider } from "../widgets/List";
import { Theme, withTheme } from '@material-ui/core/styles'; import { Theme, withTheme } from "@material-ui/core/styles";
import ActionCode from '@material-ui/icons/Code'; import ActionCode from "@material-ui/icons/Code";
import ActionHome from '@material-ui/icons/Home'; import ActionHome from "@material-ui/icons/Home";
import ActionSettings from '@material-ui/icons/Settings'; import ActionSettings from "@material-ui/icons/Settings";
import ActionJournals from '@material-ui/icons/LibraryBooks'; import ActionJournals from "@material-ui/icons/LibraryBooks";
import ActionBugReport from '@material-ui/icons/BugReport'; import ActionBugReport from "@material-ui/icons/BugReport";
import ActionQuestionAnswer from '@material-ui/icons/QuestionAnswer'; import ActionQuestionAnswer from "@material-ui/icons/QuestionAnswer";
import LogoutIcon from '@material-ui/icons/PowerSettingsNew'; import LogoutIcon from "@material-ui/icons/PowerSettingsNew";
import IconImport from '@material-ui/icons/ImportExport'; import IconImport from "@material-ui/icons/ImportExport";
import logo from '../images/logo.svg'; import logo from "../images/logo.svg";
import { routeResolver } from '../App'; import { routeResolver } from "../App";
import { store, UserInfoData, StoreState, CredentialsData } from '../store'; import { store, UserInfoData, StoreState, CredentialsData } from "../store";
import { logout } from '../store/actions'; import { logout } from "../store/actions";
import * as C from '../constants'; import * as C from "../constants";
interface PropsType { interface PropsType {
etesync: CredentialsData | null; etesync: CredentialsData | null;
@ -60,7 +60,7 @@ class SideMenu extends React.PureComponent<PropsTypeInner> {
leftIcon={<ActionJournals />} leftIcon={<ActionJournals />}
onClick={() => { onClick={() => {
this.props.onCloseDrawerRequest(); this.props.onCloseDrawerRequest();
this.props.history.push(routeResolver.getRoute('journals')); this.props.history.push(routeResolver.getRoute("journals"));
}} }}
/> />
<ListItem <ListItem
@ -68,7 +68,7 @@ class SideMenu extends React.PureComponent<PropsTypeInner> {
leftIcon={<IconImport />} leftIcon={<IconImport />}
onClick={() => { onClick={() => {
this.props.onCloseDrawerRequest(); this.props.onCloseDrawerRequest();
this.props.history.push(routeResolver.getRoute('journals.import')); this.props.history.push(routeResolver.getRoute("journals.import"));
}} }}
/> />
<ListItem <ListItem
@ -76,7 +76,7 @@ class SideMenu extends React.PureComponent<PropsTypeInner> {
leftIcon={<ActionSettings />} leftIcon={<ActionSettings />}
onClick={() => { onClick={() => {
this.props.onCloseDrawerRequest(); this.props.onCloseDrawerRequest();
this.props.history.push(routeResolver.getRoute('settings')); this.props.history.push(routeResolver.getRoute("settings"));
}} }}
/> />
<ListItem primaryText="Log Out" leftIcon={<LogoutIcon />} onClick={this.logout} /> <ListItem primaryText="Log Out" leftIcon={<LogoutIcon />} onClick={this.logout} />
@ -85,7 +85,7 @@ class SideMenu extends React.PureComponent<PropsTypeInner> {
} }
return ( return (
<div style={{ overflowX: 'hidden', width: 250 }}> <div style={{ overflowX: "hidden", width: 250 }}>
<div className="App-drawer-header"> <div className="App-drawer-header">
<img alt="App logo" className="App-drawer-logo" src={logo} /> <img alt="App logo" className="App-drawer-logo" src={logo} />
<div style={{ color: theme.palette.secondary.contrastText }}> <div style={{ color: theme.palette.secondary.contrastText }}>
@ -98,7 +98,7 @@ class SideMenu extends React.PureComponent<PropsTypeInner> {
leftIcon={<ActionHome />} leftIcon={<ActionHome />}
onClick={() => { onClick={() => {
this.props.onCloseDrawerRequest(); this.props.onCloseDrawerRequest();
this.props.history.push(routeResolver.getRoute('home')); this.props.history.push(routeResolver.getRoute("home"));
}} }}
/> />
{loggedInItems} {loggedInItems}

@ -1,31 +1,31 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import { useSelector } from 'react-redux'; import { useSelector } from "react-redux";
import { Route, Switch, Redirect, RouteComponentProps, withRouter } from 'react-router'; import { Route, Switch, Redirect, RouteComponentProps, withRouter } from "react-router";
import moment from 'moment'; import moment from "moment";
import 'moment/locale/en-gb'; import "moment/locale/en-gb";
import { List, Map } from 'immutable'; import { List, Map } from "immutable";
import { createSelector } from 'reselect'; import { createSelector } from "reselect";
import { routeResolver } from './App'; import { routeResolver } from "./App";
import AppBarOverride from './widgets/AppBarOverride'; import AppBarOverride from "./widgets/AppBarOverride";
import LoadingIndicator from './widgets/LoadingIndicator'; import LoadingIndicator from "./widgets/LoadingIndicator";
import Journals from './Journals'; import Journals from "./Journals";
import Settings from './Settings'; import Settings from "./Settings";
import Debug from './Debug'; import Debug from "./Debug";
import Pim from './Pim'; import Pim from "./Pim";
import * as EteSync from 'etesync'; import * as EteSync from "etesync";
import { CURRENT_VERSION } from 'etesync'; import { CURRENT_VERSION } from "etesync";
import { store, JournalsData, EntriesData, StoreState, CredentialsData, UserInfoData } from './store'; import { store, JournalsData, EntriesData, StoreState, CredentialsData, UserInfoData } from "./store";
import { addJournal, fetchAll, fetchEntries, fetchUserInfo, createUserInfo } from './store/actions'; import { addJournal, fetchAll, fetchEntries, fetchUserInfo, createUserInfo } from "./store/actions";
export interface SyncInfoJournal { export interface SyncInfoJournal {
journal: EteSync.Journal; journal: EteSync.Journal;
@ -119,16 +119,16 @@ export default withRouter(function SyncGate(props: RouteComponentProps<{}> & Pro
[ [
{ {
type: 'ADDRESS_BOOK', type: "ADDRESS_BOOK",
name: 'My Contacts', name: "My Contacts",
}, },
{ {
type: 'CALENDAR', type: "CALENDAR",
name: 'My Calendar', name: "My Calendar",
}, },
{ {
type: 'TASKS', type: "TASKS",
name: 'My Tasks', name: "My Tasks",
}, },
].forEach((collectionDesc) => { ].forEach((collectionDesc) => {
const collection = new EteSync.CollectionInfo(); const collection = new EteSync.CollectionInfo();
@ -179,7 +179,7 @@ export default withRouter(function SyncGate(props: RouteComponentProps<{}> & Pro
((fetchCount > 0) && ((fetchCount > 0) &&
((entryArrays.size === 0) || entryArrays.some((x) => (x.size === 0)))) ((entryArrays.size === 0) || entryArrays.some((x) => (x.size === 0))))
) { ) {
return (<LoadingIndicator style={{ display: 'block', margin: '40px auto' }} />); return (<LoadingIndicator style={{ display: "block", margin: "40px auto" }} />);
} }
// FIXME: Shouldn't be here // FIXME: Shouldn't be here
@ -191,14 +191,14 @@ export default withRouter(function SyncGate(props: RouteComponentProps<{}> & Pro
return ( return (
<Switch> <Switch>
<Route <Route
path={routeResolver.getRoute('home')} path={routeResolver.getRoute("home")}
exact exact
render={() => ( render={() => (
<Redirect to={routeResolver.getRoute('pim')} /> <Redirect to={routeResolver.getRoute("pim")} />
)} )}
/> />
<Route <Route
path={routeResolver.getRoute('pim')} path={routeResolver.getRoute("pim")}
render={({ history }) => ( render={({ history }) => (
<> <>
<AppBarOverride title="EteSync" /> <AppBarOverride title="EteSync" />
@ -212,7 +212,7 @@ export default withRouter(function SyncGate(props: RouteComponentProps<{}> & Pro
)} )}
/> />
<Route <Route
path={routeResolver.getRoute('journals')} path={routeResolver.getRoute("journals")}
render={({ location, history }) => ( render={({ location, history }) => (
<Journals <Journals
etesync={etesync} etesync={etesync}
@ -225,14 +225,14 @@ export default withRouter(function SyncGate(props: RouteComponentProps<{}> & Pro
)} )}
/> />
<Route <Route
path={routeResolver.getRoute('settings')} path={routeResolver.getRoute("settings")}
exact exact
render={() => ( render={() => (
<Settings /> <Settings />
)} )}
/> />
<Route <Route
path={routeResolver.getRoute('debug')} path={routeResolver.getRoute("debug")}
exact exact
render={() => ( render={() => (
<Debug <Debug

@ -1,17 +1,17 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import { createSelector } from 'reselect'; import { createSelector } from "reselect";
import * as colors from '@material-ui/core/colors'; import * as colors from "@material-ui/core/colors";
import { AutoSizer, List as VirtualizedList } from 'react-virtualized'; import { AutoSizer, List as VirtualizedList } from "react-virtualized";
import { Avatar } from '../widgets/Avatar'; import { Avatar } from "../widgets/Avatar";
import { List, ListItem } from '../widgets/List'; import { List, ListItem } from "../widgets/List";
import { ContactType } from '../pim-types'; import { ContactType } from "../pim-types";
function getContactColor(contact: ContactType) { function getContactColor(contact: ContactType) {
const colorOptions = [ const colorOptions = [
@ -73,10 +73,10 @@ const sortSelector = createSelector(
(entries: ContactType[]) => entries, (entries: ContactType[]) => entries,
(entries) => { (entries) => {
return entries.sort((_a, _b) => { return entries.sort((_a, _b) => {
const a = _a.fn ?? ''; const a = _a.fn ?? "";
const b = _b.fn ?? ''; const b = _b.fn ?? "";
return a.localeCompare(b, undefined, { sensitivity: 'base' }); return a.localeCompare(b, undefined, { sensitivity: "base" });
}); });
} }
); );
@ -96,7 +96,7 @@ class AddressBook extends React.PureComponent<PropsType> {
: sortedEntries; : sortedEntries;
return ( return (
<List style={{ height: 'calc(100vh - 300px)' }}> <List style={{ height: "calc(100vh - 300px)" }}>
<AutoSizer> <AutoSizer>
{({ height, width }) => ( {({ height, width }) => (
<VirtualizedList <VirtualizedList

@ -1,15 +1,15 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import { Calendar as BigCalendar, momentLocalizer, View } from 'react-big-calendar'; import { Calendar as BigCalendar, momentLocalizer, View } from "react-big-calendar";
import 'react-big-calendar/lib/css/react-big-calendar.css'; import "react-big-calendar/lib/css/react-big-calendar.css";
import moment from 'moment'; import moment from "moment";
import * as ICAL from 'ical.js'; import * as ICAL from "ical.js";
import { EventType } from '../pim-types'; import { EventType } from "../pim-types";
import './Calendar.css'; import "./Calendar.css";
const calendarLocalizer = momentLocalizer(moment); const calendarLocalizer = momentLocalizer(moment);
@ -25,8 +25,8 @@ function eventPropGetter(event: EventType) {
} }
function agendaHeaderFormat(date: {start: Date, end: Date}, _culture: string, localizer: any) { function agendaHeaderFormat(date: {start: Date, end: Date}, _culture: string, localizer: any) {
const format = 'll'; const format = "ll";
return localizer.format(date.start, format) + ' - ' + localizer.format(date.end, format); return localizer.format(date.start, format) + " - " + localizer.format(date.end, format);
} }
interface PropsType { interface PropsType {
@ -75,7 +75,7 @@ class Calendar extends React.PureComponent<PropsType> {
}); });
return ( return (
<div style={{ width: '100%', height: 'calc(100vh - 230px)', minHeight: 500 }}> <div style={{ width: "100%", height: "calc(100vh - 230px)", minHeight: 500 }}>
<BigCalendar <BigCalendar
defaultDate={new Date()} defaultDate={new Date()}
scrollToTime={new Date(1970, 1, 1, 8)} scrollToTime={new Date(1970, 1, 1, 8)}

@ -1,21 +1,21 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import moment from 'moment'; import moment from "moment";
import { List, ListItem, ListDivider as Divider } from '../widgets/List'; import { List, ListItem, ListDivider as Divider } from "../widgets/List";
import IconHome from '@material-ui/icons/Home'; import IconHome from "@material-ui/icons/Home";
import IconDate from '@material-ui/icons/DateRange'; import IconDate from "@material-ui/icons/DateRange";
import CommunicationCall from '@material-ui/icons/Call'; import CommunicationCall from "@material-ui/icons/Call";
import CommunicationChatBubble from '@material-ui/icons/ChatBubble'; import CommunicationChatBubble from "@material-ui/icons/ChatBubble";
import CommunicationEmail from '@material-ui/icons/Email'; import CommunicationEmail from "@material-ui/icons/Email";
import CopyIcon from '../icons/Copy'; import CopyIcon from "../icons/Copy";
import PimItemHeader from './PimItemHeader'; import PimItemHeader from "./PimItemHeader";
import { ContactType } from '../pim-types'; import { ContactType } from "../pim-types";
import { IconButton, Avatar } from '@material-ui/core'; import { IconButton, Avatar } from "@material-ui/core";
class Contact extends React.PureComponent { class Contact extends React.PureComponent {
public props: { public props: {
@ -24,15 +24,15 @@ class Contact extends React.PureComponent {
public render() { public render() {
if (this.props.item === undefined) { if (this.props.item === undefined) {
throw Error('Contact should be defined!'); throw Error("Contact should be defined!");
} }
const contact = this.props.item; const contact = this.props.item;
const name = contact.fn; const name = contact.fn;
const revProp = contact.comp.getFirstProperty('rev'); const revProp = contact.comp.getFirstProperty("rev");
const lastModified = (revProp) ? const lastModified = (revProp) ?
'Modified: ' + moment(revProp.getFirstValue().toJSDate()).format('LLLL') : undefined; "Modified: " + moment(revProp.getFirstValue().toJSDate()).format("LLLL") : undefined;
const lists = []; const lists = [];
@ -74,60 +74,60 @@ class Contact extends React.PureComponent {
} }
lists.push(getAllType( lists.push(getAllType(
'tel', "tel",
{ {
leftIcon: <CommunicationCall />, leftIcon: <CommunicationCall />,
}, },
(x) => ('tel:' + x) (x) => ("tel:" + x)
)); ));
lists.push(getAllType( lists.push(getAllType(
'email', "email",
{ {
leftIcon: <CommunicationEmail />, leftIcon: <CommunicationEmail />,
}, },
(x) => ('mailto:' + x) (x) => ("mailto:" + x)
)); ));
lists.push(getAllType( lists.push(getAllType(
'impp', "impp",
{ {
leftIcon: <CommunicationChatBubble />, leftIcon: <CommunicationChatBubble />,
}, },
(x) => x, (x) => x,
(x) => (x.substring(x.indexOf(':') + 1)), (x) => (x.substring(x.indexOf(":") + 1)),
(x) => (x.substring(0, x.indexOf(':'))) (x) => (x.substring(0, x.indexOf(":")))
)); ));
lists.push(getAllType( lists.push(getAllType(
'adr', "adr",
{ {
leftIcon: <IconHome />, leftIcon: <IconHome />,
} }
)); ));
lists.push(getAllType( lists.push(getAllType(
'bday', "bday",
{ {
leftIcon: <IconDate />, leftIcon: <IconDate />,
}, },
undefined, undefined,
((x: any) => moment(x.toJSDate()).format('dddd, LL')), ((x: any) => moment(x.toJSDate()).format("dddd, LL")),
() => 'Birthday' () => "Birthday"
)); ));
lists.push(getAllType( lists.push(getAllType(
'anniversary', "anniversary",
{ {
leftIcon: <IconDate />, leftIcon: <IconDate />,
}, },
undefined, undefined,
((x: any) => moment(x.toJSDate()).format('dddd, LL')), ((x: any) => moment(x.toJSDate()).format("dddd, LL")),
() => 'Anniversary' () => "Anniversary"
)); ));
const skips = ['tel', 'email', 'impp', 'adr', 'bday', 'anniversary', 'rev', const skips = ["tel", "email", "impp", "adr", "bday", "anniversary", "rev",
'prodid', 'uid', 'fn', 'n', 'version', 'photo', 'note']; "prodid", "uid", "fn", "n", "version", "photo", "note"];
const theRest = contact.comp.getAllProperties().filter((prop) => ( const theRest = contact.comp.getAllProperties().filter((prop) => (
skips.indexOf(prop.name) === -1 skips.indexOf(prop.name) === -1
)).map((prop, idx) => { )).map((prop, idx) => {
@ -146,14 +146,14 @@ class Contact extends React.PureComponent {
}); });
{ {
const note = contact.comp.getFirstPropertyValue('note'); const note = contact.comp.getFirstPropertyValue("note");
const item = ( const item = (
<ListItem <ListItem
key="note" key="note"
insetChildren insetChildren
secondaryText="note" secondaryText="note"
> >
<pre style={{ wordWrap: 'break-word', whiteSpace: 'pre-wrap', overflowX: 'auto' }}>{note}</pre> <pre style={{ wordWrap: "break-word", whiteSpace: "pre-wrap", overflowX: "auto" }}>{note}</pre>
</ListItem> </ListItem>
); );
theRest.push([item]); theRest.push([item]);
@ -176,13 +176,13 @@ class Contact extends React.PureComponent {
} }
} }
const contactImageSrc = contact.comp.getFirstProperty('photo')?.getFirstValue(); const contactImageSrc = contact.comp.getFirstProperty("photo")?.getFirstValue();
return ( return (
<div> <div>
<PimItemHeader text={name} rightItem={contactImageSrc && (<Avatar style={{ width: '3em', height: '3em' }} src={contactImageSrc} />)}> <PimItemHeader text={name} rightItem={contactImageSrc && (<Avatar style={{ width: "3em", height: "3em" }} src={contactImageSrc} />)}>
{lastModified && ( {lastModified && (
<span style={{ fontSize: '90%' }}>{lastModified}</span> <span style={{ fontSize: "90%" }}>{lastModified}</span>
)} )}
</PimItemHeader> </PimItemHeader>
{lists.map((list, idx) => ( {lists.map((list, idx) => (

@ -1,52 +1,52 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import IconButton from '@material-ui/core/IconButton'; import IconButton from "@material-ui/core/IconButton";
import Button from '@material-ui/core/Button'; import Button from "@material-ui/core/Button";
import TextField from '@material-ui/core/TextField'; import TextField from "@material-ui/core/TextField";
import Select from '@material-ui/core/Select'; import Select from "@material-ui/core/Select";
import MenuItem from '@material-ui/core/MenuItem'; import MenuItem from "@material-ui/core/MenuItem";
import FormControl from '@material-ui/core/FormControl'; import FormControl from "@material-ui/core/FormControl";
import InputLabel from '@material-ui/core/InputLabel'; import InputLabel from "@material-ui/core/InputLabel";
import * as colors from '@material-ui/core/colors'; import * as colors from "@material-ui/core/colors";
import IconDelete from '@material-ui/icons/Delete'; import IconDelete from "@material-ui/icons/Delete";
import IconAdd from '@material-ui/icons/Add'; import IconAdd from "@material-ui/icons/Add";
import IconClear from '@material-ui/icons/Clear'; import IconClear from "@material-ui/icons/Clear";
import IconCancel from '@material-ui/icons/Clear'; import IconCancel from "@material-ui/icons/Clear";
import IconSave from '@material-ui/icons/Save'; import IconSave from "@material-ui/icons/Save";
import ConfirmationDialog from '../widgets/ConfirmationDialog'; import ConfirmationDialog from "../widgets/ConfirmationDialog";
import * as uuid from 'uuid'; import * as uuid from "uuid";
import * as ICAL from 'ical.js'; import * as ICAL from "ical.js";
import * as EteSync from 'etesync'; import * as EteSync from "etesync";
import { ContactType } from '../pim-types'; import { ContactType } from "../pim-types";
import { History } from 'history'; import { History } from "history";
const telTypes = [ const telTypes = [
{ type: 'Home' }, { type: "Home" },
{ type: 'Work' }, { type: "Work" },
{ type: 'Cell' }, { type: "Cell" },
{ type: 'Other' }, { type: "Other" },
]; ];
const emailTypes = telTypes; const emailTypes = telTypes;
const addressTypes = [ const addressTypes = [
{ type: 'Home' }, { type: "Home" },
{ type: 'Work' }, { type: "Work" },
{ type: 'Other' }, { type: "Other" },
]; ];
const imppTypes = [ const imppTypes = [
{ type: 'Jabber' }, { type: "Jabber" },
{ type: 'Hangouts' }, { type: "Hangouts" },
{ type: 'Other' }, { type: "Other" },
]; ];
const TypeSelector = (props: any) => { const TypeSelector = (props: any) => {
@ -70,8 +70,8 @@ class ValueType {
public value: string; public value: string;
constructor(type?: string, value?: string) { constructor(type?: string, value?: string) {
this.type = type ? type : 'home'; this.type = type ? type : "home";
this.value = value ? value : ''; this.value = value ? value : "";
} }
} }
@ -150,22 +150,22 @@ class ContactEdit extends React.PureComponent<PropsType> {
constructor(props: any) { constructor(props: any) {
super(props); super(props);
this.state = { this.state = {
uid: '', uid: "",
fn: '', fn: "",
lastName: '', lastName: "",
firstName: '', firstName: "",
middleName: '', middleName: "",
namePrefix: '', namePrefix: "",
nameSuffix: '', nameSuffix: "",
phone: [new ValueType()], phone: [new ValueType()],
email: [new ValueType()], email: [new ValueType()],
address: [new ValueType()], address: [new ValueType()],
impp: [new ValueType('jabber')], impp: [new ValueType("jabber")],
org: '', org: "",
note: '', note: "",
title: '', title: "",
journalUid: '', journalUid: "",
showDeleteDialog: false, showDeleteDialog: false,
}; };
@ -173,7 +173,7 @@ class ContactEdit extends React.PureComponent<PropsType> {
const contact = this.props.item; const contact = this.props.item;
this.state.uid = contact.uid; this.state.uid = contact.uid;
this.state.fn = contact.fn ? contact.fn : ''; this.state.fn = contact.fn ? contact.fn : "";
if (contact.n) { if (contact.n) {
this.state.lastName = contact.n[0]; this.state.lastName = contact.n[0];
this.state.firstName = contact.n[1]; this.state.firstName = contact.n[1];
@ -181,18 +181,18 @@ class ContactEdit extends React.PureComponent<PropsType> {
this.state.namePrefix = contact.n[3]; this.state.namePrefix = contact.n[3];
this.state.nameSuffix = contact.n[4]; this.state.nameSuffix = contact.n[4];
} else { } else {
let name = this.state.fn.trim().split(','); let name = this.state.fn.trim().split(",");
if (name.length > 2 && name[0] !== '' && name[name.length - 1] !== '') { if (name.length > 2 && name[0] !== "" && name[name.length - 1] !== "") {
this.state.nameSuffix = name.pop() || ''; this.state.nameSuffix = name.pop() || "";
} }
name = name.join(',').split(' '); name = name.join(",").split(" ");
if (name.length === 1) { if (name.length === 1) {
this.state.firstName = name[0]; this.state.firstName = name[0];
} else if (name.length === 2) { } else if (name.length === 2) {
this.state.firstName = name[0]; this.state.firstName = name[0];
this.state.lastName = name[1]; this.state.lastName = name[1];
} else if (name.length > 2) { } else if (name.length > 2) {
this.state.firstName = name.slice(0, name.length - 2).join(' '); this.state.firstName = name.slice(0, name.length - 2).join(" ");
this.state.middleName = name[name.length - 2]; this.state.middleName = name[name.length - 2];
this.state.lastName = name[name.length - 1]; this.state.lastName = name[name.length - 1];
} }
@ -208,19 +208,19 @@ class ContactEdit extends React.PureComponent<PropsType> {
)) ))
); );
this.state.phone = propToValueType(contact.comp, 'tel'); this.state.phone = propToValueType(contact.comp, "tel");
this.state.email = propToValueType(contact.comp, 'email'); this.state.email = propToValueType(contact.comp, "email");
this.state.address = propToValueType(contact.comp, 'adr'); this.state.address = propToValueType(contact.comp, "adr");
this.state.impp = propToValueType(contact.comp, 'impp'); this.state.impp = propToValueType(contact.comp, "impp");
const propToStringType = (comp: ICAL.Component, propName: string) => { const propToStringType = (comp: ICAL.Component, propName: string) => {
const val = comp.getFirstPropertyValue(propName); const val = comp.getFirstPropertyValue(propName);
return val ? val : ''; return val ? val : "";
}; };
this.state.org = propToStringType(contact.comp, 'org'); this.state.org = propToStringType(contact.comp, "org");
this.state.title = propToStringType(contact.comp, 'title'); this.state.title = propToStringType(contact.comp, "title");
this.state.note = propToStringType(contact.comp, 'note'); this.state.note = propToStringType(contact.comp, "note");
} else { } else {
this.state.uid = uuid.v4(); this.state.uid = uuid.v4();
@ -241,7 +241,7 @@ class ContactEdit extends React.PureComponent<PropsType> {
} }
public addValueType(name: string, _type?: string) { public addValueType(name: string, _type?: string) {
const type = _type ? _type : 'home'; const type = _type ? _type : "home";
this.setState((prevState) => { this.setState((prevState) => {
const newArray = prevState[name].slice(0); const newArray = prevState[name].slice(0);
newArray.push(new ValueType(type)); newArray.push(new ValueType(type));
@ -293,14 +293,14 @@ class ContactEdit extends React.PureComponent<PropsType> {
const contact = (this.props.item) ? const contact = (this.props.item) ?
this.props.item.clone() this.props.item.clone()
: :
new ContactType(new ICAL.Component(['vcard', [], []])) new ContactType(new ICAL.Component(["vcard", [], []]))
; ;
const comp = contact.comp; const comp = contact.comp;
comp.updatePropertyWithValue('prodid', '-//iCal.js EteSync Web'); comp.updatePropertyWithValue("prodid", "-//iCal.js EteSync Web");
comp.updatePropertyWithValue('version', '4.0'); comp.updatePropertyWithValue("version", "4.0");
comp.updatePropertyWithValue('uid', this.state.uid); comp.updatePropertyWithValue("uid", this.state.uid);
comp.updatePropertyWithValue('rev', ICAL.Time.now()); comp.updatePropertyWithValue("rev", ICAL.Time.now());
const lastName = this.state.lastName.trim(); const lastName = this.state.lastName.trim();
const firstName = this.state.firstName.trim(); const firstName = this.state.firstName.trim();
@ -310,13 +310,13 @@ class ContactEdit extends React.PureComponent<PropsType> {
let fn = `${namePrefix} ${firstName} ${middleName} ${lastName}`.trim(); let fn = `${namePrefix} ${firstName} ${middleName} ${lastName}`.trim();
if (fn === '') { if (fn === "") {
fn = nameSuffix; fn = nameSuffix;
} else if (nameSuffix !== '') { } else if (nameSuffix !== "") {
fn = `${fn}, ${nameSuffix}`; fn = `${fn}, ${nameSuffix}`;
} }
comp.updatePropertyWithValue('fn', fn); comp.updatePropertyWithValue("fn", fn);
const name = [lastName, const name = [lastName,
firstName, firstName,
@ -325,39 +325,39 @@ class ContactEdit extends React.PureComponent<PropsType> {
nameSuffix, nameSuffix,
]; ];
comp.updatePropertyWithValue('n', name); comp.updatePropertyWithValue("n", name);
function setProperties(name: string, source: ValueType[]) { function setProperties(name: string, source: ValueType[]) {
comp.removeAllProperties(name); comp.removeAllProperties(name);
source.forEach((x) => { source.forEach((x) => {
if (x.value === '') { if (x.value === "") {
return; return;
} }
const prop = new ICAL.Property(name, comp); const prop = new ICAL.Property(name, comp);
prop.setParameter('type', x.type); prop.setParameter("type", x.type);
prop.setValue(x.value); prop.setValue(x.value);
comp.addProperty(prop); comp.addProperty(prop);
}); });
} }
setProperties('tel', this.state.phone); setProperties("tel", this.state.phone);
setProperties('email', this.state.email); setProperties("email", this.state.email);
setProperties('adr', this.state.address); setProperties("adr", this.state.address);
setProperties('impp', this.state.impp.map((x) => ( setProperties("impp", this.state.impp.map((x) => (
{ type: x.type, value: x.type + ':' + x.value } { type: x.type, value: x.type + ":" + x.value }
))); )));
function setProperty(name: string, value: string) { function setProperty(name: string, value: string) {
comp.removeAllProperties(name); comp.removeAllProperties(name);
if (value !== '') { if (value !== "") {
comp.updatePropertyWithValue(name, value); comp.updatePropertyWithValue(name, value);
} }
} }
setProperty('org', this.state.org); setProperty("org", this.state.org);
setProperty('title', this.state.title); setProperty("title", this.state.title);
setProperty('note', this.state.note); setProperty("note", this.state.note);
this.props.onSave(contact, this.state.journalUid, this.props.item) this.props.onSave(contact, this.state.journalUid, this.props.item)
@ -377,20 +377,20 @@ class ContactEdit extends React.PureComponent<PropsType> {
form: { form: {
}, },
fullWidth: { fullWidth: {
width: '100%', width: "100%",
boxSizing: 'border-box' as any, boxSizing: "border-box" as any,
}, },
submit: { submit: {
marginTop: 40, marginTop: 40,
marginBottom: 20, marginBottom: 20,
textAlign: 'right' as any, textAlign: "right" as any,
}, },
}; };
return ( return (
<React.Fragment> <React.Fragment>
<h2> <h2>
{this.props.item ? 'Edit Contact' : 'New Contact'} {this.props.item ? "Edit Contact" : "New Contact"}
</h2> </h2>
<form style={styles.form} onSubmit={this.onSubmit}> <form style={styles.form} onSubmit={this.onSubmit}>
<FormControl disabled={this.props.item !== undefined} style={styles.fullWidth}> <FormControl disabled={this.props.item !== undefined} style={styles.fullWidth}>
@ -411,7 +411,7 @@ class ContactEdit extends React.PureComponent<PropsType> {
<TextField <TextField
name="namePrefix" name="namePrefix"
placeholder="Prefix" placeholder="Prefix"
style={{ marginTop: '2rem', ...styles.fullWidth }} style={{ marginTop: "2rem", ...styles.fullWidth }}
value={this.state.namePrefix} value={this.state.namePrefix}
onChange={this.handleInputChange} onChange={this.handleInputChange}
/> />
@ -419,7 +419,7 @@ class ContactEdit extends React.PureComponent<PropsType> {
<TextField <TextField
name="firstName" name="firstName"
placeholder="First name" placeholder="First name"
style={{ marginTop: '2rem', ...styles.fullWidth }} style={{ marginTop: "2rem", ...styles.fullWidth }}
value={this.state.firstName} value={this.state.firstName}
onChange={this.handleInputChange} onChange={this.handleInputChange}
/> />
@ -427,7 +427,7 @@ class ContactEdit extends React.PureComponent<PropsType> {
<TextField <TextField
name="middleName" name="middleName"
placeholder="Middle name" placeholder="Middle name"
style={{ marginTop: '2rem', ...styles.fullWidth }} style={{ marginTop: "2rem", ...styles.fullWidth }}
value={this.state.middleName} value={this.state.middleName}
onChange={this.handleInputChange} onChange={this.handleInputChange}
/> />
@ -435,7 +435,7 @@ class ContactEdit extends React.PureComponent<PropsType> {
<TextField <TextField
name="lastName" name="lastName"
placeholder="Last name" placeholder="Last name"
style={{ marginTop: '2rem', ...styles.fullWidth }} style={{ marginTop: "2rem", ...styles.fullWidth }}
value={this.state.lastName} value={this.state.lastName}
onChange={this.handleInputChange} onChange={this.handleInputChange}
/> />
@ -443,7 +443,7 @@ class ContactEdit extends React.PureComponent<PropsType> {
<TextField <TextField
name="nameSuffix" name="nameSuffix"
placeholder="Suffix" placeholder="Suffix"
style={{ marginTop: '2rem', ...styles.fullWidth }} style={{ marginTop: "2rem", ...styles.fullWidth }}
value={this.state.nameSuffix} value={this.state.nameSuffix}
onChange={this.handleInputChange} onChange={this.handleInputChange}
/> />
@ -451,7 +451,7 @@ class ContactEdit extends React.PureComponent<PropsType> {
<div> <div>
Phone numbers Phone numbers
<IconButton <IconButton
onClick={() => this.addValueType('phone')} onClick={() => this.addValueType("phone")}
title="Add phone number" title="Add phone number"
> >
<IconAdd /> <IconAdd />
@ -474,7 +474,7 @@ class ContactEdit extends React.PureComponent<PropsType> {
<div> <div>
Emails Emails
<IconButton <IconButton
onClick={() => this.addValueType('email')} onClick={() => this.addValueType("email")}
title="Add email address" title="Add email address"
> >
<IconAdd /> <IconAdd />
@ -497,7 +497,7 @@ class ContactEdit extends React.PureComponent<PropsType> {
<div> <div>
IMPP IMPP
<IconButton <IconButton
onClick={() => this.addValueType('impp', 'jabber')} onClick={() => this.addValueType("impp", "jabber")}
title="Add impp address" title="Add impp address"
> >
<IconAdd /> <IconAdd />
@ -520,7 +520,7 @@ class ContactEdit extends React.PureComponent<PropsType> {
<div> <div>
Addresses Addresses
<IconButton <IconButton
onClick={() => this.addValueType('address')} onClick={() => this.addValueType("address")}
title="Add address" title="Add address"
> >
<IconAdd /> <IconAdd />
@ -578,7 +578,7 @@ class ContactEdit extends React.PureComponent<PropsType> {
{this.props.item && {this.props.item &&
<Button <Button
variant="contained" variant="contained"
style={{ marginLeft: 15, backgroundColor: colors.red[500], color: 'white' }} style={{ marginLeft: 15, backgroundColor: colors.red[500], color: "white" }}
onClick={this.onDeleteRequest} onClick={this.onDeleteRequest}
> >
<IconDelete style={{ marginRight: 8 }} /> <IconDelete style={{ marginRight: 8 }} />

@ -1,9 +1,9 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import Button from '@material-ui/core/Button'; import Button from "@material-ui/core/Button";
import TextField from '@material-ui/core/TextField'; import TextField from "@material-ui/core/TextField";
interface FormErrors { interface FormErrors {
errorEncryptionPassword?: string; errorEncryptionPassword?: string;
@ -25,7 +25,7 @@ class EncryptionLoginForm extends React.PureComponent {
super(props); super(props);
this.state = { this.state = {
errors: {}, errors: {},
encryptionPassword: '', encryptionPassword: "",
}; };
this.generateEncryption = this.generateEncryption.bind(this); this.generateEncryption = this.generateEncryption.bind(this);
this.handleInputChange = this.handleInputChange.bind(this); this.handleInputChange = this.handleInputChange.bind(this);
@ -45,7 +45,7 @@ class EncryptionLoginForm extends React.PureComponent {
const encryptionPassword = this.state.encryptionPassword; const encryptionPassword = this.state.encryptionPassword;
const errors: FormErrors = {}; const errors: FormErrors = {};
const fieldRequired = 'This field is required!'; const fieldRequired = "This field is required!";
if (!encryptionPassword) { if (!encryptionPassword) {
errors.errorEncryptionPassword = fieldRequired; errors.errorEncryptionPassword = fieldRequired;
} }
@ -66,7 +66,7 @@ class EncryptionLoginForm extends React.PureComponent {
}, },
submit: { submit: {
marginTop: 40, marginTop: 40,
textAlign: 'right' as any, textAlign: "right" as any,
}, },
}; };
@ -92,7 +92,7 @@ class EncryptionLoginForm extends React.PureComponent {
color="secondary" color="secondary"
disabled={this.props.loading} disabled={this.props.loading}
> >
{this.props.loading ? 'Loading…' : 'Continue'} {this.props.loading ? "Loading…" : "Continue"}
</Button> </Button>
</div> </div>
</form> </form>

@ -1,13 +1,13 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import { store, persistor } from '../store'; import { store, persistor } from "../store";
import { resetKey } from '../store/actions'; import { resetKey } from "../store/actions";
import { EncryptionPasswordError, IntegrityError } from 'etesync'; import { EncryptionPasswordError, IntegrityError } from "etesync";
import PrettyError from '../widgets/PrettyError'; import PrettyError from "../widgets/PrettyError";
interface PropsType { interface PropsType {
children: React.ReactNode | React.ReactNode[]; children: React.ReactNode | React.ReactNode[];

@ -1,13 +1,13 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import PimItemHeader from './PimItemHeader'; import PimItemHeader from "./PimItemHeader";
import { formatDateRange, formatOurTimezoneOffset } from '../helpers'; import { formatDateRange, formatOurTimezoneOffset } from "../helpers";
import { EventType } from '../pim-types'; import { EventType } from "../pim-types";
class Event extends React.PureComponent { class Event extends React.PureComponent {
public props: { public props: {
@ -16,7 +16,7 @@ class Event extends React.PureComponent {
public render() { public render() {
if (this.props.item === undefined) { if (this.props.item === undefined) {
throw Error('Event should be defined!'); throw Error("Event should be defined!");
} }
const style = { const style = {
@ -35,9 +35,9 @@ class Event extends React.PureComponent {
<div><u>{this.props.item.location}</u></div> <div><u>{this.props.item.location}</u></div>
</PimItemHeader> </PimItemHeader>
<div style={style.content}> <div style={style.content}>
<p style={{ wordWrap: 'break-word' }}>{this.props.item.description}</p> <p style={{ wordWrap: "break-word" }}>{this.props.item.description}</p>
{(this.props.item.attendees.length > 0) && ( {(this.props.item.attendees.length > 0) && (
<div>Attendees: {this.props.item.attendees.map((x) => (x.getFirstValue())).join(', ')}</div>)} <div>Attendees: {this.props.item.attendees.map((x) => (x.getFirstValue())).join(", ")}</div>)}
</div> </div>
</React.Fragment> </React.Fragment>
); );

@ -1,45 +1,45 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import FormGroup from '@material-ui/core/FormGroup'; import FormGroup from "@material-ui/core/FormGroup";
import FormControlLabel from '@material-ui/core/FormControlLabel'; import FormControlLabel from "@material-ui/core/FormControlLabel";
import Switch from '@material-ui/core/Switch'; import Switch from "@material-ui/core/Switch";
import Button from '@material-ui/core/Button'; import Button from "@material-ui/core/Button";
import TextField from '@material-ui/core/TextField'; import TextField from "@material-ui/core/TextField";
import Select from '@material-ui/core/Select'; import Select from "@material-ui/core/Select";
import MenuItem from '@material-ui/core/MenuItem'; import MenuItem from "@material-ui/core/MenuItem";
import FormControl from '@material-ui/core/FormControl'; import FormControl from "@material-ui/core/FormControl";
import FormHelperText from '@material-ui/core/FormHelperText'; import FormHelperText from "@material-ui/core/FormHelperText";
import InputLabel from '@material-ui/core/InputLabel'; import InputLabel from "@material-ui/core/InputLabel";
import * as colors from '@material-ui/core/colors'; import * as colors from "@material-ui/core/colors";
import IconDelete from '@material-ui/icons/Delete'; import IconDelete from "@material-ui/icons/Delete";
import IconCancel from '@material-ui/icons/Clear'; import IconCancel from "@material-ui/icons/Clear";
import IconSave from '@material-ui/icons/Save'; import IconSave from "@material-ui/icons/Save";
import DateTimePicker from '../widgets/DateTimePicker'; import DateTimePicker from "../widgets/DateTimePicker";
import ConfirmationDialog from '../widgets/ConfirmationDialog'; import ConfirmationDialog from "../widgets/ConfirmationDialog";
import TimezonePicker from '../widgets/TimezonePicker'; import TimezonePicker from "../widgets/TimezonePicker";
import Toast from '../widgets/Toast'; import Toast from "../widgets/Toast";
import { Location } from 'history'; import { Location } from "history";
import { withRouter } from 'react-router'; import { withRouter } from "react-router";
import * as uuid from 'uuid'; import * as uuid from "uuid";
import * as ICAL from 'ical.js'; import * as ICAL from "ical.js";
import * as EteSync from 'etesync'; import * as EteSync from "etesync";
import { getCurrentTimezone } from '../helpers'; import { getCurrentTimezone } from "../helpers";
import { EventType, timezoneLoadFromName } from '../pim-types'; import { EventType, timezoneLoadFromName } from "../pim-types";
import RRule, { RRuleOptions } from '../widgets/RRule'; import RRule, { RRuleOptions } from "../widgets/RRule";
import { History } from 'history'; import { History } from "history";
interface PropsType { interface PropsType {
collections: EteSync.CollectionInfo[]; collections: EteSync.CollectionInfo[];
@ -73,14 +73,14 @@ class EventEdit extends React.PureComponent<PropsType> {
constructor(props: any) { constructor(props: any) {
super(props); super(props);
this.state = { this.state = {
uid: '', uid: "",
title: '', title: "",
allDay: false, allDay: false,
location: '', location: "",
description: '', description: "",
timezone: null, timezone: null,
journalUid: '', journalUid: "",
showDeleteDialog: false, showDeleteDialog: false,
}; };
@ -107,18 +107,18 @@ class EventEdit extends React.PureComponent<PropsType> {
} }
if (this.props.duplicate) { if (this.props.duplicate) {
this.state.title = event.title ? `Copy of ${event.title}` : ''; this.state.title = event.title ? `Copy of ${event.title}` : "";
} else { } else {
this.state.uid = event.uid; this.state.uid = event.uid;
this.state.title = event.title ? event.title : ''; this.state.title = event.title ? event.title : "";
} }
this.state.allDay = allDay; this.state.allDay = allDay;
this.state.start = event.startDate.convertToZone(ICAL.Timezone.localTimezone).toJSDate(); this.state.start = event.startDate.convertToZone(ICAL.Timezone.localTimezone).toJSDate();
this.state.end = endDate.convertToZone(ICAL.Timezone.localTimezone).toJSDate(); this.state.end = endDate.convertToZone(ICAL.Timezone.localTimezone).toJSDate();
this.state.location = event.location ? event.location : ''; this.state.location = event.location ? event.location : "";
this.state.description = event.description ? event.description : ''; this.state.description = event.description ? event.description : "";
this.state.timezone = event.timezone; this.state.timezone = event.timezone;
const rruleProp = this.props.item?.component.getFirstPropertyValue<ICAL.Recur>('rrule'); const rruleProp = this.props.item?.component.getFirstPropertyValue<ICAL.Recur>("rrule");
if (rruleProp) { if (rruleProp) {
this.state.rrule = rruleProp.toJSON() as any; this.state.rrule = rruleProp.toJSON() as any;
if (this.state.rrule && rruleProp.until) { if (this.state.rrule && rruleProp.until) {
@ -165,7 +165,7 @@ class EventEdit extends React.PureComponent<PropsType> {
this.setState({ allDay: !this.state.allDay }); this.setState({ allDay: !this.state.allDay });
} }
public toggleRecurring() { public toggleRecurring() {
const value = this.state.rrule ? undefined : { freq: 'WEEKLY', interval: 1 }; const value = this.state.rrule ? undefined : { freq: "WEEKLY", interval: 1 };
this.setState({ rrule: value }); this.setState({ rrule: value });
} }
@ -175,18 +175,18 @@ class EventEdit extends React.PureComponent<PropsType> {
} }
public handleCloseToast(_event?: React.SyntheticEvent, reason?: string) { public handleCloseToast(_event?: React.SyntheticEvent, reason?: string) {
if (reason === 'clickaway') { if (reason === "clickaway") {
return; return;
} }
this.setState({ error: '' }); this.setState({ error: "" });
} }
public onSubmit(e: React.FormEvent<any>) { public onSubmit(e: React.FormEvent<any>) {
e.preventDefault(); e.preventDefault();
if ((!this.state.start) || (!this.state.end)) { if ((!this.state.start) || (!this.state.end)) {
this.setState({ error: 'Both start and end time must be set!' }); this.setState({ error: "Both start and end time must be set!" });
return; return;
} }
@ -209,7 +209,7 @@ class EventEdit extends React.PureComponent<PropsType> {
} }
if (startDate.compare(endDate) >= 0) { if (startDate.compare(endDate) >= 0) {
this.setState({ error: 'End time must be later than start time!' }); this.setState({ error: "End time must be later than start time!" });
return; return;
} }
@ -233,10 +233,10 @@ class EventEdit extends React.PureComponent<PropsType> {
} }
} }
if (this.state.rrule) { if (this.state.rrule) {
event.component.updatePropertyWithValue('rrule', new ICAL.Recur(this.state.rrule!)); event.component.updatePropertyWithValue("rrule", new ICAL.Recur(this.state.rrule!));
} }
event.component.updatePropertyWithValue('last-modified', ICAL.Time.now()); event.component.updatePropertyWithValue("last-modified", ICAL.Time.now());
this.props.onSave(event, this.state.journalUid, this.props.item) this.props.onSave(event, this.state.journalUid, this.props.item)
.then(() => { .then(() => {
@ -255,14 +255,14 @@ class EventEdit extends React.PureComponent<PropsType> {
form: { form: {
}, },
fullWidth: { fullWidth: {
width: '100%', width: "100%",
boxSizing: 'border-box' as any, boxSizing: "border-box" as any,
marginTop: 16, marginTop: 16,
}, },
submit: { submit: {
marginTop: 40, marginTop: 40,
marginBottom: 20, marginBottom: 20,
textAlign: 'right' as any, textAlign: "right" as any,
}, },
}; };
@ -272,11 +272,11 @@ class EventEdit extends React.PureComponent<PropsType> {
return ( return (
<> <>
<h2> <h2>
{(this.props.item && !this.props.duplicate) ? 'Edit Event' : 'New Event'} {(this.props.item && !this.props.duplicate) ? "Edit Event" : "New Event"}
</h2> </h2>
{recurring && ( {recurring && (
<div> <div>
<span style={{ color: 'red' }}>IMPORTANT: </span> <span style={{ color: "red" }}>IMPORTANT: </span>
This is a recurring event, for now, only editing the whole series This is a recurring event, for now, only editing the whole series
(by editing the first instance) is supported. (by editing the first instance) is supported.
</div> </div>
@ -385,7 +385,7 @@ class EventEdit extends React.PureComponent<PropsType> {
{this.state.rrule && {this.state.rrule &&
<RRule <RRule
onChange={this.handleRRuleChange} onChange={this.handleRRuleChange}
rrule={this.state.rrule ? this.state.rrule : { freq: 'DAILY', interval: 1 }} rrule={this.state.rrule ? this.state.rrule : { freq: "DAILY", interval: 1 }}
/> />
} }
<div style={styles.submit}> <div style={styles.submit}>
@ -400,7 +400,7 @@ class EventEdit extends React.PureComponent<PropsType> {
{this.props.item && {this.props.item &&
<Button <Button
variant="contained" variant="contained"
style={{ marginLeft: 15, backgroundColor: colors.red[500], color: 'white' }} style={{ marginLeft: 15, backgroundColor: colors.red[500], color: "white" }}
onClick={this.onDeleteRequest} onClick={this.onDeleteRequest}
> >
<IconDelete style={{ marginRight: 8 }} /> <IconDelete style={{ marginRight: 8 }} />

@ -1,31 +1,31 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as Immutable from 'immutable'; import * as Immutable from "immutable";
import { AutoSizer, List as VirtualizedList } from 'react-virtualized'; import { AutoSizer, List as VirtualizedList } from "react-virtualized";
import * as React from 'react'; import * as React from "react";
import { List, ListItem } from '../widgets/List'; import { List, ListItem } from "../widgets/List";
import Dialog from '@material-ui/core/Dialog'; import Dialog from "@material-ui/core/Dialog";
import DialogTitle from '@material-ui/core/DialogTitle'; import DialogTitle from "@material-ui/core/DialogTitle";
import DialogContent from '@material-ui/core/DialogContent'; import DialogContent from "@material-ui/core/DialogContent";
import DialogActions from '@material-ui/core/DialogActions'; import DialogActions from "@material-ui/core/DialogActions";
import Button from '@material-ui/core/Button'; import Button from "@material-ui/core/Button";
import IconAdd from '@material-ui/icons/Add'; import IconAdd from "@material-ui/icons/Add";
import IconDelete from '@material-ui/icons/Delete'; import IconDelete from "@material-ui/icons/Delete";
import IconEdit from '@material-ui/icons/Edit'; import IconEdit from "@material-ui/icons/Edit";
import IconError from '@material-ui/icons/Error'; import IconError from "@material-ui/icons/Error";
import { TaskType, EventType, ContactType, parseString } from '../pim-types'; import { TaskType, EventType, ContactType, parseString } from "../pim-types";
import * as EteSync from 'etesync'; import * as EteSync from "etesync";
import LoadingIndicator from '../widgets/LoadingIndicator'; import LoadingIndicator from "../widgets/LoadingIndicator";
import { useCredentials } from '../login'; import { useCredentials } from "../login";
import { createJournalEntry } from '../etesync-helpers'; import { createJournalEntry } from "../etesync-helpers";
import { useSelector, useDispatch } from 'react-redux'; import { useSelector, useDispatch } from "react-redux";
import { StoreState } from '../store'; import { StoreState } from "../store";
import { addEntries } from '../store/actions'; import { addEntries } from "../store/actions";
interface RollbackToHereDialogPropsType { interface RollbackToHereDialogPropsType {
journal: EteSync.Journal; journal: EteSync.Journal;
@ -48,8 +48,8 @@ function RollbackToHereDialog(props: RollbackToHereDialogPropsType) {
for (const entry of props.entries.reverse()) { for (const entry of props.entries.reverse()) {
const comp = parseString(entry.content); const comp = parseString(entry.content);
const itemComp = comp.getFirstSubcomponent('vevent') ?? comp.getFirstSubcomponent('vtodo') ?? comp; const itemComp = comp.getFirstSubcomponent("vevent") ?? comp.getFirstSubcomponent("vtodo") ?? comp;
const itemUid = itemComp.getFirstPropertyValue('uid'); const itemUid = itemComp.getFirstPropertyValue("uid");
if (itemUid && !changes.has(itemUid)) { if (itemUid && !changes.has(itemUid)) {
changes.set(itemUid, entry); changes.set(itemUid, entry);
@ -94,7 +94,7 @@ function RollbackToHereDialog(props: RollbackToHereDialogPropsType) {
</DialogTitle> </DialogTitle>
<DialogContent> <DialogContent>
{loading ? ( {loading ? (
<LoadingIndicator style={{ display: 'block', margin: 'auto' }} /> <LoadingIndicator style={{ display: "block", margin: "auto" }} />
) : ( ) : (
<p> <p>
This function restores all of the deleted items that happened after this change entry. It will not modify any items that haven't been changed since the item was deleted. This function restores all of the deleted items that happened after this change entry. It will not modify any items that haven't been changed since the item was deleted.
@ -154,7 +154,7 @@ class JournalEntries extends React.PureComponent {
try { try {
comp = parseString(syncEntry.content); comp = parseString(syncEntry.content);
} catch (e) { } catch (e) {
const icon = (<IconError style={{ color: 'red' }} />); const icon = (<IconError style={{ color: "red" }} />);
return ( return (
<ListItem <ListItem
key={key} key={key}
@ -173,16 +173,16 @@ class JournalEntries extends React.PureComponent {
let icon; let icon;
if (syncEntry.action === EteSync.SyncEntryAction.Add) { if (syncEntry.action === EteSync.SyncEntryAction.Add) {
icon = (<IconAdd style={{ color: '#16B14B' }} />); icon = (<IconAdd style={{ color: "#16B14B" }} />);
} else if (syncEntry.action === EteSync.SyncEntryAction.Change) { } else if (syncEntry.action === EteSync.SyncEntryAction.Change) {
icon = (<IconEdit style={{ color: '#FEB115' }} />); icon = (<IconEdit style={{ color: "#FEB115" }} />);
} else if (syncEntry.action === EteSync.SyncEntryAction.Delete) { } else if (syncEntry.action === EteSync.SyncEntryAction.Delete) {
icon = (<IconDelete style={{ color: '#F20C0C' }} />); icon = (<IconDelete style={{ color: "#F20C0C" }} />);
} }
let name; let name;
let uid; let uid;
if (comp.name === 'vcalendar') { if (comp.name === "vcalendar") {
if (EventType.isEvent(comp)) { if (EventType.isEvent(comp)) {
const vevent = EventType.fromVCalendar(comp); const vevent = EventType.fromVCalendar(comp);
name = vevent.summary; name = vevent.summary;
@ -192,13 +192,13 @@ class JournalEntries extends React.PureComponent {
name = vtodo.summary; name = vtodo.summary;
uid = vtodo.uid; uid = vtodo.uid;
} }
} else if (comp.name === 'vcard') { } else if (comp.name === "vcard") {
const vcard = new ContactType(comp); const vcard = new ContactType(comp);
name = vcard.fn; name = vcard.fn;
uid = vcard.uid; uid = vcard.uid;
} else { } else {
name = 'Error processing entry'; name = "Error processing entry";
uid = ''; uid = "";
} }
if (this.props.uid && (this.props.uid !== uid)) { if (this.props.uid && (this.props.uid !== uid)) {
@ -265,7 +265,7 @@ class JournalEntries extends React.PureComponent {
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
<List style={{ height: 'calc(100vh - 300px)' }}> <List style={{ height: "calc(100vh - 300px)" }}>
<AutoSizer> <AutoSizer>
{({ height, width }) => ( {({ height, width }) => (
<VirtualizedList <VirtualizedList

@ -1,16 +1,16 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import Button from '@material-ui/core/Button'; import Button from "@material-ui/core/Button";
import TextField from '@material-ui/core/TextField'; import TextField from "@material-ui/core/TextField";
import FormGroup from '@material-ui/core/FormGroup'; import FormGroup from "@material-ui/core/FormGroup";
import FormControlLabel from '@material-ui/core/FormControlLabel'; import FormControlLabel from "@material-ui/core/FormControlLabel";
import Switch from '@material-ui/core/Switch'; import Switch from "@material-ui/core/Switch";
import ExternalLink from '../widgets/ExternalLink'; import ExternalLink from "../widgets/ExternalLink";
import * as C from '../constants'; import * as C from "../constants";
interface FormErrors { interface FormErrors {
errorEmail?: string; errorEmail?: string;
@ -40,9 +40,9 @@ class LoginForm extends React.PureComponent {
this.state = { this.state = {
showAdvanced: false, showAdvanced: false,
errors: {}, errors: {},
server: '', server: "",
username: '', username: "",
password: '', password: "",
}; };
this.generateEncryption = this.generateEncryption.bind(this); this.generateEncryption = this.generateEncryption.bind(this);
this.toggleAdvancedSettings = this.toggleAdvancedSettings.bind(this); this.toggleAdvancedSettings = this.toggleAdvancedSettings.bind(this);
@ -65,7 +65,7 @@ class LoginForm extends React.PureComponent {
const password = this.state.password; const password = this.state.password;
const errors: FormErrors = {}; const errors: FormErrors = {};
const fieldRequired = 'This field is required!'; const fieldRequired = "This field is required!";
if (!username) { if (!username) {
errors.errorEmail = fieldRequired; errors.errorEmail = fieldRequired;
} }
@ -73,9 +73,9 @@ class LoginForm extends React.PureComponent {
errors.errorPassword = fieldRequired; errors.errorPassword = fieldRequired;
} }
if (process.env.NODE_ENV !== 'development') { if (process.env.NODE_ENV !== "development") {
if (this.state.showAdvanced && !this.state.server.startsWith('https://')) { if (this.state.showAdvanced && !this.state.server.startsWith("https://")) {
errors.errorServer = 'Server URI must start with https://'; errors.errorServer = "Server URI must start with https://";
} }
} }
@ -105,7 +105,7 @@ class LoginForm extends React.PureComponent {
}, },
submit: { submit: {
marginTop: 40, marginTop: 40,
textAlign: 'right' as any, textAlign: "right" as any,
}, },
}; };
@ -133,7 +133,7 @@ class LoginForm extends React.PureComponent {
{(this.props.error) && (<div>Error! {this.props.error.message}</div>)} {(this.props.error) && (<div>Error! {this.props.error.message}</div>)}
<form style={styles.form} onSubmit={this.generateEncryption}> <form style={styles.form} onSubmit={this.generateEncryption}>
<TextField <TextField
type={this.state.showAdvanced ? 'text' : 'email'} type={this.state.showAdvanced ? "text" : "email"}
style={styles.textField} style={styles.textField}
error={!!this.state.errors.errorEmail} error={!!this.state.errors.errorEmail}
helperText={this.state.errors.errorEmail} helperText={this.state.errors.errorEmail}
@ -177,7 +177,7 @@ class LoginForm extends React.PureComponent {
color="secondary" color="secondary"
disabled={this.props.loading} disabled={this.props.loading}
> >
{this.props.loading ? 'Loading…' : 'Log In'} {this.props.loading ? "Loading…" : "Log In"}
</Button> </Button>
</div> </div>
</form> </form>

@ -1,10 +1,10 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import Color from 'color'; import Color from "color";
import { Theme, withTheme } from '@material-ui/core/styles'; import { Theme, withTheme } from "@material-ui/core/styles";
export default withTheme((props: {text: string, backgroundColor?: string, children?: any, rightItem?: React.ReactNode, theme: Theme}) => { export default withTheme((props: {text: string, backgroundColor?: string, children?: any, rightItem?: React.ReactNode, theme: Theme}) => {
const backgroundColor = props.backgroundColor ?? props.theme.palette.secondary.main; const backgroundColor = props.backgroundColor ?? props.theme.palette.secondary.main;
@ -14,8 +14,8 @@ export default withTheme((props: {text: string, backgroundColor?: string, childr
backgroundColor, backgroundColor,
color: foregroundColor, color: foregroundColor,
padding: 15, padding: 15,
display: 'flex', display: "flex",
justifyContent: 'space-between', justifyContent: "space-between",
}, },
headerText: { headerText: {
marginTop: 10, marginTop: 10,

@ -1,17 +1,17 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import TextField from '@material-ui/core/TextField'; import TextField from "@material-ui/core/TextField";
import IconButton from '@material-ui/core/IconButton'; import IconButton from "@material-ui/core/IconButton";
import IconSearch from '@material-ui/icons/Search'; import IconSearch from "@material-ui/icons/Search";
import IconClear from '@material-ui/icons/Clear'; import IconClear from "@material-ui/icons/Clear";
import { ContactType } from '../pim-types'; import { ContactType } from "../pim-types";
import AddressBook from '../components/AddressBook'; import AddressBook from "../components/AddressBook";
class SearchableAddressBook extends React.PureComponent { class SearchableAddressBook extends React.PureComponent {
public props: { public props: {
@ -25,7 +25,7 @@ class SearchableAddressBook extends React.PureComponent {
constructor(props: any) { constructor(props: any) {
super(props); super(props);
this.state = { searchQuery: '' }; this.state = { searchQuery: "" };
this.handleInputChange = this.handleInputChange.bind(this); this.handleInputChange = this.handleInputChange.bind(this);
} }
@ -43,19 +43,19 @@ class SearchableAddressBook extends React.PureComponent {
...rest ...rest
} = this.props; } = this.props;
const reg = new RegExp(this.state.searchQuery, 'i'); const reg = new RegExp(this.state.searchQuery, "i");
return ( return (
<React.Fragment> <React.Fragment>
<TextField <TextField
name="searchQuery" name="searchQuery"
value={this.state.searchQuery} value={this.state.searchQuery}
style={{ fontSize: '120%', marginLeft: 20 }} style={{ fontSize: "120%", marginLeft: 20 }}
placeholder="Find Contacts" placeholder="Find Contacts"
onChange={this.handleInputChange} onChange={this.handleInputChange}
/> />
{this.state.searchQuery && {this.state.searchQuery &&
<IconButton onClick={() => this.setState({ searchQuery: '' })}> <IconButton onClick={() => this.setState({ searchQuery: "" })}>
<IconClear /> <IconClear />
</IconButton> </IconButton>
} }

@ -1,17 +1,17 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import * as EteSync from 'etesync'; import * as EteSync from "etesync";
import ICAL from 'ical.js'; import ICAL from "ical.js";
import uuid from 'uuid'; import uuid from "uuid";
import TextField from '@material-ui/core/TextField'; import TextField from "@material-ui/core/TextField";
import { TaskType, PimType, TaskStatusType } from '../../pim-types'; import { TaskType, PimType, TaskStatusType } from "../../pim-types";
interface PropsType { interface PropsType {
style: React.CSSProperties; style: React.CSSProperties;
@ -20,7 +20,7 @@ interface PropsType {
} }
function QuickAdd(props: PropsType) { function QuickAdd(props: PropsType) {
const [title, setTitle] = React.useState(''); const [title, setTitle] = React.useState("");
const { style, onSubmit: save, defaultCollection } = props; const { style, onSubmit: save, defaultCollection } = props;
@ -39,7 +39,7 @@ function QuickAdd(props: PropsType) {
save(task, defaultCollection.uid, undefined); save(task, defaultCollection.uid, undefined);
setTitle(''); setTitle("");
} }

@ -1,16 +1,16 @@
import * as React from 'react'; import * as React from "react";
import { useSelector, useDispatch } from 'react-redux'; import { useSelector, useDispatch } from "react-redux";
import InboxIcon from '@material-ui/icons/Inbox'; import InboxIcon from "@material-ui/icons/Inbox";
import LabelIcon from '@material-ui/icons/LabelOutlined'; import LabelIcon from "@material-ui/icons/LabelOutlined";
import TodayIcon from '@material-ui/icons/Today'; import TodayIcon from "@material-ui/icons/Today";
import { setSettings } from '../../store/actions'; import { setSettings } from "../../store/actions";
import { StoreState } from '../../store'; import { StoreState } from "../../store";
import { List, ListItem, ListSubheader } from '../../widgets/List'; import { List, ListItem, ListSubheader } from "../../widgets/List";
import { TaskType } from '../../pim-types'; import { TaskType } from "../../pim-types";
interface ListItemPropsType { interface ListItemPropsType {
name: string | null; name: string | null;
@ -34,7 +34,7 @@ function SidebarListItem(props: ListItemPropsType) {
onClick={handleClick} onClick={handleClick}
selected={name === filterBy} selected={name === filterBy}
leftIcon={icon} leftIcon={icon}
rightIcon={<span style={{ width: '100%', textAlign: 'right' }}>{(amount > 0) && amount}</span>} rightIcon={<span style={{ width: "100%", textAlign: "right" }}>{(amount > 0) && amount}</span>}
primaryText={primaryText} primaryText={primaryText}
/> />
); );

@ -1,13 +1,13 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import PimItemHeader from '../PimItemHeader'; import PimItemHeader from "../PimItemHeader";
import { formatDate, formatOurTimezoneOffset } from '../../helpers'; import { formatDate, formatOurTimezoneOffset } from "../../helpers";
import { TaskType } from '../../pim-types'; import { TaskType } from "../../pim-types";
class Task extends React.PureComponent { class Task extends React.PureComponent {
public props: { public props: {
@ -16,7 +16,7 @@ class Task extends React.PureComponent {
public render() { public render() {
if (this.props.item === undefined) { if (this.props.item === undefined) {
throw Error('Task should be defined!'); throw Error("Task should be defined!");
} }
const { item } = this.props; const { item } = this.props;
@ -42,9 +42,9 @@ class Task extends React.PureComponent {
<div><u>{this.props.item.location}</u></div> <div><u>{this.props.item.location}</u></div>
</PimItemHeader> </PimItemHeader>
<div style={style.content}> <div style={style.content}>
<p style={{ wordWrap: 'break-word' }}>{this.props.item.description}</p> <p style={{ wordWrap: "break-word" }}>{this.props.item.description}</p>
{(this.props.item.attendees.length > 0) && ( {(this.props.item.attendees.length > 0) && (
<div>Attendees: {this.props.item.attendees.map((x) => (x.getFirstValue())).join(', ')}</div>)} <div>Attendees: {this.props.item.attendees.map((x) => (x.getFirstValue())).join(", ")}</div>)}
</div> </div>
</React.Fragment> </React.Fragment>
); );

@ -1,51 +1,51 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import FormGroup from '@material-ui/core/FormGroup'; import FormGroup from "@material-ui/core/FormGroup";
import FormControlLabel from '@material-ui/core/FormControlLabel'; import FormControlLabel from "@material-ui/core/FormControlLabel";
import Switch from '@material-ui/core/Switch'; import Switch from "@material-ui/core/Switch";
import Button from '@material-ui/core/Button'; import Button from "@material-ui/core/Button";
import TextField from '@material-ui/core/TextField'; import TextField from "@material-ui/core/TextField";
import Select from '@material-ui/core/Select'; import Select from "@material-ui/core/Select";
import MenuItem from '@material-ui/core/MenuItem'; import MenuItem from "@material-ui/core/MenuItem";
import FormControl from '@material-ui/core/FormControl'; import FormControl from "@material-ui/core/FormControl";
import FormHelperText from '@material-ui/core/FormHelperText'; import FormHelperText from "@material-ui/core/FormHelperText";
import InputLabel from '@material-ui/core/InputLabel'; import InputLabel from "@material-ui/core/InputLabel";
import * as colors from '@material-ui/core/colors'; import * as colors from "@material-ui/core/colors";
import FormLabel from '@material-ui/core/FormLabel'; import FormLabel from "@material-ui/core/FormLabel";
import RadioGroup from '@material-ui/core/RadioGroup'; import RadioGroup from "@material-ui/core/RadioGroup";
import Autocomplete from '@material-ui/lab/Autocomplete'; import Autocomplete from "@material-ui/lab/Autocomplete";
import IconDelete from '@material-ui/icons/Delete'; import IconDelete from "@material-ui/icons/Delete";
import IconCancel from '@material-ui/icons/Clear'; import IconCancel from "@material-ui/icons/Clear";
import IconSave from '@material-ui/icons/Save'; import IconSave from "@material-ui/icons/Save";
import DateTimePicker from '../../widgets/DateTimePicker'; import DateTimePicker from "../../widgets/DateTimePicker";
import ConfirmationDialog from '../../widgets/ConfirmationDialog'; import ConfirmationDialog from "../../widgets/ConfirmationDialog";
import TimezonePicker from '../../widgets/TimezonePicker'; import TimezonePicker from "../../widgets/TimezonePicker";
import Toast from '../../widgets/Toast'; import Toast from "../../widgets/Toast";
import { Location } from 'history'; import { Location } from "history";
import { withRouter } from 'react-router'; import { withRouter } from "react-router";
import * as uuid from 'uuid'; import * as uuid from "uuid";
import * as ICAL from 'ical.js'; import * as ICAL from "ical.js";
import * as EteSync from 'etesync'; import * as EteSync from "etesync";
import { getCurrentTimezone, mapPriority } from '../../helpers'; import { getCurrentTimezone, mapPriority } from "../../helpers";
import { TaskType, TaskStatusType, timezoneLoadFromName, TaskPriorityType, TaskTags } from '../../pim-types'; import { TaskType, TaskStatusType, timezoneLoadFromName, TaskPriorityType, TaskTags } from "../../pim-types";
import { History } from 'history'; import { History } from "history";
import ColoredRadio from '../../widgets/ColoredRadio'; import ColoredRadio from "../../widgets/ColoredRadio";
import RRule, { RRuleOptions } from '../../widgets/RRule'; import RRule, { RRuleOptions } from "../../widgets/RRule";
interface PropsType { interface PropsType {
collections: EteSync.CollectionInfo[]; collections: EteSync.CollectionInfo[];
@ -81,17 +81,17 @@ class TaskEdit extends React.PureComponent<PropsType> {
constructor(props: any) { constructor(props: any) {
super(props); super(props);
this.state = { this.state = {
uid: '', uid: "",
title: '', title: "",
status: TaskStatusType.NeedsAction, status: TaskStatusType.NeedsAction,
priority: TaskPriorityType.Undefined, priority: TaskPriorityType.Undefined,
includeTime: false, includeTime: false,
location: '', location: "",
description: '', description: "",
tags: [], tags: [],
timezone: null, timezone: null,
journalUid: '', journalUid: "",
showDeleteDialog: false, showDeleteDialog: false,
}; };
@ -99,7 +99,7 @@ class TaskEdit extends React.PureComponent<PropsType> {
const task = this.props.item; const task = this.props.item;
this.state.uid = task.uid; this.state.uid = task.uid;
this.state.title = task.title ? task.title : ''; this.state.title = task.title ? task.title : "";
this.state.status = task.status ?? TaskStatusType.NeedsAction; this.state.status = task.status ?? TaskStatusType.NeedsAction;
this.state.priority = task.priority ?? TaskPriorityType.Undefined; this.state.priority = task.priority ?? TaskPriorityType.Undefined;
if (task.startDate) { if (task.startDate) {
@ -116,8 +116,8 @@ class TaskEdit extends React.PureComponent<PropsType> {
this.state.rrule.until = rrule.until; this.state.rrule.until = rrule.until;
} }
} }
this.state.location = task.location ? task.location : ''; this.state.location = task.location ? task.location : "";
this.state.description = task.description ? task.description : ''; this.state.description = task.description ? task.description : "";
this.state.timezone = task.timezone; this.state.timezone = task.timezone;
this.state.tags = task.tags; this.state.tags = task.tags;
} else { } else {
@ -160,15 +160,15 @@ class TaskEdit extends React.PureComponent<PropsType> {
} }
public handleCloseToast(_event?: React.SyntheticEvent, reason?: string) { public handleCloseToast(_event?: React.SyntheticEvent, reason?: string) {
if (reason === 'clickaway') { if (reason === "clickaway") {
return; return;
} }
this.setState({ error: '' }); this.setState({ error: "" });
} }
public toggleRecurring() { public toggleRecurring() {
const value = this.state.rrule ? undefined : { freq: 'WEEKLY', interval: 1 }; const value = this.state.rrule ? undefined : { freq: "WEEKLY", interval: 1 };
this.setState({ rrule: value }); this.setState({ rrule: value });
} }
@ -180,7 +180,7 @@ class TaskEdit extends React.PureComponent<PropsType> {
e.preventDefault(); e.preventDefault();
if (this.state.rrule && !(this.state.start || this.state.due)) { if (this.state.rrule && !(this.state.start || this.state.due)) {
this.setState({ error: 'A recurring task must have either Hide Until or Due Date set!' }); this.setState({ error: "A recurring task must have either Hide Until or Due Date set!" });
return; return;
} }
@ -203,7 +203,7 @@ class TaskEdit extends React.PureComponent<PropsType> {
if (startDate && dueDate) { if (startDate && dueDate) {
if (startDate.compare(dueDate) >= 0) { if (startDate.compare(dueDate) >= 0) {
this.setState({ error: 'End time must be later than start time!' }); this.setState({ error: "End time must be later than start time!" });
return; return;
} }
} }
@ -243,7 +243,7 @@ class TaskEdit extends React.PureComponent<PropsType> {
} }
} }
task.component.updatePropertyWithValue('last-modified', ICAL.Time.now()); task.component.updatePropertyWithValue("last-modified", ICAL.Time.now());
this.props.onSave(task, this.state.journalUid, this.props.item) this.props.onSave(task, this.state.journalUid, this.props.item)
.then(() => { .then(() => {
@ -258,7 +258,7 @@ class TaskEdit extends React.PureComponent<PropsType> {
this.props.history.goBack(); this.props.history.goBack();
}) })
.catch(() => { .catch(() => {
this.setState({ error: 'Could not save task' }); this.setState({ error: "Could not save task" });
}); });
} }
@ -273,14 +273,14 @@ class TaskEdit extends React.PureComponent<PropsType> {
form: { form: {
}, },
fullWidth: { fullWidth: {
width: '100%', width: "100%",
boxSizing: 'border-box' as any, boxSizing: "border-box" as any,
marginTop: 16, marginTop: 16,
}, },
submit: { submit: {
marginTop: 40, marginTop: 40,
marginBottom: 20, marginBottom: 20,
textAlign: 'right' as any, textAlign: "right" as any,
}, },
}; };
@ -290,11 +290,11 @@ class TaskEdit extends React.PureComponent<PropsType> {
return ( return (
<React.Fragment> <React.Fragment>
<h2> <h2>
{this.props.item ? 'Edit Task' : 'New Task'} {this.props.item ? "Edit Task" : "New Task"}
</h2> </h2>
{recurring && ( {recurring && (
<div> <div>
<span style={{ color: 'red' }}>IMPORTANT: </span> <span style={{ color: "red" }}>IMPORTANT: </span>
This is a recurring task, for now, only editing the whole series This is a recurring task, for now, only editing the whole series
(by editing the first instance) is supported. (by editing the first instance) is supported.
</div> </div>
@ -349,7 +349,7 @@ class TaskEdit extends React.PureComponent<PropsType> {
<RadioGroup <RadioGroup
row row
value={mapPriority(this.state.priority)} value={mapPriority(this.state.priority)}
onChange={(e) => this.handleChange('priority', Number(e.target.value))} onChange={(e) => this.handleChange("priority", Number(e.target.value))}
> >
<ColoredRadio value={TaskPriorityType.Undefined} label="None" color={colors.grey[600]} /> <ColoredRadio value={TaskPriorityType.Undefined} label="None" color={colors.grey[600]} />
<ColoredRadio value={TaskPriorityType.Low} label="Low" color={colors.blue[600]} /> <ColoredRadio value={TaskPriorityType.Low} label="Low" color={colors.blue[600]} />
@ -418,7 +418,7 @@ class TaskEdit extends React.PureComponent<PropsType> {
{this.state.rrule && {this.state.rrule &&
<RRule <RRule
onChange={this.handleRRuleChange} onChange={this.handleRRuleChange}
rrule={this.state.rrule ? this.state.rrule : { freq: 'DAILY', interval: 1 }} rrule={this.state.rrule ? this.state.rrule : { freq: "DAILY", interval: 1 }}
/> />
} }
@ -445,7 +445,7 @@ class TaskEdit extends React.PureComponent<PropsType> {
multiple multiple
options={TaskTags} options={TaskTags}
value={this.state.tags} value={this.state.tags}
onChange={(_e, value) => this.handleChange('tags', value)} onChange={(_e, value) => this.handleChange("tags", value)}
renderInput={(params) => ( renderInput={(params) => (
<TextField <TextField
{...params} {...params}
@ -468,7 +468,7 @@ class TaskEdit extends React.PureComponent<PropsType> {
{this.props.item && {this.props.item &&
<Button <Button
variant="contained" variant="contained"
style={{ marginLeft: 15, backgroundColor: colors.red[500], color: 'white' }} style={{ marginLeft: 15, backgroundColor: colors.red[500], color: "white" }}
onClick={this.onDeleteRequest} onClick={this.onDeleteRequest}
> >
<IconDelete style={{ marginRight: 8 }} /> <IconDelete style={{ marginRight: 8 }} />

@ -1,34 +1,34 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import * as EteSync from 'etesync'; import * as EteSync from "etesync";
import { List } from '../../widgets/List'; import { List } from "../../widgets/List";
import Toast, { PropsType as ToastProps } from '../../widgets/Toast'; import Toast, { PropsType as ToastProps } from "../../widgets/Toast";
import { TaskType, PimType, TaskStatusType } from '../../pim-types'; import { TaskType, PimType, TaskStatusType } from "../../pim-types";
import Divider from '@material-ui/core/Divider'; import Divider from "@material-ui/core/Divider";
import Grid from '@material-ui/core/Grid'; import Grid from "@material-ui/core/Grid";
import { useTheme, makeStyles } from '@material-ui/core/styles'; import { useTheme, makeStyles } from "@material-ui/core/styles";
import { useSelector, useDispatch } from 'react-redux'; import { useSelector, useDispatch } from "react-redux";
import Fuse from 'fuse.js'; import Fuse from "fuse.js";
import TaskListItem from './TaskListItem'; import TaskListItem from "./TaskListItem";
import Sidebar from './Sidebar'; import Sidebar from "./Sidebar";
import Toolbar from './Toolbar'; import Toolbar from "./Toolbar";
import QuickAdd from './QuickAdd'; import QuickAdd from "./QuickAdd";
import { StoreState, UserInfoData } from '../../store'; import { StoreState, UserInfoData } from "../../store";
import { formatDate } from '../../helpers'; import { formatDate } from "../../helpers";
import { SyncInfo } from '../../SyncGate'; import { SyncInfo } from "../../SyncGate";
import { fetchEntries } from '../../store/actions'; import { fetchEntries } from "../../store/actions";
import { Action } from 'redux-actions'; import { Action } from "redux-actions";
import { addJournalEntries } from '../../etesync-helpers'; import { addJournalEntries } from "../../etesync-helpers";
import { useCredentials } from '../../login'; import { useCredentials } from "../../login";
function sortCompleted(a: TaskType, b: TaskType) { function sortCompleted(a: TaskType, b: TaskType) {
return (!!a.finished === !!b.finished) ? 0 : (a.finished) ? 1 : -1; return (!!a.finished === !!b.finished) ? 0 : (a.finished) ? 1 : -1;
@ -55,8 +55,8 @@ function sortPriority(aIn: TaskType, bIn: TaskType) {
} }
function sortTitle(aIn: TaskType, bIn: TaskType) { function sortTitle(aIn: TaskType, bIn: TaskType) {
const a = aIn.title ?? ''; const a = aIn.title ?? "";
const b = bIn.title ?? ''; const b = bIn.title ?? "";
return a.localeCompare(b); return a.localeCompare(b);
} }
@ -64,22 +64,22 @@ function getSortFunction(sortOrder: string) {
const sortFunctions: (typeof sortTitle)[] = [sortCompleted]; const sortFunctions: (typeof sortTitle)[] = [sortCompleted];
switch (sortOrder) { switch (sortOrder) {
case 'smart': case "smart":
sortFunctions.push(sortPriority); sortFunctions.push(sortPriority);
sortFunctions.push(sortDueDate); sortFunctions.push(sortDueDate);
sortFunctions.push(sortTitle); sortFunctions.push(sortTitle);
break; break;
case 'dueDate': case "dueDate":
sortFunctions.push(sortDueDate); sortFunctions.push(sortDueDate);
break; break;
case 'priority': case "priority":
sortFunctions.push(sortPriority); sortFunctions.push(sortPriority);
sortFunctions.push(sortDueDate); sortFunctions.push(sortDueDate);
break; break;
case 'title': case "title":
sortFunctions.push(sortTitle); sortFunctions.push(sortTitle);
break; break;
case 'lastModifiedDate': case "lastModifiedDate":
// Do nothing because it's the last sort function anyway // Do nothing because it's the last sort function anyway
break; break;
} }
@ -116,8 +116,8 @@ interface PropsType {
export default function TaskList(props: PropsType) { export default function TaskList(props: PropsType) {
const [showCompleted, setShowCompleted] = React.useState(false); const [showCompleted, setShowCompleted] = React.useState(false);
const [showHidden, setShowHidden] = React.useState(false); const [showHidden, setShowHidden] = React.useState(false);
const [searchTerm, setSearchTerm] = React.useState(''); const [searchTerm, setSearchTerm] = React.useState("");
const [toast, setToast] = React.useState<{ message: string, severity: ToastProps['severity'] }>({ message: '', severity: undefined }); const [toast, setToast] = React.useState<{ message: string, severity: ToastProps["severity"] }>({ message: "", severity: undefined });
const settings = useSelector((state: StoreState) => state.settings.taskSettings); const settings = useSelector((state: StoreState) => state.settings.taskSettings);
const { filterBy, sortBy } = settings; const { filterBy, sortBy } = settings;
const etesync = useCredentials()!; const etesync = useCredentials()!;
@ -136,7 +136,7 @@ export default function TaskList(props: PropsType) {
const syncJournal = props.syncInfo.get((task as any).journalUid); const syncJournal = props.syncInfo.get((task as any).journalUid);
if (syncJournal === undefined) { if (syncJournal === undefined) {
setToast({ message: 'Could not sync.', severity: 'error' }); setToast({ message: "Could not sync.", severity: "error" });
return; return;
} }
@ -170,11 +170,11 @@ export default function TaskList(props: PropsType) {
}) })
.then(() => { .then(() => {
if (nextTask) { if (nextTask) {
setToast({ message: `${nextTask.title} rescheduled for ${formatDate(nextTask.startDate ?? nextTask.dueDate)}`, severity: 'success' }); setToast({ message: `${nextTask.title} rescheduled for ${formatDate(nextTask.startDate ?? nextTask.dueDate)}`, severity: "success" });
} }
}) })
.catch(() => { .catch(() => {
setToast({ message: 'Failed to save changes. This may be due to a network error.', severity: 'error' }); setToast({ message: "Failed to save changes. This may be due to a network error.", severity: "error" });
}); });
}; };
@ -187,8 +187,8 @@ export default function TaskList(props: PropsType) {
maxPatternLength: 32, maxPatternLength: 32,
minMatchCharLength: 2, minMatchCharLength: 2,
keys: [ keys: [
'title', "title",
'desc', "desc",
], ],
}).search(searchTerm); }).search(searchTerm);
return result.map((x) => x.item); return result.map((x) => x.item);
@ -201,11 +201,11 @@ export default function TaskList(props: PropsType) {
let entries; let entries;
const tagPrefix = 'tag:'; const tagPrefix = "tag:";
if (filterBy?.startsWith(tagPrefix)) { if (filterBy?.startsWith(tagPrefix)) {
const tag = filterBy.slice(tagPrefix.length); const tag = filterBy.slice(tagPrefix.length);
entries = potentialEntries.filter((x) => x.tags.includes(tag)); entries = potentialEntries.filter((x) => x.tags.includes(tag));
} else if (filterBy === 'today') { } else if (filterBy === "today") {
entries = potentialEntries.filter((x) => x.dueToday); entries = potentialEntries.filter((x) => x.dueToday);
} else { } else {
entries = potentialEntries; entries = potentialEntries;
@ -267,16 +267,16 @@ export default function TaskList(props: PropsType) {
<Grid item xs> <Grid item xs>
{props.collections?.[0] && <QuickAdd style={{ flexGrow: 1, marginRight: '0.75em' }} onSubmit={props.onItemSave} defaultCollection={props.collections?.[0]} />} {props.collections?.[0] && <QuickAdd style={{ flexGrow: 1, marginRight: "0.75em" }} onSubmit={props.onItemSave} defaultCollection={props.collections?.[0]} />}
<Divider style={{ marginTop: '1em' }} /> <Divider style={{ marginTop: "1em" }} />
<List> <List>
{itemList} {itemList}
</List> </List>
</Grid> </Grid>
<Toast open={!!toast.message} severity={toast.severity} onClose={() => setToast({ message: '', severity: undefined })} autoHideDuration={3000}> <Toast open={!!toast.message} severity={toast.severity} onClose={() => setToast({ message: "", severity: undefined })} autoHideDuration={3000}>
{toast.message} {toast.message}
</Toast> </Toast>
</Grid> </Grid>

@ -1,17 +1,17 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import { TaskType, TaskPriorityType } from '../../pim-types'; import { TaskType, TaskPriorityType } from "../../pim-types";
import { ListItem } from '../../widgets/List'; import { ListItem } from "../../widgets/List";
import Checkbox from '@material-ui/core/Checkbox'; import Checkbox from "@material-ui/core/Checkbox";
import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank'; import CheckBoxOutlineBlankIcon from "@material-ui/icons/CheckBoxOutlineBlank";
import * as colors from '@material-ui/core/colors'; import * as colors from "@material-ui/core/colors";
import Chip from '@material-ui/core/Chip'; import Chip from "@material-ui/core/Chip";
import { mapPriority, formatDate } from '../../helpers'; import { mapPriority, formatDate } from "../../helpers";
const checkboxColor = { const checkboxColor = {
[TaskPriorityType.Undefined]: colors.grey[600], [TaskPriorityType.Undefined]: colors.grey[600],
@ -27,7 +27,7 @@ const TagsList = React.memo((props: { tags: string[] }) => (
color="secondary" color="secondary"
size="small" size="small"
label={tag} label={tag}
style={{ marginRight: '0.75em' }} style={{ marginRight: "0.75em" }}
component="li" component="li"
/>)} />)}
</ul>)); </ul>));
@ -48,15 +48,15 @@ export default React.memo(function TaskListItem(props: PropsType) {
} = props; } = props;
const title = task.title; const title = task.title;
const dueDateText = task.dueDate ? `Due ${formatDate(task.dueDate)}` : ''; const dueDateText = task.dueDate ? `Due ${formatDate(task.dueDate)}` : "";
const freqText = task.rrule ? `(repeats ${task.rrule.freq.toLowerCase()})` : ''; const freqText = task.rrule ? `(repeats ${task.rrule.freq.toLowerCase()})` : "";
const secondaryText = `${dueDateText} ${freqText}`; const secondaryText = `${dueDateText} ${freqText}`;
return ( return (
<ListItem <ListItem
primaryText={title} primaryText={title}
secondaryText={secondaryText} secondaryText={secondaryText}
secondaryTextColor={task.overdue ? 'error' : 'textSecondary'} secondaryTextColor={task.overdue ? "error" : "textSecondary"}
nestedItems={nestedItems} nestedItems={nestedItems}
onClick={() => onClick(task)} onClick={() => onClick(task)}
leftIcon={ leftIcon={

@ -1,36 +1,36 @@
import * as React from 'react'; import * as React from "react";
import * as EteSync from 'etesync'; import * as EteSync from "etesync";
import Switch from '@material-ui/core/Switch'; import Switch from "@material-ui/core/Switch";
import IconButton from '@material-ui/core/IconButton'; import IconButton from "@material-ui/core/IconButton";
import MoreVertIcon from '@material-ui/icons/MoreVert'; import MoreVertIcon from "@material-ui/icons/MoreVert";
import MenuItem from '@material-ui/core/MenuItem'; import MenuItem from "@material-ui/core/MenuItem";
import SortIcon from '@material-ui/icons/Sort'; import SortIcon from "@material-ui/icons/Sort";
import SearchIcon from '@material-ui/icons/Search'; import SearchIcon from "@material-ui/icons/Search";
import CloseIcon from '@material-ui/icons/Close'; import CloseIcon from "@material-ui/icons/Close";
import TextField from '@material-ui/core/TextField'; import TextField from "@material-ui/core/TextField";
import { makeStyles } from '@material-ui/core/styles'; import { makeStyles } from "@material-ui/core/styles";
import { Transition } from 'react-transition-group'; import { Transition } from "react-transition-group";
import InputAdornment from '@material-ui/core/InputAdornment'; import InputAdornment from "@material-ui/core/InputAdornment";
import { PimType } from '../../pim-types'; import { PimType } from "../../pim-types";
import { useSelector, useDispatch } from 'react-redux'; import { useSelector, useDispatch } from "react-redux";
import { setSettings } from '../../store/actions'; import { setSettings } from "../../store/actions";
import { StoreState } from '../../store'; import { StoreState } from "../../store";
import Menu from '../../widgets/Menu'; import Menu from "../../widgets/Menu";
import { ListItemText, ListItemSecondaryAction } from '@material-ui/core'; import { ListItemText, ListItemSecondaryAction } from "@material-ui/core";
const transitionTimeout = 300; const transitionTimeout = 300;
const transitionStyles = { const transitionStyles = {
entering: { visibility: 'visible', width: '100%', overflow: 'hidden' }, entering: { visibility: "visible", width: "100%", overflow: "hidden" },
entered: { visibility: 'visible', width: '100%' }, entered: { visibility: "visible", width: "100%" },
exiting: { visibility: 'visible', width: '0%', overflow: 'hidden' }, exiting: { visibility: "visible", width: "0%", overflow: "hidden" },
exited: { visibility: 'hidden', width: '0%' }, exited: { visibility: "hidden", width: "0%" },
}; };
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
@ -69,7 +69,7 @@ export default function Toolbar(props: PropsType) {
const toggleSearchField = () => { const toggleSearchField = () => {
if (showSearchField) { if (showSearchField) {
setSearchTerm(''); setSearchTerm("");
} }
setShowSearchField(!showSearchField); setShowSearchField(!showSearchField);
}; };
@ -86,7 +86,7 @@ export default function Toolbar(props: PropsType) {
}); });
return ( return (
<div style={{ display: 'flex', justifyContent: 'flex-end', alignItems: 'center' }}> <div style={{ display: "flex", justifyContent: "flex-end", alignItems: "center" }}>
<Transition in={showSearchField} timeout={transitionTimeout}> <Transition in={showSearchField} timeout={transitionTimeout}>
{(state) => ( {(state) => (
<TextField <TextField
@ -110,7 +110,7 @@ export default function Toolbar(props: PropsType) {
</Transition> </Transition>
<div className={classes.button}> <div className={classes.button}>
<IconButton size="small" onClick={toggleSearchField} title={showSearchField ? 'Close' : 'Search'}> <IconButton size="small" onClick={toggleSearchField} title={showSearchField ? "Close" : "Search"}>
{showSearchField ? <CloseIcon /> : <SearchIcon />} {showSearchField ? <CloseIcon /> : <SearchIcon />}
</IconButton> </IconButton>
</div> </div>
@ -160,13 +160,13 @@ export default function Toolbar(props: PropsType) {
onClose={() => setOptionsAnchorEl(null)} onClose={() => setOptionsAnchorEl(null)}
> >
<MenuItem> <MenuItem>
<ListItemText style={{ marginRight: '1.5em' }}>Show completed</ListItemText> <ListItemText style={{ marginRight: "1.5em" }}>Show completed</ListItemText>
<ListItemSecondaryAction> <ListItemSecondaryAction>
<Switch checked={showCompleted} onChange={(_e, checked) => setShowCompleted(checked)} edge="end" /> <Switch checked={showCompleted} onChange={(_e, checked) => setShowCompleted(checked)} edge="end" />
</ListItemSecondaryAction> </ListItemSecondaryAction>
</MenuItem> </MenuItem>
<MenuItem> <MenuItem>
<ListItemText style={{ marginRight: '1.5em' }}>Show hidden</ListItemText> <ListItemText style={{ marginRight: "1.5em" }}>Show hidden</ListItemText>
<ListItemSecondaryAction> <ListItemSecondaryAction>
<Switch checked={showHidden} onChange={(_e, checked) => setShowHidden(checked)} edge="end" /> <Switch checked={showHidden} onChange={(_e, checked) => setShowHidden(checked)} edge="end" />
</ListItemSecondaryAction> </ListItemSecondaryAction>

@ -1,12 +1,12 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
export const appName = 'EteSync'; export const appName = "EteSync";
export const homePage = 'https://www.etesync.com/'; export const homePage = "https://www.etesync.com/";
export const faq = homePage + 'faq/'; export const faq = homePage + "faq/";
export const sourceCode = 'https://github.com/etesync/etesync-web'; export const sourceCode = "https://github.com/etesync/etesync-web";
export const reportIssue = sourceCode + '/issues'; export const reportIssue = sourceCode + "/issues";
export const forgotPassword = 'https://www.etesync.com/accounts/password/reset/'; export const forgotPassword = "https://www.etesync.com/accounts/password/reset/";
export const serviceApiBase = process.env.REACT_APP_DEFAULT_API_PATH || 'https://api.etesync.com/'; export const serviceApiBase = process.env.REACT_APP_DEFAULT_API_PATH || "https://api.etesync.com/";

@ -1,10 +1,10 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as EteSync from 'etesync'; import * as EteSync from "etesync";
import { CredentialsData, UserInfoData } from './store'; import { CredentialsData, UserInfoData } from "./store";
import { addEntries } from './store/actions'; import { addEntries } from "./store/actions";
export function createJournalEntry( export function createJournalEntry(
etesync: CredentialsData, etesync: CredentialsData,

@ -1,10 +1,10 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import * as ICAL from 'ical.js'; import * as ICAL from "ical.js";
import moment from 'moment'; import moment from "moment";
import { TaskPriorityType } from './pim-types'; import { TaskPriorityType } from "./pim-types";
// Generic handling of input changes // Generic handling of input changes
export function handleInputChange(self: React.Component, part?: string) { export function handleInputChange(self: React.Component, part?: string) {
@ -14,7 +14,7 @@ export function handleInputChange(self: React.Component, part?: string) {
let newState; let newState;
if (event.target.type === 'checkbox') { if (event.target.type === "checkbox") {
newState = { newState = {
[name]: event.target.checked, [name]: event.target.checked,
}; };
@ -54,8 +54,8 @@ export function insertSorted<T>(array: T[] = [], newItem: T, key: string) {
return array; return array;
} }
const allDayFormat = 'dddd, LL'; const allDayFormat = "dddd, LL";
const fullFormat = 'LLLL'; const fullFormat = "LLLL";
export function formatDate(date: ICAL.Time) { export function formatDate(date: ICAL.Time) {
const mDate = moment(date.toJSDate()); const mDate = moment(date.toJSDate());
@ -74,15 +74,15 @@ export function formatDateRange(start: ICAL.Time, end: ICAL.Time) {
// All day // All day
if (start.isDate) { if (start.isDate) {
if (mEnd.diff(mStart, 'days', true) === 1) { if (mEnd.diff(mStart, "days", true) === 1) {
return mStart.format(allDayFormat); return mStart.format(allDayFormat);
} else { } else {
strStart = mStart.format(allDayFormat); strStart = mStart.format(allDayFormat);
strEnd = mEnd.clone().subtract(1, 'day').format(allDayFormat); strEnd = mEnd.clone().subtract(1, "day").format(allDayFormat);
} }
} else if (mStart.isSame(mEnd, 'day')) { } else if (mStart.isSame(mEnd, "day")) {
strStart = mStart.format(fullFormat); strStart = mStart.format(fullFormat);
strEnd = mEnd.format('LT'); strEnd = mEnd.format("LT");
if (mStart.isSame(mEnd)) { if (mStart.isSame(mEnd)) {
return strStart; return strStart;
@ -92,17 +92,17 @@ export function formatDateRange(start: ICAL.Time, end: ICAL.Time) {
strEnd = mEnd.format(fullFormat); strEnd = mEnd.format(fullFormat);
} }
return strStart + ' - ' + strEnd; return strStart + " - " + strEnd;
} }
export function formatOurTimezoneOffset() { export function formatOurTimezoneOffset() {
let offset = new Date().getTimezoneOffset(); let offset = new Date().getTimezoneOffset();
const prefix = (offset > 0) ? '-' : '+'; const prefix = (offset > 0) ? "-" : "+";
offset = Math.abs(offset); offset = Math.abs(offset);
const hours = Math.floor(offset / 60); const hours = Math.floor(offset / 60);
const minutes = offset % 60; const minutes = offset % 60;
return `GMT${prefix}${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`; return `GMT${prefix}${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}`;
} }
export function getCurrentTimezone() { export function getCurrentTimezone() {

@ -1,9 +1,9 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import SvgIcon from '@material-ui/core/SvgIcon'; import SvgIcon from "@material-ui/core/SvgIcon";
export default function CopyIcon(props: any) { export default function CopyIcon(props: any) {
return ( return (

@ -1,15 +1,15 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import * as ReactDOM from 'react-dom'; import * as ReactDOM from "react-dom";
import { Provider } from 'react-redux'; import { Provider } from "react-redux";
import { PersistGate } from 'redux-persist/es/integration/react'; import { PersistGate } from "redux-persist/es/integration/react";
import App from './App'; import App from "./App";
import registerServiceWorker from './registerServiceWorker'; import registerServiceWorker from "./registerServiceWorker";
import './index.css'; import "./index.css";
import { store, persistor } from './store'; import { store, persistor } from "./store";
ReactDOM.render( ReactDOM.render(
<Provider store={store}> <Provider store={store}>
@ -17,6 +17,6 @@ ReactDOM.render(
<App /> <App />
</PersistGate> </PersistGate>
</Provider>, </Provider>,
document.getElementById('root') as HTMLElement document.getElementById("root") as HTMLElement
); );
registerServiceWorker(); registerServiceWorker();

@ -1,21 +1,21 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import { colorHtmlToInt, colorIntToHtml } from './journal-processors'; import { colorHtmlToInt, colorIntToHtml } from "./journal-processors";
it('Color conversion', () => { it("Color conversion", () => {
const testColors = [ const testColors = [
'#aaaaaaaa', "#aaaaaaaa",
'#00aaaaaa', "#00aaaaaa",
'#0000aaaa', "#0000aaaa",
'#000000aa', "#000000aa",
'#00000000', "#00000000",
'#bb00bbbb', "#bb00bbbb",
'#bb0000bb', "#bb0000bb",
'#bb000000', "#bb000000",
'#11110011', "#11110011",
'#11110000', "#11110000",
'#11111100', "#11111100",
]; ];
for (const color of testColors) { for (const color of testColors) {

@ -1,13 +1,13 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import { List } from 'immutable'; import { List } from "immutable";
import { EventType, ContactType, TaskType } from './pim-types'; import { EventType, ContactType, TaskType } from "./pim-types";
import { store } from './store'; import { store } from "./store";
import { appendError } from './store/actions'; import { appendError } from "./store/actions";
import * as EteSync from 'etesync'; import * as EteSync from "etesync";
export function syncEntriesToItemMap( export function syncEntriesToItemMap(
collection: EteSync.CollectionInfo, entries: List<EteSync.SyncEntry>, base: {[key: string]: ContactType} = {}) { collection: EteSync.CollectionInfo, entries: List<EteSync.SyncEntry>, base: {[key: string]: ContactType} = {}) {
@ -44,7 +44,7 @@ export function syncEntriesToItemMap(
return items; return items;
} }
export const defaultColor = '#8BC34A'; export const defaultColor = "#8BC34A";
export function colorIntToHtml(color?: number) { export function colorIntToHtml(color?: number) {
if (color === undefined) { if (color === undefined) {
@ -60,10 +60,10 @@ export function colorIntToHtml(color?: number) {
function toHex(num: number) { function toHex(num: number) {
const ret = num.toString(16); const ret = num.toString(16);
return (ret.length === 1) ? '0' + ret : ret; return (ret.length === 1) ? "0" + ret : ret;
} }
return '#' + toHex(red) + toHex(green) + toHex(blue) + toHex(alpha); return "#" + toHex(red) + toHex(green) + toHex(blue) + toHex(alpha);
} }
export function colorHtmlToInt(color?: string) { export function colorHtmlToInt(color?: string) {

@ -1,10 +1,10 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import { shallowEqual, useSelector } from 'react-redux'; import { shallowEqual, useSelector } from "react-redux";
import { createSelector } from 'reselect'; import { createSelector } from "reselect";
import * as store from '../store'; import * as store from "../store";
export const remoteCredentialsSelector = createSelector( export const remoteCredentialsSelector = createSelector(
(state: store.StoreState) => state.credentials.credentials, (state: store.StoreState) => state.credentials.credentials,

@ -1,8 +1,8 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import { withRouter } from 'react-router'; import { withRouter } from "react-router";
// FIXME: Should probably tie this to the history object, or at least based on the depth of the history // FIXME: Should probably tie this to the history object, or at least based on the depth of the history
const stateCache = {}; const stateCache = {};
@ -29,7 +29,7 @@ export function historyPersistor(tag: string) {
} }
public getKeyForTag(props: any, tagName: string) { public getKeyForTag(props: any, tagName: string) {
return props.location.pathname + ':' + tagName; return props.location.pathname + ":" + tagName;
} }
}); });
}; };

@ -1,12 +1,12 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as ICAL from 'ical.js'; import * as ICAL from "ical.js";
import * as zones from './data/zones.json'; import * as zones from "./data/zones.json";
import moment from 'moment'; import moment from "moment";
import * as uuid from 'uuid'; import * as uuid from "uuid";
export const PRODID = '-//iCal.js EteSync iOS'; export const PRODID = "-//iCal.js EteSync iOS";
export interface PimType { export interface PimType {
uid: string; uid: string;
@ -32,11 +32,11 @@ export function timezoneLoadFromName(timezone: string | null) {
return ICAL.TimezoneService.get(timezone); return ICAL.TimezoneService.get(timezone);
} }
const component = new ICAL.Component('vtimezone'); const component = new ICAL.Component("vtimezone");
zone.ics.forEach((zonePart: string) => { zone.ics.forEach((zonePart: string) => {
component.addSubcomponent(new ICAL.Component(ICAL.parse(zonePart))); component.addSubcomponent(new ICAL.Component(ICAL.parse(zonePart)));
}); });
component.addPropertyWithValue('tzid', timezone); component.addPropertyWithValue("tzid", timezone);
const retZone = new ICAL.Timezone({ const retZone = new ICAL.Timezone({
component, component,
@ -49,17 +49,17 @@ export function timezoneLoadFromName(timezone: string | null) {
} }
export function parseString(content: string) { export function parseString(content: string) {
content = content.replace(/^[a-zA-Z0-9]*\./gm, ''); // FIXME: ugly hack to ignore item groups. content = content.replace(/^[a-zA-Z0-9]*\./gm, ""); // FIXME: ugly hack to ignore item groups.
return new ICAL.Component(ICAL.parse(content)); return new ICAL.Component(ICAL.parse(content));
} }
export class EventType extends ICAL.Event implements PimType { export class EventType extends ICAL.Event implements PimType {
public static isEvent(comp: ICAL.Component) { public static isEvent(comp: ICAL.Component) {
return !!comp.getFirstSubcomponent('vevent'); return !!comp.getFirstSubcomponent("vevent");
} }
public static fromVCalendar(comp: ICAL.Component) { public static fromVCalendar(comp: ICAL.Component) {
const event = new EventType(comp.getFirstSubcomponent('vevent')); const event = new EventType(comp.getFirstSubcomponent("vevent"));
// FIXME: we need to clone it so it loads the correct timezone and applies it // FIXME: we need to clone it so it loads the correct timezone and applies it
timezoneLoadFromName(event.timezone); timezoneLoadFromName(event.timezone);
return event.clone(); return event.clone();
@ -106,25 +106,25 @@ export class EventType extends ICAL.Event implements PimType {
} }
get lastModified() { get lastModified() {
return this.component.getFirstPropertyValue('last-modified'); return this.component.getFirstPropertyValue("last-modified");
} }
set lastModified(time: ICAL.Time) { set lastModified(time: ICAL.Time) {
this.component.updatePropertyWithValue('last-modified', time); this.component.updatePropertyWithValue("last-modified", time);
} }
get rrule() { get rrule() {
return this.component.getFirstPropertyValue('rrule'); return this.component.getFirstPropertyValue("rrule");
} }
set rrule(rule: ICAL.Recur) { set rrule(rule: ICAL.Recur) {
this.component.updatePropertyWithValue('rrule', rule); this.component.updatePropertyWithValue("rrule", rule);
} }
public toIcal() { public toIcal() {
const comp = new ICAL.Component(['vcalendar', [], []]); const comp = new ICAL.Component(["vcalendar", [], []]);
comp.updatePropertyWithValue('prodid', PRODID); comp.updatePropertyWithValue("prodid", PRODID);
comp.updatePropertyWithValue('version', '2.0'); comp.updatePropertyWithValue("version", "2.0");
comp.addSubcomponent(this.component); comp.addSubcomponent(this.component);
ICAL.helpers.updateTimezones(comp); ICAL.helpers.updateTimezones(comp);
@ -139,10 +139,10 @@ export class EventType extends ICAL.Event implements PimType {
} }
export enum TaskStatusType { export enum TaskStatusType {
NeedsAction = 'NEEDS-ACTION', NeedsAction = "NEEDS-ACTION",
Completed = 'COMPLETED', Completed = "COMPLETED",
InProcess = 'IN-PROCESS', InProcess = "IN-PROCESS",
Cancelled = 'CANCELLED', Cancelled = "CANCELLED",
} }
export enum TaskPriorityType { export enum TaskPriorityType {
@ -152,11 +152,11 @@ export enum TaskPriorityType {
Low = 9 Low = 9
} }
export const TaskTags = ['Work', 'Home']; export const TaskTags = ["Work", "Home"];
export class TaskType extends EventType { export class TaskType extends EventType {
public static fromVCalendar(comp: ICAL.Component) { public static fromVCalendar(comp: ICAL.Component) {
const task = new TaskType(comp.getFirstSubcomponent('vtodo')); const task = new TaskType(comp.getFirstSubcomponent("vtodo"));
// FIXME: we need to clone it so it loads the correct timezone and applies it // FIXME: we need to clone it so it loads the correct timezone and applies it
timezoneLoadFromName(task.timezone); timezoneLoadFromName(task.timezone);
return task.clone(); return task.clone();
@ -169,7 +169,7 @@ export class TaskType extends EventType {
public color: string; public color: string;
constructor(comp?: ICAL.Component | null) { constructor(comp?: ICAL.Component | null) {
super(comp ? comp : new ICAL.Component('vtodo')); super(comp ? comp : new ICAL.Component("vtodo"));
} }
get finished() { get finished() {
@ -178,64 +178,64 @@ export class TaskType extends EventType {
} }
set status(status: TaskStatusType) { set status(status: TaskStatusType) {
this.component.updatePropertyWithValue('status', status); this.component.updatePropertyWithValue("status", status);
} }
get status(): TaskStatusType { get status(): TaskStatusType {
return this.component.getFirstPropertyValue('status'); return this.component.getFirstPropertyValue("status");
} }
set priority(priority: TaskPriorityType) { set priority(priority: TaskPriorityType) {
this.component.updatePropertyWithValue('priority', priority); this.component.updatePropertyWithValue("priority", priority);
} }
get priority() { get priority() {
return this.component.getFirstPropertyValue('priority'); return this.component.getFirstPropertyValue("priority");
} }
set tags(tags: string[]) { set tags(tags: string[]) {
this.component.updatePropertyWithValue('categories', tags.join(',')); this.component.updatePropertyWithValue("categories", tags.join(","));
} }
get tags() { get tags() {
const tags = this.component.getFirstPropertyValue('categories'); const tags = this.component.getFirstPropertyValue("categories");
return tags ? tags.split(',') : []; return tags ? tags.split(",") : [];
} }
set dueDate(date: ICAL.Time | undefined) { set dueDate(date: ICAL.Time | undefined) {
if (date) { if (date) {
this.component.updatePropertyWithValue('due', date); this.component.updatePropertyWithValue("due", date);
} else { } else {
this.component.removeAllProperties('due'); this.component.removeAllProperties("due");
} }
} }
get dueDate() { get dueDate() {
return this.component.getFirstPropertyValue('due'); return this.component.getFirstPropertyValue("due");
} }
set completionDate(date: ICAL.Time | undefined) { set completionDate(date: ICAL.Time | undefined) {
if (date) { if (date) {
this.component.updatePropertyWithValue('completed', date); this.component.updatePropertyWithValue("completed", date);
} else { } else {
this.component.removeAllProperties('completed'); this.component.removeAllProperties("completed");
} }
} }
get completionDate() { get completionDate() {
return this.component.getFirstPropertyValue('completed'); return this.component.getFirstPropertyValue("completed");
} }
set relatedTo(parentUid: string | undefined) { set relatedTo(parentUid: string | undefined) {
if (parentUid !== undefined) { if (parentUid !== undefined) {
this.component.updatePropertyWithValue('related-to', parentUid); this.component.updatePropertyWithValue("related-to", parentUid);
} else { } else {
this.component.removeAllProperties('related-to'); this.component.removeAllProperties("related-to");
} }
} }
get relatedTo(): string | undefined { get relatedTo(): string | undefined {
return this.component.getFirstPropertyValue('related-to'); return this.component.getFirstPropertyValue("related-to");
} }
get endDate() { get endDate() {
@ -248,7 +248,7 @@ export class TaskType extends EventType {
} }
get dueToday() { get dueToday() {
return this.dueDate && moment(this.dueDate.toJSDate()).isSameOrBefore(moment(), 'day'); return this.dueDate && moment(this.dueDate.toJSDate()).isSameOrBefore(moment(), "day");
} }
get overdue() { get overdue() {
@ -258,7 +258,7 @@ export class TaskType extends EventType {
const dueDate = moment(this.dueDate.toJSDate()); const dueDate = moment(this.dueDate.toJSDate());
const now = moment(); const now = moment();
return (this.dueDate.isDate) ? dueDate.isBefore(now, 'day') : dueDate.isBefore(now); return (this.dueDate.isDate) ? dueDate.isBefore(now, "day") : dueDate.isBefore(now);
} }
get hidden() { get hidden() {
@ -349,23 +349,23 @@ export class ContactType implements PimType {
} }
get uid() { get uid() {
return this.comp.getFirstPropertyValue('uid'); return this.comp.getFirstPropertyValue("uid");
} }
set uid(uid: string) { set uid(uid: string) {
this.comp.updatePropertyWithValue('uid', uid); this.comp.updatePropertyWithValue("uid", uid);
} }
get fn() { get fn() {
return this.comp.getFirstPropertyValue('fn'); return this.comp.getFirstPropertyValue("fn");
} }
get n() { get n() {
return this.comp.getFirstPropertyValue('n'); return this.comp.getFirstPropertyValue("n");
} }
get group() { get group() {
const kind = this.comp.getFirstPropertyValue('kind'); const kind = this.comp.getFirstPropertyValue("kind");
return kind in ['group', 'organization']; return kind in ["group", "organization"];
} }
} }

@ -13,9 +13,9 @@
// This link also includes instructions on opting out of this behavior. // This link also includes instructions on opting out of this behavior.
const isLocalhost = Boolean( const isLocalhost = Boolean(
window.location.hostname === 'localhost' || window.location.hostname === "localhost" ||
// [::1] is the IPv6 localhost address. // [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' || window.location.hostname === "[::1]" ||
// 127.0.0.1/8 is considered localhost for IPv4. // 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match( window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
@ -23,7 +23,7 @@ const isLocalhost = Boolean(
); );
export default function register() { export default function register() {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) {
// The URL constructor is available in all browsers that support SW. // The URL constructor is available in all browsers that support SW.
const publicUrl = new URL( const publicUrl = new URL(
process.env.PUBLIC_URL!, process.env.PUBLIC_URL!,
@ -36,7 +36,7 @@ export default function register() {
return; return;
} }
window.addEventListener('load', () => { window.addEventListener("load", () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (!isLocalhost) { if (!isLocalhost) {
@ -58,18 +58,18 @@ function registerValidSW(swUrl: string) {
const installingWorker = registration.installing; const installingWorker = registration.installing;
if (installingWorker) { if (installingWorker) {
installingWorker.onstatechange = () => { installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') { if (installingWorker.state === "installed") {
if (navigator.serviceWorker.controller) { if (navigator.serviceWorker.controller) {
// At this point, the old content will have been purged and // At this point, the old content will have been purged and
// the fresh content will have been added to the cache. // the fresh content will have been added to the cache.
// It's the perfect time to display a 'New content is // It's the perfect time to display a 'New content is
// available; please refresh.' message in your web app. // available; please refresh.' message in your web app.
console.log('New content is available; please refresh.'); console.log("New content is available; please refresh.");
} else { } else {
// At this point, everything has been precached. // At this point, everything has been precached.
// It's the perfect time to display a // It's the perfect time to display a
// 'Content is cached for offline use.' message. // 'Content is cached for offline use.' message.
console.log('Content is cached for offline use.'); console.log("Content is cached for offline use.");
} }
} }
}; };
@ -77,7 +77,7 @@ function registerValidSW(swUrl: string) {
}; };
}) })
.catch((error) => { .catch((error) => {
console.error('Error during service worker registration:', error); console.error("Error during service worker registration:", error);
}); });
} }
@ -88,7 +88,7 @@ function checkValidServiceWorker(swUrl: string) {
// Ensure service worker exists, and that we really are getting a JS file. // Ensure service worker exists, and that we really are getting a JS file.
if ( if (
response.status === 404 || response.status === 404 ||
response.headers.get('content-type')!.indexOf('javascript') === -1 response.headers.get("content-type")!.indexOf("javascript") === -1
) { ) {
// No service worker found. Probably a different app. Reload the page. // No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then((registration) => { navigator.serviceWorker.ready.then((registration) => {
@ -103,13 +103,13 @@ function checkValidServiceWorker(swUrl: string) {
}) })
.catch(() => { .catch(() => {
console.log( console.log(
'No internet connection found. App is running in offline mode.' "No internet connection found. App is running in offline mode."
); );
}); });
} }
export function unregister() { export function unregister() {
if ('serviceWorker' in navigator) { if ("serviceWorker" in navigator) {
navigator.serviceWorker.ready.then((registration) => { navigator.serviceWorker.ready.then((registration) => {
registration.unregister(); registration.unregister();
}); });

@ -1,63 +1,63 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import { RouteResolver } from './routes'; import { RouteResolver } from "./routes";
const routes = { const routes = {
home: '', home: "",
post: { post: {
_base: 'post', _base: "post",
_id: { _id: {
_base: ':postId', _base: ":postId",
comment: 'comment/:commentId', comment: "comment/:commentId",
revision: 'history/:revisionId/:someOtherVar/test', revision: "history/:revisionId/:someOtherVar/test",
}, },
}, },
}; };
const routeResolver = new RouteResolver(routes); const routeResolver = new RouteResolver(routes);
it('translating routes', () => { it("translating routes", () => {
// Working basic resolves // Working basic resolves
expect(routeResolver.getRoute('home')).toBe('/'); expect(routeResolver.getRoute("home")).toBe("/");
expect(routeResolver.getRoute('post')).toBe('/post'); expect(routeResolver.getRoute("post")).toBe("/post");
expect(routeResolver.getRoute('post._id')).toBe('/post/:postId'); expect(routeResolver.getRoute("post._id")).toBe("/post/:postId");
expect(routeResolver.getRoute('post._id.comment')).toBe('/post/:postId/comment/:commentId'); expect(routeResolver.getRoute("post._id.comment")).toBe("/post/:postId/comment/:commentId");
// Working translation resolves // Working translation resolves
expect(routeResolver.getRoute('home')).toBe('/'); expect(routeResolver.getRoute("home")).toBe("/");
expect(routeResolver.getRoute('post')).toBe('/post'); expect(routeResolver.getRoute("post")).toBe("/post");
expect(routeResolver.getRoute('post._id', { postId: 3 })).toBe('/post/3'); expect(routeResolver.getRoute("post._id", { postId: 3 })).toBe("/post/3");
expect(routeResolver.getRoute('post._id.comment', expect(routeResolver.getRoute("post._id.comment",
{ postId: 3, commentId: 5 })).toBe('/post/3/comment/5'); { postId: 3, commentId: 5 })).toBe("/post/3/comment/5");
expect(routeResolver.getRoute('post._id.revision', expect(routeResolver.getRoute("post._id.revision",
{ postId: 3, revisionId: 5, someOtherVar: 'a' })).toBe('/post/3/history/5/a/test'); { postId: 3, revisionId: 5, someOtherVar: "a" })).toBe("/post/3/history/5/a/test");
// Failing basic resolves // Failing basic resolves
expect(() => { expect(() => {
routeResolver.getRoute('bad'); routeResolver.getRoute("bad");
}).toThrow(); }).toThrow();
expect(() => { expect(() => {
routeResolver.getRoute('home.bad'); routeResolver.getRoute("home.bad");
}).toThrow(); }).toThrow();
expect(() => { expect(() => {
routeResolver.getRoute('post._id.bad'); routeResolver.getRoute("post._id.bad");
}).toThrow(); }).toThrow();
// Failing translations // Failing translations
expect(() => { expect(() => {
routeResolver.getRoute('home', { test: 4 }); routeResolver.getRoute("home", { test: 4 });
}).toThrow(); }).toThrow();
expect(() => { expect(() => {
routeResolver.getRoute('post._id', { test: 4 }); routeResolver.getRoute("post._id", { test: 4 });
}).toThrow(); }).toThrow();
expect(() => { expect(() => {
routeResolver.getRoute('post._id', { postId: 3, test: 4 }); routeResolver.getRoute("post._id", { postId: 3, test: 4 });
}).toThrow(); }).toThrow();
expect(() => { expect(() => {
routeResolver.getRoute('post._id.comment', { postId: 3, commentId: 5, test: 4 }); routeResolver.getRoute("post._id.comment", { postId: 3, commentId: 5, test: 4 });
}).toThrow(); }).toThrow();
expect(() => { expect(() => {
routeResolver.getRoute('post._id.comment', { postId: 3 }); routeResolver.getRoute("post._id.comment", { postId: 3 });
}).toThrow(); }).toThrow();
}); });

@ -16,8 +16,8 @@ export class RouteResolver {
let dict = this.routes; let dict = this.routes;
let path: string[] = []; let path: string[] = [];
name.split('.').forEach((key) => { name.split(".").forEach((key) => {
const val = (typeof dict[key] === 'string') ? dict[key] : (dict[key]._base) ? dict[key]._base : key; const val = (typeof dict[key] === "string") ? dict[key] : (dict[key]._base) ? dict[key]._base : key;
path.push(val); path.push(val);
dict = dict[key]; dict = dict[key];
@ -27,11 +27,11 @@ export class RouteResolver {
const keys = Object.assign({}, _keys); const keys = Object.assign({}, _keys);
path = path.map((pathComponent) => { path = path.map((pathComponent) => {
return pathComponent.split('/').map((val) => { return pathComponent.split("/").map((val) => {
if (val[0] === ':') { if (val[0] === ":") {
const ret = keys[val.slice(1)]; const ret = keys[val.slice(1)];
if (ret === undefined) { if (ret === undefined) {
throw new Error('Missing key: ' + val.slice(1)); throw new Error("Missing key: " + val.slice(1));
} }
delete keys[val.slice(1)]; delete keys[val.slice(1)];
@ -39,14 +39,14 @@ export class RouteResolver {
} }
return val; return val;
}).join('/'); }).join("/");
}); });
if (Object.keys(keys).length !== 0) { if (Object.keys(keys).length !== 0) {
throw new Error('Too many keys for route.'); throw new Error("Too many keys for route.");
} }
} }
return '/' + path.join('/'); return "/" + path.join("/");
} }
} }

@ -1,12 +1,12 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import { Action, createAction, createActions } from 'redux-actions'; import { Action, createAction, createActions } from "redux-actions";
import * as EteSync from 'etesync'; import * as EteSync from "etesync";
import { UserInfo } from 'etesync'; import { UserInfo } from "etesync";
import { CredentialsData, CredentialsDataRemote, EntriesData, SettingsType } from './'; import { CredentialsData, CredentialsDataRemote, EntriesData, SettingsType } from "./";
export const { fetchCredentials } = createActions({ export const { fetchCredentials } = createActions({
FETCH_CREDENTIALS: (username: string, password: string, server: string) => { FETCH_CREDENTIALS: (username: string, password: string, server: string) => {
@ -33,7 +33,7 @@ export const { fetchCredentials } = createActions({
}); });
export const logout = createAction( export const logout = createAction(
'LOGOUT', "LOGOUT",
(etesync: CredentialsDataRemote) => { (etesync: CredentialsDataRemote) => {
(async () => { (async () => {
const authenticator = new EteSync.Authenticator(etesync.serviceApiUrl); const authenticator = new EteSync.Authenticator(etesync.serviceApiUrl);
@ -54,7 +54,7 @@ export const { deriveKey } = createActions({
}); });
export const resetKey = createAction( export const resetKey = createAction(
'RESET_KEY', "RESET_KEY",
() => { () => {
return null; return null;
} }
@ -79,7 +79,7 @@ export const { fetchListJournal } = createActions({
}); });
export const addJournal = createAction( export const addJournal = createAction(
'ADD_JOURNAL', "ADD_JOURNAL",
(etesync: CredentialsData, journal: EteSync.Journal) => { (etesync: CredentialsData, journal: EteSync.Journal) => {
const creds = etesync.credentials; const creds = etesync.credentials;
const apiBase = etesync.serviceApiUrl; const apiBase = etesync.serviceApiUrl;
@ -93,7 +93,7 @@ export const addJournal = createAction(
); );
export const updateJournal = createAction( export const updateJournal = createAction(
'UPDATE_JOURNAL', "UPDATE_JOURNAL",
(etesync: CredentialsData, journal: EteSync.Journal) => { (etesync: CredentialsData, journal: EteSync.Journal) => {
const creds = etesync.credentials; const creds = etesync.credentials;
const apiBase = etesync.serviceApiUrl; const apiBase = etesync.serviceApiUrl;
@ -107,7 +107,7 @@ export const updateJournal = createAction(
); );
export const deleteJournal = createAction( export const deleteJournal = createAction(
'DELETE_JOURNAL', "DELETE_JOURNAL",
(etesync: CredentialsData, journal: EteSync.Journal) => { (etesync: CredentialsData, journal: EteSync.Journal) => {
const creds = etesync.credentials; const creds = etesync.credentials;
const apiBase = etesync.serviceApiUrl; const apiBase = etesync.serviceApiUrl;
@ -158,7 +158,7 @@ export const { fetchUserInfo } = createActions({
}); });
export const createUserInfo = createAction( export const createUserInfo = createAction(
'CREATE_USER_INFO', "CREATE_USER_INFO",
(etesync: CredentialsData, userInfo: UserInfo) => { (etesync: CredentialsData, userInfo: UserInfo) => {
const creds = etesync.credentials; const creds = etesync.credentials;
const apiBase = etesync.serviceApiUrl; const apiBase = etesync.serviceApiUrl;
@ -197,14 +197,14 @@ export function fetchAll(etesync: CredentialsData, currentEntries: EntriesData)
export const appendError = createAction( export const appendError = createAction(
'APPEND_ERROR', "APPEND_ERROR",
(_etesync: CredentialsData, error: Error | Error[]) => { (_etesync: CredentialsData, error: Error | Error[]) => {
return Array.isArray(error) ? error : [error]; return Array.isArray(error) ? error : [error];
} }
); );
export const clearErros = createAction( export const clearErros = createAction(
'CLEAR_ERRORS', "CLEAR_ERRORS",
(_etesync: CredentialsData) => { (_etesync: CredentialsData) => {
return true; return true;
} }
@ -212,7 +212,7 @@ export const clearErros = createAction(
// FIXME: Move the rest to their own file // FIXME: Move the rest to their own file
export const setSettings = createAction( export const setSettings = createAction(
'SET_SETTINGS', "SET_SETTINGS",
(settings: Partial<SettingsType>) => { (settings: Partial<SettingsType>) => {
return { ...settings }; return { ...settings };
} }

@ -1,19 +1,19 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as localforage from 'localforage'; import * as localforage from "localforage";
import { combineReducers } from 'redux'; import { combineReducers } from "redux";
import { createMigrate, persistReducer, createTransform } from 'redux-persist'; import { createMigrate, persistReducer, createTransform } from "redux-persist";
import session from 'redux-persist/lib/storage/session'; import session from "redux-persist/lib/storage/session";
import { List, Map as ImmutableMap } from 'immutable'; import { List, Map as ImmutableMap } from "immutable";
import * as EteSync from 'etesync'; import * as EteSync from "etesync";
import { import {
JournalsData, EntriesData, UserInfoData, JournalsData, EntriesData, UserInfoData,
CredentialsDataRemote, SettingsType, CredentialsDataRemote, SettingsType,
fetchCount, journals, entries, credentials, userInfo, settingsReducer, encryptionKeyReducer, errorsReducer, fetchCount, journals, entries, credentials, userInfo, settingsReducer, encryptionKeyReducer, errorsReducer,
} from './reducers'; } from "./reducers";
export interface StoreState { export interface StoreState {
fetchCount: number; fetchCount: number;
@ -34,14 +34,14 @@ const settingsMigrations = {
...state, ...state,
taskSettings: { taskSettings: {
filterBy: null, filterBy: null,
sortBy: 'smart', sortBy: "smart",
}, },
}; };
}, },
}; };
const settingsPersistConfig = { const settingsPersistConfig = {
key: 'settings', key: "settings",
version: 0, version: 0,
storage: localforage, storage: localforage,
migrate: createMigrate(settingsMigrations, { debug: false }), migrate: createMigrate(settingsMigrations, { debug: false }),
@ -54,14 +54,14 @@ const credentialsMigrations = {
}; };
const credentialsPersistConfig = { const credentialsPersistConfig = {
key: 'credentials', key: "credentials",
version: 0, version: 0,
storage: localforage, storage: localforage,
migrate: createMigrate(credentialsMigrations, { debug: false }), migrate: createMigrate(credentialsMigrations, { debug: false }),
}; };
const encryptionKeyPersistConfig = { const encryptionKeyPersistConfig = {
key: 'encryptionKey', key: "encryptionKey",
storage: session, storage: session,
}; };
@ -127,15 +127,15 @@ const userInfoDeserialize = (state: EteSync.UserInfoJson) => {
}; };
const cacheSerialize = (state: any, key: string | number) => { const cacheSerialize = (state: any, key: string | number) => {
if (key === 'entries') { if (key === "entries") {
const ret = {}; const ret = {};
state.forEach((value: List<EteSync.Entry>, mapKey: string) => { state.forEach((value: List<EteSync.Entry>, mapKey: string) => {
ret[mapKey] = entriesSerialize(value); ret[mapKey] = entriesSerialize(value);
}); });
return ret; return ret;
} else if (key === 'journals') { } else if (key === "journals") {
return journalsSerialize(state); return journalsSerialize(state);
} else if (key === 'userInfo') { } else if (key === "userInfo") {
return userInfoSerialize(state); return userInfoSerialize(state);
} }
@ -143,15 +143,15 @@ const cacheSerialize = (state: any, key: string | number) => {
}; };
const cacheDeserialize = (state: any, key: string | number) => { const cacheDeserialize = (state: any, key: string | number) => {
if (key === 'entries') { if (key === "entries") {
const ret = {}; const ret = {};
Object.keys(state).forEach((mapKey) => { Object.keys(state).forEach((mapKey) => {
ret[mapKey] = entriesDeserialize(state[mapKey]); ret[mapKey] = entriesDeserialize(state[mapKey]);
}); });
return ImmutableMap(ret); return ImmutableMap(ret);
} else if (key === 'journals') { } else if (key === "journals") {
return journalsDeserialize(state); return journalsDeserialize(state);
} else if (key === 'userInfo') { } else if (key === "userInfo") {
return userInfoDeserialize(state); return userInfoDeserialize(state);
} }
@ -176,7 +176,7 @@ const cacheMigrations = {
}; };
const cachePersistConfig = { const cachePersistConfig = {
key: 'cache', key: "cache",
version: 2, version: 2,
storage: localforage, storage: localforage,
transforms: [createTransform(cacheSerialize, cacheDeserialize)], transforms: [createTransform(cacheSerialize, cacheDeserialize)],

@ -1,21 +1,21 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import { addEntries, fetchEntries } from './actions'; import { addEntries, fetchEntries } from "./actions";
import { entries, EntriesData } from './reducers'; import { entries, EntriesData } from "./reducers";
import { Map } from 'immutable'; import { Map } from "immutable";
import * as EteSync from 'etesync'; import * as EteSync from "etesync";
it('Entries reducer', () => { it("Entries reducer", () => {
const jId = '24324324324'; const jId = "24324324324";
let state = Map({}) as EntriesData; let state = Map({}) as EntriesData;
const entry = new EteSync.Entry(); const entry = new EteSync.Entry();
entry.deserialize({ entry.deserialize({
content: 'someContent', content: "someContent",
uid: '6355209e2a2c26a6c1e6e967c2032737d538f602cf912474da83a2902f8a0a83', uid: "6355209e2a2c26a6c1e6e967c2032737d538f602cf912474da83a2902f8a0a83",
}); });
const action = { const action = {

@ -1,25 +1,25 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import { createStore, applyMiddleware } from 'redux'; import { createStore, applyMiddleware } from "redux";
import { persistStore } from 'redux-persist'; import { persistStore } from "redux-persist";
import thunkMiddleware from 'redux-thunk'; import thunkMiddleware from "redux-thunk";
import { createLogger } from 'redux-logger'; import { createLogger } from "redux-logger";
import promiseMiddleware from './promise-middleware'; import promiseMiddleware from "./promise-middleware";
import reducers from './construct'; import reducers from "./construct";
// Workaround babel limitation // Workaround babel limitation
export * from './reducers'; export * from "./reducers";
export * from './construct'; export * from "./construct";
const middleware = [ const middleware = [
thunkMiddleware, thunkMiddleware,
promiseMiddleware, promiseMiddleware,
]; ];
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === "development") {
middleware.push(createLogger()); middleware.push(createLogger());
} }

@ -4,7 +4,7 @@
// Based on: https://github.com/acdlite/redux-promise/blob/master/src/index.js // Based on: https://github.com/acdlite/redux-promise/blob/master/src/index.js
function isPromise(val: any): val is Promise<any> { function isPromise(val: any): val is Promise<any> {
return val && typeof val.then === 'function'; return val && typeof val.then === "function";
} }
export default function promiseMiddleware({ dispatch }: any) { export default function promiseMiddleware({ dispatch }: any) {

@ -1,14 +1,14 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import { Action, ActionMeta, ActionFunctionAny, combineActions, handleAction, handleActions } from 'redux-actions'; import { Action, ActionMeta, ActionFunctionAny, combineActions, handleAction, handleActions } from "redux-actions";
import { shallowEqual } from 'react-redux'; import { shallowEqual } from "react-redux";
import { List, Map as ImmutableMap } from 'immutable'; import { List, Map as ImmutableMap } from "immutable";
import * as EteSync from 'etesync'; import * as EteSync from "etesync";
import * as actions from './actions'; import * as actions from "./actions";
export interface CredentialsDataRemote { export interface CredentialsDataRemote {
serviceApiUrl: string; serviceApiUrl: string;
@ -218,10 +218,10 @@ const fetchActions = [
] as Array<ActionFunctionAny<Action<any>>>; ] as Array<ActionFunctionAny<Action<any>>>;
for (const func in actions) { for (const func in actions) {
if (func.startsWith('fetch') || if (func.startsWith("fetch") ||
func.startsWith('add') || func.startsWith("add") ||
func.startsWith('update') || func.startsWith("update") ||
func.startsWith('delete')) { func.startsWith("delete")) {
fetchActions.push(actions[func]); fetchActions.push(actions[func]);
} }
@ -286,11 +286,11 @@ export const settingsReducer = handleActions(
), ),
}, },
{ {
locale: 'en-gb', locale: "en-gb",
darkMode: false, darkMode: false,
taskSettings: { taskSettings: {
filterBy: null, filterBy: null,
sortBy: 'smart', sortBy: "smart",
}, },
} }
); );

@ -4,7 +4,7 @@
// Disable some style eslint rules for things we can't control // Disable some style eslint rules for things we can't control
/* eslint-disable @typescript-eslint/camelcase, @typescript-eslint/class-name-casing */ /* eslint-disable @typescript-eslint/camelcase, @typescript-eslint/class-name-casing */
declare module 'ical.js' { declare module "ical.js" {
function parse(input: string): any[]; function parse(input: string): any[];
export class helpers { export class helpers {
@ -151,7 +151,7 @@ declare module 'ical.js' {
static public remove(tzid: string): Timezone | null; static public remove(tzid: string): Timezone | null;
} }
export type FrequencyValues = 'YEARLY' | 'MONTHLY' | 'WEEKLY' | 'DAILY' | 'HOURLY' | 'MINUTELY' | 'SECONDLY'; export type FrequencyValues = "YEARLY" | "MONTHLY" | "WEEKLY" | "DAILY" | "HOURLY" | "MINUTELY" | "SECONDLY";
export enum WeekDay { export enum WeekDay {
SU = 1, SU = 1,
@ -191,7 +191,7 @@ declare module 'ical.js' {
public count: number | null; public count: number | null;
public clone(): Recur; public clone(): Recur;
public toJSON(): Omit<RecurData, 'until'> & { until?: string }; public toJSON(): Omit<RecurData, "until"> & { until?: string };
public iterator(startTime?: Time): RecurIterator; public iterator(startTime?: Time): RecurIterator;
public isByCount(): boolean; public isByCount(): boolean;
} }

@ -1,6 +1,6 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
declare module 'redux-persist'; declare module "redux-persist";
declare module 'redux-persist/lib/storage/session'; declare module "redux-persist/lib/storage/session";
declare module 'redux-persist/es/integration/react'; declare module "redux-persist/es/integration/react";

@ -1,12 +1,12 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import * as ReactDOM from 'react-dom'; import * as ReactDOM from "react-dom";
export default (props: {title: string, children?: React.ReactNode | React.ReactNode[]}) => { export default (props: {title: string, children?: React.ReactNode | React.ReactNode[]}) => {
const titleEl = document.querySelector('#appbar-title'); const titleEl = document.querySelector("#appbar-title");
const buttonsEl = document.querySelector('#appbar-buttons'); const buttonsEl = document.querySelector("#appbar-buttons");
return ( return (
<> <>

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
export const Avatar = React.memo((props: { children: React.ReactNode[] | React.ReactNode, size?: number, style?: any }) => { export const Avatar = React.memo((props: { children: React.ReactNode[] | React.ReactNode, size?: number, style?: any }) => {
const size = (props.size) ? props.size : 40; const size = (props.size) ? props.size : 40;
@ -9,12 +9,12 @@ export const Avatar = React.memo((props: { children: React.ReactNode[] | React.R
return ( return (
<div <div
style={{ style={{
backgroundColor: 'grey', backgroundColor: "grey",
color: 'white', color: "white",
display: 'inline-flex', display: "inline-flex",
justifyContent: 'center', justifyContent: "center",
alignItems: 'center', alignItems: "center",
borderRadius: '50%', borderRadius: "50%",
height: size, height: size,
width: size, width: size,
...props.style, ...props.style,

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
interface PropsType { interface PropsType {
color: string; color: string;

@ -1,11 +1,11 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import ColorBox from './ColorBox'; import ColorBox from "./ColorBox";
import { colorHtmlToInt } from '../journal-processors'; import { colorHtmlToInt } from "../journal-processors";
import { TextField, ButtonBase } from '@material-ui/core'; import { TextField, ButtonBase } from "@material-ui/core";
interface PropsType { interface PropsType {
color: string; color: string;
@ -20,18 +20,18 @@ interface PropsType {
export default function ColorPicker(props: PropsType) { export default function ColorPicker(props: PropsType) {
const colors = [ const colors = [
[ [
'#F44336', "#F44336",
'#E91E63', "#E91E63",
'#673AB7', "#673AB7",
'#3F51B5', "#3F51B5",
'#2196F3', "#2196F3",
], ],
[ [
'#03A9F4', "#03A9F4",
'#4CAF50', "#4CAF50",
'#8BC34A', "#8BC34A",
'#FFEB3B', "#FFEB3B",
'#FF9800', "#FF9800",
], ],
]; ];
const color = props.color; const color = props.color;
@ -39,7 +39,7 @@ export default function ColorPicker(props: PropsType) {
return ( return (
<div> <div>
{colors.map((colorGroup, idx) => ( {colors.map((colorGroup, idx) => (
<div key={idx} style={{ flex: 1, flexDirection: 'row', justifyContent: 'space-between' }}> <div key={idx} style={{ flex: 1, flexDirection: "row", justifyContent: "space-between" }}>
{colorGroup.map((colorOption) => ( {colorGroup.map((colorOption) => (
<ButtonBase <ButtonBase
style={{ margin: 5, borderRadius: 36 / 2 }} style={{ margin: 5, borderRadius: 36 / 2 }}
@ -51,9 +51,9 @@ export default function ColorPicker(props: PropsType) {
))} ))}
</div> </div>
))} ))}
<div style={{ flex: 1, alignItems: 'center', flexDirection: 'row', margin: 5 }}> <div style={{ flex: 1, alignItems: "center", flexDirection: "row", margin: 5 }}>
<ColorBox <ColorBox
style={{ display: 'inline-block' }} style={{ display: "inline-block" }}
size={36} size={36}
color={(color && colorHtmlToInt(color)) ? color : props.defaultColor} color={(color && colorHtmlToInt(color)) ? color : props.defaultColor}
/> />
@ -61,8 +61,8 @@ export default function ColorPicker(props: PropsType) {
style={{ marginLeft: 10, flex: 1 }} style={{ marginLeft: 10, flex: 1 }}
error={!!props.error} error={!!props.error}
onChange={(event: React.FormEvent<{ value: string }>) => props.onChange(event.currentTarget.value)} onChange={(event: React.FormEvent<{ value: string }>) => props.onChange(event.currentTarget.value)}
placeholder={props.placeholder ?? 'E.g. #aabbcc'} placeholder={props.placeholder ?? "E.g. #aabbcc"}
label={props.label ?? 'Color'} label={props.label ?? "Color"}
value={color} value={color}
helperText={props.error} helperText={props.error}
/> />

@ -1,12 +1,12 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import { makeStyles } from '@material-ui/core/styles'; import { makeStyles } from "@material-ui/core/styles";
import Radio from '@material-ui/core/Radio'; import Radio from "@material-ui/core/Radio";
import { Omit } from '@material-ui/types'; import { Omit } from "@material-ui/types";
import FormControlLabel, { FormControlLabelProps } from '@material-ui/core/FormControlLabel'; import FormControlLabel, { FormControlLabelProps } from "@material-ui/core/FormControlLabel";
interface Props { interface Props {
color: string; color: string;
@ -19,7 +19,7 @@ const useStyles = makeStyles({
}, },
}); });
export default function ColoredRadio(props: Props & Omit<FormControlLabelProps, keyof Props | 'control'>) { export default function ColoredRadio(props: Props & Omit<FormControlLabelProps, keyof Props | "control">) {
const { color, label, value, ...other } = props; const { color, label, value, ...other } = props;
const { root } = useStyles(props); const { root } = useStyles(props);

@ -1,13 +1,13 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import Dialog from '@material-ui/core/Dialog'; import Dialog from "@material-ui/core/Dialog";
import DialogTitle from '@material-ui/core/DialogTitle'; import DialogTitle from "@material-ui/core/DialogTitle";
import DialogContent from '@material-ui/core/DialogContent'; import DialogContent from "@material-ui/core/DialogContent";
import DialogActions from '@material-ui/core/DialogActions'; import DialogActions from "@material-ui/core/DialogActions";
import Button from '@material-ui/core/Button'; import Button from "@material-ui/core/Button";
export default React.memo((_props: any) => { export default React.memo((_props: any) => {
const { const {
@ -41,7 +41,7 @@ export default React.memo((_props: any) => {
color="primary" color="primary"
onClick={onOk} onClick={onOk}
> >
{labelOk || 'Confirm'} {labelOk || "Confirm"}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>

@ -1,10 +1,10 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import Paper from '@material-ui/core/Paper'; import Paper from "@material-ui/core/Paper";
import './Container.css'; import "./Container.css";
export default (props: {style?: any, children: any}) => ( export default (props: {style?: any, children: any}) => (
<div className="Container" style={props.style}> <div className="Container" style={props.style}>

@ -1,12 +1,12 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import MomentUtils from '@date-io/moment'; import MomentUtils from "@date-io/moment";
import { MuiPickersUtilsProvider, KeyboardDatePicker, KeyboardDateTimePicker } from '@material-ui/pickers'; import { MuiPickersUtilsProvider, KeyboardDatePicker, KeyboardDateTimePicker } from "@material-ui/pickers";
import moment from 'moment'; import moment from "moment";
interface PropsType { interface PropsType {
placeholder: string; placeholder: string;
@ -23,7 +23,7 @@ class DateTimePicker extends React.PureComponent<PropsType> {
public render() { public render() {
const Picker = (this.props.dateOnly) ? KeyboardDatePicker : KeyboardDateTimePicker; const Picker = (this.props.dateOnly) ? KeyboardDatePicker : KeyboardDateTimePicker;
const dateFormat = (this.props.dateOnly) ? 'DD/MM/YYYY' : 'DD/MM/YYYY HH:mm'; const dateFormat = (this.props.dateOnly) ? "DD/MM/YYYY" : "DD/MM/YYYY HH:mm";
return ( return (
<MuiPickersUtilsProvider utils={MomentUtils}> <MuiPickersUtilsProvider utils={MomentUtils}>
<Picker <Picker
@ -33,7 +33,7 @@ class DateTimePicker extends React.PureComponent<PropsType> {
ampm={false} ampm={false}
showTodayButton showTodayButton
KeyboardButtonProps={{ KeyboardButtonProps={{
'aria-label': 'change date', "aria-label": "change date",
}} }}
/> />
</MuiPickersUtilsProvider> </MuiPickersUtilsProvider>

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
export const ExternalLink = React.memo(({ children, ...props }: any) => ( export const ExternalLink = React.memo(({ children, ...props }: any) => (
<a target="_blank" rel="noopener noreferrer" {...props}> <a target="_blank" rel="noopener noreferrer" {...props}>

@ -1,17 +1,17 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import { createStyles, makeStyles } from '@material-ui/core/styles'; import { createStyles, makeStyles } from "@material-ui/core/styles";
import MuiList from '@material-ui/core/List'; import MuiList from "@material-ui/core/List";
import MuiListItem from '@material-ui/core/ListItem'; import MuiListItem from "@material-ui/core/ListItem";
import MuiListSubheader from '@material-ui/core/ListSubheader'; import MuiListSubheader from "@material-ui/core/ListSubheader";
import ListItemIcon from '@material-ui/core/ListItemIcon'; import ListItemIcon from "@material-ui/core/ListItemIcon";
import ListItemText from '@material-ui/core/ListItemText'; import ListItemText from "@material-ui/core/ListItemText";
import Divider from '@material-ui/core/Divider'; import Divider from "@material-ui/core/Divider";
import ExternalLink from './ExternalLink'; import ExternalLink from "./ExternalLink";
const useStyles = makeStyles((theme) => (createStyles({ const useStyles = makeStyles((theme) => (createStyles({
inset: { inset: {
@ -46,7 +46,7 @@ interface ListItemPropsType {
insetChildren?: boolean; insetChildren?: boolean;
nestedItems?: React.ReactNode[]; nestedItems?: React.ReactNode[];
selected?: boolean; selected?: boolean;
secondaryTextColor?: 'initial' | 'inherit' | 'primary' | 'secondary' | 'textPrimary' | 'textSecondary' | 'error'; secondaryTextColor?: "initial" | "inherit" | "primary" | "secondary" | "textPrimary" | "textSecondary" | "error";
} }
export const ListItem = React.memo(function ListItem(_props: ListItemPropsType) { export const ListItem = React.memo(function ListItem(_props: ListItemPropsType) {
@ -70,7 +70,7 @@ export const ListItem = React.memo(function ListItem(_props: ListItemPropsType)
button: true, button: true,
href, href,
onClick, onClick,
component: (href) ? ExternalLink : 'div', component: (href) ? ExternalLink : "div",
} : undefined; } : undefined;
return ( return (

@ -1,8 +1,8 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import CircularProgress from '@material-ui/core/CircularProgress'; import CircularProgress from "@material-ui/core/CircularProgress";
export default (props: any) => { export default (props: any) => {
return ( return (

@ -1,19 +1,19 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import MuiMenu, { MenuProps } from '@material-ui/core/Menu'; import MuiMenu, { MenuProps } from "@material-ui/core/Menu";
import { PopoverOrigin } from '@material-ui/core/Popover'; import { PopoverOrigin } from "@material-ui/core/Popover";
const anchorOrigin: PopoverOrigin = { const anchorOrigin: PopoverOrigin = {
vertical: 'bottom', vertical: "bottom",
horizontal: 'right', horizontal: "right",
}; };
const transferOrigin: PopoverOrigin = { const transferOrigin: PopoverOrigin = {
vertical: 'top', vertical: "top",
horizontal: 'right', horizontal: "right",
}; };
export default function Menu(props: MenuProps) { export default function Menu(props: MenuProps) {

@ -1,11 +1,11 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
export const PrettyError = React.memo((props: any) => ( export const PrettyError = React.memo((props: any) => (
<div> <div>
<pre style={{ fontWeight: 'bold' }}> <pre style={{ fontWeight: "bold" }}>
{props.error.message} {props.error.message}
</pre> </pre>

@ -1,10 +1,10 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import sjcl from 'sjcl'; import sjcl from "sjcl";
import { byte, base64 } from 'etesync'; import { byte, base64 } from "etesync";
function byteArray4ToNumber(bytes: byte[], offset: number) { function byteArray4ToNumber(bytes: byte[], offset: number) {
// tslint:disable:no-bitwise // tslint:disable:no-bitwise
@ -18,7 +18,7 @@ function byteArray4ToNumber(bytes: byte[], offset: number) {
function getEncodedChunk(publicKey: byte[], offset: number) { function getEncodedChunk(publicKey: byte[], offset: number) {
const chunk = byteArray4ToNumber(publicKey, offset) % 100000; const chunk = byteArray4ToNumber(publicKey, offset) % 100000;
return chunk.toString().padStart(5, '0'); return chunk.toString().padStart(5, "0");
} }
interface PropsType { interface PropsType {
@ -31,12 +31,12 @@ class PrettyFingerprint extends React.PureComponent<PropsType> {
sjcl.hash.sha256.hash(sjcl.codec.base64.toBits(this.props.publicKey)) sjcl.hash.sha256.hash(sjcl.codec.base64.toBits(this.props.publicKey))
); );
const spacing = ' '; const spacing = " ";
const prettyPublicKey = const prettyPublicKey =
getEncodedChunk(fingerprint, 0) + spacing + getEncodedChunk(fingerprint, 0) + spacing +
getEncodedChunk(fingerprint, 4) + spacing + getEncodedChunk(fingerprint, 4) + spacing +
getEncodedChunk(fingerprint, 8) + spacing + getEncodedChunk(fingerprint, 8) + spacing +
getEncodedChunk(fingerprint, 12) + '\n' + getEncodedChunk(fingerprint, 12) + "\n" +
getEncodedChunk(fingerprint, 16) + spacing + getEncodedChunk(fingerprint, 16) + spacing +
getEncodedChunk(fingerprint, 20) + spacing + getEncodedChunk(fingerprint, 20) + spacing +
getEncodedChunk(fingerprint, 24) + spacing + getEncodedChunk(fingerprint, 24) + spacing +

@ -1,10 +1,10 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import { TextField, Select, MenuItem, InputLabel, FormControl } from '@material-ui/core'; import { TextField, Select, MenuItem, InputLabel, FormControl } from "@material-ui/core";
import DateTimePicker from '../widgets/DateTimePicker'; import DateTimePicker from "../widgets/DateTimePicker";
import * as ICAL from 'ical.js'; import * as ICAL from "ical.js";
export type RRuleOptions = ICAL.RecurData; export type RRuleOptions = ICAL.RecurData;
@ -53,13 +53,13 @@ const menuItemsEnds = [Ends.Forever, Ends.Until, Ends.After].map((key) => {
let displayhName; let displayhName;
switch (key) { switch (key) {
case Ends.Forever: case Ends.Forever:
displayhName = 'Forever'; displayhName = "Forever";
break; break;
case Ends.Until: case Ends.Until:
displayhName = 'Until'; displayhName = "Until";
break; break;
case Ends.After: case Ends.After:
displayhName = 'For'; displayhName = "For";
break; break;
} }
@ -67,7 +67,7 @@ const menuItemsEnds = [Ends.Forever, Ends.Until, Ends.After].map((key) => {
<MenuItem key={key} value={key}>{displayhName}</MenuItem> <MenuItem key={key} value={key}>{displayhName}</MenuItem>
); );
}); });
const menuItemsFrequency = ['YEARLY', 'MONTHLY', 'WEEKLY', 'DAILY'].map((value) => { const menuItemsFrequency = ["YEARLY", "MONTHLY", "WEEKLY", "DAILY"].map((value) => {
return ( return (
<MenuItem key={value} value={value}>{value.toLowerCase()}</MenuItem> <MenuItem key={value} value={value}>{value.toLowerCase()}</MenuItem>
); );
@ -108,7 +108,7 @@ function sanitizeByDay(item: string | string[] | undefined) {
} }
const styles = { const styles = {
multiSelect: { minWidth: 120, maxWidth: '100%' }, multiSelect: { minWidth: 120, maxWidth: "100%" },
width: { width: 120 }, width: { width: 120 },
}; };
@ -146,12 +146,12 @@ export default function RRule(props: PropsType) {
} }
} }
return ( return (
<div style={{ paddingLeft: '2em' }}> <div style={{ paddingLeft: "2em" }}>
<div style={{ display: 'flex', alignItems: 'center' }}> <div style={{ display: "flex", alignItems: "center" }}>
<span>Repeat</span> <span>Repeat</span>
<Select <Select
value={options.freq} value={options.freq}
style={{ marginLeft: '0.5em' }} style={{ marginLeft: "0.5em" }}
onChange={(event: React.FormEvent<{ value: unknown }>) => { onChange={(event: React.FormEvent<{ value: unknown }>) => {
const freq = (event.target as HTMLSelectElement).value as ICAL.FrequencyValues; const freq = (event.target as HTMLSelectElement).value as ICAL.FrequencyValues;
updateRule({ freq: freq }, true); updateRule({ freq: freq }, true);
@ -159,16 +159,16 @@ export default function RRule(props: PropsType) {
> >
{menuItemsFrequency} {menuItemsFrequency}
</Select> </Select>
<span style={{ marginLeft: '0.5em' }}>every</span> <span style={{ marginLeft: "0.5em" }}>every</span>
<TextField <TextField
style={{ marginLeft: '0.5em', width: '2.2em' }} style={{ marginLeft: "0.5em", width: "2.2em" }}
type="number" type="number"
inputProps={{ min: 1, max: 99 }} inputProps={{ min: 1, max: 99 }}
value={options.interval ?? 1} value={options.interval ?? 1}
onChange={(event: React.FormEvent<{ value: unknown }>) => { onChange={(event: React.FormEvent<{ value: unknown }>) => {
event.preventDefault(); event.preventDefault();
const inputNode = event.currentTarget as HTMLInputElement; const inputNode = event.currentTarget as HTMLInputElement;
if (inputNode.value === '') { if (inputNode.value === "") {
updateRule({ interval: 1 }); updateRule({ interval: 1 });
} else if (inputNode.valueAsNumber) { } else if (inputNode.valueAsNumber) {
updateRule({ interval: inputNode.valueAsNumber }); updateRule({ interval: inputNode.valueAsNumber });
@ -176,7 +176,7 @@ export default function RRule(props: PropsType) {
}} }}
/> />
</div> </div>
{(options.freq && options.freq !== 'DAILY') && {(options.freq && options.freq !== "DAILY") &&
<div> <div>
<FormControl> <FormControl>
<InputLabel>Weekdays</InputLabel> <InputLabel>Weekdays</InputLabel>
@ -194,8 +194,8 @@ export default function RRule(props: PropsType) {
</div> </div>
} }
{!disableComplex && ( {!disableComplex && (
<div style={{ display: 'flex' }}> <div style={{ display: "flex" }}>
{(options.freq === 'MONTHLY') && {(options.freq === "MONTHLY") &&
<Select value={options.bysetpos ? MonthRepeat.Bysetpos : MonthRepeat.Bymonthday} <Select value={options.bysetpos ? MonthRepeat.Bysetpos : MonthRepeat.Bymonthday}
onChange={(event: React.FormEvent<{ value: unknown }>) => { onChange={(event: React.FormEvent<{ value: unknown }>) => {
const value = Number((event.target as HTMLInputElement).value); const value = Number((event.target as HTMLInputElement).value);
@ -225,7 +225,7 @@ export default function RRule(props: PropsType) {
)} )}
<div> <div>
{options.freq === 'MONTHLY' && {options.freq === "MONTHLY" &&
<TextField <TextField
type="number" type="number"
value={options.bymonthday ? options.bymonthday[0] : undefined} value={options.bymonthday ? options.bymonthday[0] : undefined}
@ -236,7 +236,7 @@ export default function RRule(props: PropsType) {
event.preventDefault(); event.preventDefault();
const value = (event.currentTarget as HTMLInputElement).value; const value = (event.currentTarget as HTMLInputElement).value;
const numberValue = Number(value); const numberValue = Number(value);
if (value === '') { if (value === "") {
updateRule({ bymonthday: undefined }); updateRule({ bymonthday: undefined });
} else if (numberValue < 32 && numberValue > 0) { } else if (numberValue < 32 && numberValue > 0) {
updateRule({ bymonthday: [numberValue] }); updateRule({ bymonthday: [numberValue] });
@ -244,7 +244,7 @@ export default function RRule(props: PropsType) {
}} }}
/> />
} }
{options.freq === 'YEARLY' && {options.freq === "YEARLY" &&
<div> <div>
<FormControl> <FormControl>
<InputLabel>Months</InputLabel> <InputLabel>Months</InputLabel>
@ -262,10 +262,10 @@ export default function RRule(props: PropsType) {
</div> </div>
} }
</div> </div>
<div style={{ display: 'inline-flex', alignItems: 'center' }}> <div style={{ display: "inline-flex", alignItems: "center" }}>
<Select <Select
value={getEnds()} value={getEnds()}
style={{ marginRight: '0.5em' }} style={{ marginRight: "0.5em" }}
onChange={(event: React.FormEvent<{ value: unknown }>) => { onChange={(event: React.FormEvent<{ value: unknown }>) => {
const value = Number((event.target as HTMLSelectElement).value); const value = Number((event.target as HTMLSelectElement).value);
let updateOptions; let updateOptions;
@ -296,19 +296,19 @@ export default function RRule(props: PropsType) {
<TextField <TextField
type="number" type="number"
value={options.count} value={options.count}
style={{ width: '4em' }} style={{ width: "4em" }}
inputProps={{ min: 1, step: 1 }} inputProps={{ min: 1, step: 1 }}
onChange={(event: React.FormEvent<{ value: unknown }>) => { onChange={(event: React.FormEvent<{ value: unknown }>) => {
event.preventDefault(); event.preventDefault();
const inputNode = event.currentTarget as HTMLInputElement; const inputNode = event.currentTarget as HTMLInputElement;
if (inputNode.value === '') { if (inputNode.value === "") {
updateRule({ count: 1 }); updateRule({ count: 1 });
} else if (inputNode.valueAsNumber) { } else if (inputNode.valueAsNumber) {
updateRule({ count: inputNode.valueAsNumber }); updateRule({ count: inputNode.valueAsNumber });
} }
}} }}
/> />
<span style={{ marginLeft: '0.5em' }}>events</span> <span style={{ marginLeft: "0.5em" }}>events</span>
</> </>
} }
</div> </div>

@ -1,12 +1,12 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import Autocomplete from '@material-ui/lab/Autocomplete'; import Autocomplete from "@material-ui/lab/Autocomplete";
import TextField from '@material-ui/core/TextField'; import TextField from "@material-ui/core/TextField";
import * as zones from '../data/zones.json'; import * as zones from "../data/zones.json";
const zonelist = Object.keys(zones.zones).sort(); const zonelist = Object.keys(zones.zones).sort();
interface PropsType { interface PropsType {
@ -21,7 +21,7 @@ export default React.memo(function TimezonePicker(props: PropsType) {
options={zonelist} options={zonelist}
value={props.value} value={props.value}
onChange={(_e: any, value: string) => props.onChange(value)} onChange={(_e: any, value: string) => props.onChange(value)}
getOptionLabel={(option) => option.replace('_', ' ')} getOptionLabel={(option) => option.replace("_", " ")}
style={props.style} style={props.style}
renderInput={(params) => ( renderInput={(params) => (
<TextField {...params} label="Timezone" fullWidth /> <TextField {...params} label="Timezone" fullWidth />

@ -1,16 +1,16 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import Snackbar from '@material-ui/core/Snackbar'; import Snackbar from "@material-ui/core/Snackbar";
import Alert from '@material-ui/lab/Alert'; import Alert from "@material-ui/lab/Alert";
export interface PropsType { export interface PropsType {
open: boolean; open: boolean;
children: React.ReactNode; children: React.ReactNode;
onClose?: (event?: React.SyntheticEvent, reason?: string) => void; onClose?: (event?: React.SyntheticEvent, reason?: string) => void;
severity?: 'error' | 'info' | 'success' | 'warning'; severity?: "error" | "info" | "success" | "warning";
autoHideDuration?: number; autoHideDuration?: number;
} }

@ -1,9 +1,9 @@
// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-FileCopyrightText: © 2017 EteSync Authors
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react'; import * as React from "react";
import './withSpin.css'; import "./withSpin.css";
const withSpin = (Component: any) => { const withSpin = (Component: any) => {
return React.memo((_props: any) => { return React.memo((_props: any) => {
@ -12,7 +12,7 @@ const withSpin = (Component: any) => {
...props ...props
} = _props; } = _props;
return ( return (
<Component {...props} className={spin ? 'withSpin-spin' : ''} /> <Component {...props} className={spin ? "withSpin-spin" : ""} />
); );
}); });
}; };

Loading…
Cancel
Save