mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-22 14:39:34 +01:00
feat: improve UI of all dialogs
This commit is contained in:
@@ -6,7 +6,7 @@ import { store as editorStore } from "../stores/editor-store";
|
||||
import { store as trashStore } from "../stores/trash-store";
|
||||
import { db } from "./index";
|
||||
import { showMoveNoteDialog } from "../components/dialogs/movenotedialog";
|
||||
import { confirm } from "../components/dialogs/confirm";
|
||||
import { confirm, showDeleteConfirmation } from "../components/dialogs/confirm";
|
||||
|
||||
function createOption(key, icon, onClick) {
|
||||
return {
|
||||
@@ -26,12 +26,10 @@ function createOptions(options = []) {
|
||||
const DeleteOption = createOption("deleteOption", Icon.Trash, async function (
|
||||
state
|
||||
) {
|
||||
if (
|
||||
!(await confirm(Icon.Trash, "Delete", "Are you sure you want to proceed?"))
|
||||
)
|
||||
return;
|
||||
const item = state.selectedItems[0];
|
||||
|
||||
if (!(await showDeleteConfirmation(item.type, true))) return;
|
||||
|
||||
var isAnyNoteOpened = false;
|
||||
const items = state.selectedItems.map((item) => {
|
||||
if (item.id === editorStore.get().session.id) isAnyNoteOpened = true;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { Flex, Box, Text, Button as RebassButton } from "rebass";
|
||||
import { Flex, Box, Button as RebassButton } from "rebass";
|
||||
import { Input } from "@rebass/forms";
|
||||
import * as Icon from "../icons";
|
||||
import Dialog, { showDialog } from "./dialog";
|
||||
@@ -86,10 +86,13 @@ class AddNotebookDialog extends React.Component {
|
||||
return (
|
||||
<Dialog
|
||||
isOpen={props.isOpen}
|
||||
title="Notebook"
|
||||
title={props.edit ? "Edit this Notebook" : "Create a Notebook"}
|
||||
description={
|
||||
props.edit ? "" : "Notebooks are the best way to organize your notes."
|
||||
}
|
||||
icon={Icon.Notebook}
|
||||
positiveButton={{
|
||||
text: props.edit ? "Edit" : "Add",
|
||||
text: "Create notebook",
|
||||
onClick: () => {
|
||||
props.onDone({
|
||||
title: this.title,
|
||||
@@ -104,7 +107,7 @@ class AddNotebookDialog extends React.Component {
|
||||
}}
|
||||
negativeButton={{ text: "Cancel", onClick: props.close }}
|
||||
>
|
||||
<Box my={1}>
|
||||
<Box>
|
||||
<Input
|
||||
autoFocus
|
||||
onChange={(e) => (this.title = e.target.value)}
|
||||
@@ -112,19 +115,16 @@ class AddNotebookDialog extends React.Component {
|
||||
defaultValue={this.title}
|
||||
/>
|
||||
<Input
|
||||
sx={{ marginTop: 1 }}
|
||||
sx={{ marginTop: 2 }}
|
||||
onChange={(e) => (this.description = e.target.value)}
|
||||
placeholder="Enter description (optional)"
|
||||
defaultValue={this.description}
|
||||
/>
|
||||
<Text variant="body" fontWeight="bold" my={1}>
|
||||
Topics (optional):
|
||||
</Text>
|
||||
<Box
|
||||
mt={2}
|
||||
sx={{
|
||||
maxHeight: this.MAX_AVAILABLE_HEIGHT,
|
||||
overflowY: "auto",
|
||||
marginBottom: 1,
|
||||
}}
|
||||
>
|
||||
{this.state.topics.map(
|
||||
@@ -164,7 +164,7 @@ class AddNotebookDialog extends React.Component {
|
||||
/>
|
||||
<RebassButton
|
||||
variant="tertiary"
|
||||
sx={{ marginLeft: 1 }}
|
||||
sx={{ marginLeft: 2 }}
|
||||
px={2}
|
||||
py={1}
|
||||
onClick={() => this.performActionOnTopic(index)}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from "react";
|
||||
import { Box, Text } from "rebass";
|
||||
import Dialog, { showDialog } from "./dialog";
|
||||
import * as Icon from "../icons";
|
||||
|
||||
function Confirm(props) {
|
||||
return (
|
||||
@@ -8,14 +9,15 @@ function Confirm(props) {
|
||||
isOpen={true}
|
||||
title={props.title}
|
||||
icon={props.icon}
|
||||
description={props.subtitle}
|
||||
positiveButton={{
|
||||
text: "Yes",
|
||||
text: props.yesText,
|
||||
onClick: props.onYes,
|
||||
}}
|
||||
negativeButton={{ text: "No", onClick: props.onNo }}
|
||||
negativeButton={{ text: props.noText, onClick: props.onNo }}
|
||||
>
|
||||
<Box my={1}>
|
||||
<Text textAlign="center" variant="body">
|
||||
<Box my={2}>
|
||||
<Text as="span" variant="body" fontSize={18}>
|
||||
{props.message}
|
||||
</Text>
|
||||
</Box>
|
||||
@@ -23,14 +25,67 @@ function Confirm(props) {
|
||||
);
|
||||
}
|
||||
|
||||
export function confirm(icon, title, message) {
|
||||
export function confirm(icon, { title, subtitle, message, yesText, noText }) {
|
||||
return showDialog((perform) => (
|
||||
<Confirm
|
||||
title={title}
|
||||
subtitle={subtitle}
|
||||
message={message}
|
||||
yesText={yesText}
|
||||
noText={noText}
|
||||
icon={icon}
|
||||
onNo={() => perform(false)}
|
||||
onYes={() => perform(true)}
|
||||
/>
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {"note"|"notebook"} type
|
||||
*/
|
||||
export function showDeleteConfirmation(type, multi = false) {
|
||||
let noun = type === "note" ? "Note" : "Notebook";
|
||||
if (multi) noun += "s";
|
||||
let lowerCaseNoun = noun.toLowerCase();
|
||||
|
||||
let [firstPronoun, secondPronoun] = multi
|
||||
? ["these", "they"]
|
||||
: ["this", "it"];
|
||||
|
||||
return confirm(Icon.Trash, {
|
||||
title: `Delete ${noun}`,
|
||||
subtitle: `Are you sure you want to delete ${firstPronoun} ${lowerCaseNoun}?`,
|
||||
message: (
|
||||
<Text as="span">
|
||||
The {lowerCaseNoun} will be{" "}
|
||||
<Text as="span" color="primary">
|
||||
kept in your Trash for 7 days
|
||||
</Text>{" "}
|
||||
after which {secondPronoun} will be permanently removed.
|
||||
</Text>
|
||||
),
|
||||
yesText: `Delete ${lowerCaseNoun}`,
|
||||
noText: "Cancel",
|
||||
});
|
||||
}
|
||||
|
||||
export function showMultiDeleteConfirmation(type) {
|
||||
let noun = type === "note" ? "Notes" : "Notebooks";
|
||||
|
||||
return confirm(Icon.Trash, {
|
||||
title: `Delete these ${noun}`,
|
||||
subtitle: `Are you sure you want to delete these ${type}s?`,
|
||||
message: (
|
||||
<Text as="span">
|
||||
These {type}s will be{" "}
|
||||
<Text as="span" color="primary">
|
||||
kept in your Trash for 7 days
|
||||
</Text>{" "}
|
||||
after which they will be permanently removed.
|
||||
</Text>
|
||||
),
|
||||
yesText: `Delete these ${type}s`,
|
||||
noText: "Cancel",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,85 +5,91 @@ import ThemeProvider from "../theme-provider";
|
||||
import * as Icon from "../icons";
|
||||
import Modal from "react-modal";
|
||||
import useMobile from "../../utils/use-mobile";
|
||||
import { useTheme } from "emotion-theming";
|
||||
|
||||
function Dialog(props) {
|
||||
const isMobile = useMobile();
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<ThemeProvider>
|
||||
{(theme) => (
|
||||
<Modal
|
||||
isOpen={props.isOpen || false}
|
||||
shouldCloseOnOverlayClick={true}
|
||||
onRequestClose={props?.negativeButton?.onClick}
|
||||
style={{
|
||||
content: {
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
right: "auto",
|
||||
bottom: "auto",
|
||||
marginRight: "-50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
borderWidth: 0,
|
||||
borderRadius: theme.radii["default"],
|
||||
backgroundColor: theme.colors.background,
|
||||
color: theme.colors.text,
|
||||
//boxShadow: theme.shadows["3"],
|
||||
width: isMobile ? "80%" : "25%",
|
||||
paddingRight: 20,
|
||||
paddingLeft: 20,
|
||||
overflowY: "hidden",
|
||||
},
|
||||
overlay: {
|
||||
zIndex: 999,
|
||||
background: theme.colors.overlay,
|
||||
},
|
||||
}}
|
||||
<Modal
|
||||
isOpen={props.isOpen || false}
|
||||
shouldCloseOnOverlayClick={true}
|
||||
onRequestClose={props?.negativeButton?.onClick}
|
||||
style={{
|
||||
content: {
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
right: "auto",
|
||||
bottom: "auto",
|
||||
marginRight: "-50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
borderWidth: 0,
|
||||
borderRadius: theme.radii["default"],
|
||||
backgroundColor: theme.colors.background,
|
||||
color: theme.colors.text,
|
||||
boxShadow: "4px 5px 18px 2px #00000038",
|
||||
width: isMobile ? "80%" : "30%",
|
||||
padding: 0,
|
||||
overflowY: "hidden",
|
||||
},
|
||||
overlay: {
|
||||
zIndex: 999,
|
||||
background: theme.colors.overlay,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Flex p={30} flexDirection="column">
|
||||
<Flex
|
||||
variant="columnCenter"
|
||||
pb={2}
|
||||
mb={3}
|
||||
sx={{ borderBottom: "1px solid", borderColor: "border" }}
|
||||
>
|
||||
<Flex flexDirection="column">
|
||||
<Flex variant="rowCenter" pb={2}>
|
||||
<props.icon size={props.iconSize || 38} color="primary" />
|
||||
<Text variant="heading" color="primary" mx={1}>
|
||||
{props.title}
|
||||
</Text>
|
||||
</Flex>
|
||||
{props.children}
|
||||
<Flex variant="rowCenter" mt={3}>
|
||||
{props.positiveButton && (
|
||||
<RebassButton
|
||||
variant="primary"
|
||||
sx={{ opacity: props.positiveButton.disabled ? 0.7 : 1 }}
|
||||
mx={1}
|
||||
width={"50%"}
|
||||
disabled={props.positiveButton.disabled || false}
|
||||
onClick={
|
||||
!props.positiveButton.disabled
|
||||
? props.positiveButton.onClick
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{props.positiveButton.loading ? (
|
||||
<Icon.Loading rotate={true} color="static" />
|
||||
) : (
|
||||
props.positiveButton.text || "OK"
|
||||
)}
|
||||
</RebassButton>
|
||||
<props.icon size={props.iconSize || 38} color="primary" />
|
||||
<Text variant="heading" textAlign="center" color="text" mx={1} mt={1}>
|
||||
{props.title}
|
||||
</Text>
|
||||
<Text variant="body" textAlign="center" color="gray" mx={1} mt={1}>
|
||||
{props.description}
|
||||
</Text>
|
||||
</Flex>
|
||||
{props.children}
|
||||
<Flex
|
||||
sx={{ justifyContent: props.buttonsAlignment || "flex-end" }}
|
||||
mt={3}
|
||||
>
|
||||
{props.positiveButton && (
|
||||
<RebassButton
|
||||
variant="primary"
|
||||
sx={{ opacity: props.positiveButton.disabled ? 0.7 : 1 }}
|
||||
mx={1}
|
||||
disabled={props.positiveButton.disabled || false}
|
||||
onClick={
|
||||
!props.positiveButton.disabled
|
||||
? props.positiveButton.onClick
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{props.positiveButton.loading ? (
|
||||
<Icon.Loading rotate={true} color="static" />
|
||||
) : (
|
||||
props.positiveButton.text || "OK"
|
||||
)}
|
||||
|
||||
{props.negativeButton && (
|
||||
<RebassButton
|
||||
variant="secondary"
|
||||
width={"50%"}
|
||||
onClick={props.negativeButton.onClick}
|
||||
>
|
||||
{props.negativeButton.text || "Cancel"}
|
||||
</RebassButton>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Modal>
|
||||
)}
|
||||
</ThemeProvider>
|
||||
</RebassButton>
|
||||
)}
|
||||
{props.negativeButton && (
|
||||
<RebassButton
|
||||
variant="secondary"
|
||||
onClick={props.negativeButton.onClick}
|
||||
>
|
||||
{props.negativeButton.text || "Cancel"}
|
||||
</RebassButton>
|
||||
)}
|
||||
</Flex>
|
||||
{props.footer}
|
||||
</Flex>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
export default Dialog;
|
||||
@@ -97,7 +103,7 @@ export function showDialog(dialog) {
|
||||
if (root) {
|
||||
return new Promise((resolve) => {
|
||||
const PropDialog = dialog(perform.bind(this, resolve));
|
||||
ReactDOM.render(PropDialog, root);
|
||||
ReactDOM.render(<ThemeProvider>{PropDialog}</ThemeProvider>, root);
|
||||
});
|
||||
}
|
||||
return Promise.reject("No element with id 'dialogContainer'");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { Flex, Button, Text, Box } from "rebass";
|
||||
import { Flex, Button, Text } from "rebass";
|
||||
import { db } from "../../common";
|
||||
import download from "../../utils/download";
|
||||
import { showToast } from "../../utils/toast";
|
||||
@@ -12,38 +12,33 @@ function ExportDialog(props) {
|
||||
isOpen={true}
|
||||
title={props.title}
|
||||
icon={props.icon}
|
||||
negativeButton={{ onClick: props.onClose, text: "Cancel" }}
|
||||
description="You can export your note to Markdown, HTML, or Text."
|
||||
buttonsAlignment="center"
|
||||
negativeButton={{
|
||||
onClick: props.onClose,
|
||||
text: "I don't want to export anymore",
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<Text variant="body" mb={2}>
|
||||
Please choose a format to export the note into:
|
||||
</Text>
|
||||
<Flex my={1} justifyContent="center" alignItems="center">
|
||||
<Button
|
||||
variant="tertiary"
|
||||
mr={2}
|
||||
onClick={() => props.exportNote("html")}
|
||||
>
|
||||
<Icon.HTML size={100} color="dimPrimary" /> HTML
|
||||
</Button>
|
||||
<Button
|
||||
variant="tertiary"
|
||||
mr={2}
|
||||
onClick={() => props.exportNote("md")}
|
||||
>
|
||||
<Icon.Markdown size={100} color="dimPrimary" />
|
||||
Markdown
|
||||
</Button>
|
||||
<Button
|
||||
variant="tertiary"
|
||||
mr={2}
|
||||
onClick={() => props.exportNote("txt")}
|
||||
>
|
||||
<Icon.Text size={100} color="dimPrimary" />
|
||||
Text
|
||||
</Button>
|
||||
</Flex>
|
||||
</Box>
|
||||
<Flex justifyContent="center" alignItems="center">
|
||||
<Button mr={2} onClick={() => props.exportNote("html")}>
|
||||
<Flex variant="rowCenter">
|
||||
<Icon.HTML color="static" />
|
||||
<Text ml={1}>HTML</Text>
|
||||
</Flex>
|
||||
</Button>
|
||||
<Button mr={2} onClick={() => props.exportNote("md")}>
|
||||
<Flex variant="rowCenter">
|
||||
<Icon.Markdown color="static" />
|
||||
<Text ml={1}>Markdown</Text>
|
||||
</Flex>
|
||||
</Button>
|
||||
<Button mr={2} onClick={() => props.exportNote("txt")}>
|
||||
<Flex variant="rowCenter">
|
||||
<Icon.Text color="static" />
|
||||
<Text ml={1}>Text</Text>
|
||||
</Flex>
|
||||
</Button>
|
||||
</Flex>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -51,7 +46,7 @@ function ExportDialog(props) {
|
||||
export function showExportDialog(noteId) {
|
||||
return showDialog((perform) => (
|
||||
<ExportDialog
|
||||
title={"Export Note"}
|
||||
title={"Export your Note"}
|
||||
icon={Icon.Export}
|
||||
onClose={() => perform(false)}
|
||||
exportNote={async (format) => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState } from "react";
|
||||
import { Box, Button, Text } from "rebass";
|
||||
import { Flex, Button, Text } from "rebass";
|
||||
import Input from "../inputs";
|
||||
import * as Icon from "../icons";
|
||||
import Dialog, { showDialog } from "./dialog";
|
||||
@@ -18,19 +18,38 @@ function LoginDialog(props) {
|
||||
return (
|
||||
<Dialog
|
||||
isOpen={true}
|
||||
title={"Login"}
|
||||
title={"Sign in to Your Account"}
|
||||
description={"Signing in allows you to sync your notes across devices."}
|
||||
icon={Icon.Login}
|
||||
onCloseClick={onClose}
|
||||
negativeButton={{ onClick: onClose }}
|
||||
negativeButton={{ text: "I don't want to", onClick: onClose }}
|
||||
positiveButton={{
|
||||
text: "Login",
|
||||
text: "Sign me in",
|
||||
loading: isLoggingIn,
|
||||
disabled: isLoggingIn,
|
||||
onClick: () => submit(setError, form, login, onClose),
|
||||
}}
|
||||
buttonsAlignment="center"
|
||||
footer={
|
||||
<>
|
||||
<Text textAlign="center" color="gray" mt={3}>
|
||||
Don't have an account?
|
||||
</Text>
|
||||
<Button
|
||||
mt={3}
|
||||
variant="anchor"
|
||||
justifySelf="center"
|
||||
alignSelf="center"
|
||||
fontSize="body"
|
||||
onClick={showSignUpDialog}
|
||||
>
|
||||
Sign up here
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Box
|
||||
mt={1}
|
||||
<Flex
|
||||
variant="columnFill"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") submit(setError, form, login, onClose);
|
||||
}}
|
||||
@@ -39,11 +58,11 @@ function LoginDialog(props) {
|
||||
<Input autoFocus name="username" title="Username" />
|
||||
<PasswordInput />
|
||||
</Dropper>
|
||||
<Button variant="anchor" onClick={showSignUpDialog}>
|
||||
Create a New Account
|
||||
</Button>
|
||||
{error && <Text variant="error">{error}</Text>}
|
||||
</Box>
|
||||
{/* <Button variant="anchor" onClick={showSignUpDialog}>
|
||||
I don't have an account
|
||||
</Button> */}
|
||||
</Flex>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { Flex, Box, Text } from "rebass";
|
||||
import { Flex, Box, Text, Button } from "rebass";
|
||||
import { Input } from "@rebass/forms";
|
||||
import * as Icon from "../icons";
|
||||
import { db } from "../../common";
|
||||
@@ -14,73 +14,32 @@ class MoveDialog extends React.Component {
|
||||
selectedNotebook;
|
||||
selectedTopic;
|
||||
state = {
|
||||
items: db.notebooks.all,
|
||||
type: "notebooks",
|
||||
title: "Notebooks",
|
||||
mode: "read",
|
||||
currentOpenedIndex: -1,
|
||||
notebooks: db.notebooks.all,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { items, type, title, mode } = this.state;
|
||||
const { notebooks, currentOpenedIndex } = this.state;
|
||||
const props = this.props;
|
||||
return (
|
||||
<Dialog
|
||||
isOpen={true}
|
||||
title={
|
||||
type === "notes"
|
||||
? "Move Note"
|
||||
: "Select " + toTitleCase(type.substring(0, type.length - 1))
|
||||
}
|
||||
icon={
|
||||
type === "notes"
|
||||
? Icon.Move
|
||||
: type === "topics"
|
||||
? Icon.Topic
|
||||
: Icon.Notebook
|
||||
}
|
||||
positiveButton={{
|
||||
text: "Move",
|
||||
onClick: async () => {
|
||||
try {
|
||||
const notebook = {
|
||||
id: this.selectedNotebook.id,
|
||||
topic: this.selectedTopic,
|
||||
};
|
||||
const note = db.notes.note(props.noteIds[0]).data;
|
||||
await db.notes.move(notebook, ...props.noteIds);
|
||||
showNotesMovedToast(note, props.noteIds, notebook);
|
||||
props.onMove();
|
||||
} catch (e) {
|
||||
showToast("error", e.message);
|
||||
console.error(e);
|
||||
} finally {
|
||||
props.onClose();
|
||||
}
|
||||
},
|
||||
disabled: type !== "notes",
|
||||
title={"Add Note to Notebook"}
|
||||
description={"Organize your notes by adding them to notebooks."}
|
||||
icon={Icon.Move}
|
||||
buttonsAlignment="center"
|
||||
negativeButton={{
|
||||
text: "Get me out of here",
|
||||
onClick: props.onClose,
|
||||
}}
|
||||
negativeButton={{ text: "Cancel", onClick: props.onClose }}
|
||||
>
|
||||
<Box>
|
||||
<Flex alignContent="center" justifyContent="space-between" my={1}>
|
||||
<Flex>
|
||||
<Text
|
||||
onClick={() => {
|
||||
let item = this.history.pop();
|
||||
this.setState({ ...item });
|
||||
}}
|
||||
sx={{
|
||||
display: this.history.length ? "block" : "none",
|
||||
":hover": { color: "primary" },
|
||||
marginRight: 2,
|
||||
}}
|
||||
>
|
||||
<Icon.ArrowLeft />
|
||||
</Text>
|
||||
<Text variant="title">{title}</Text>
|
||||
</Flex>
|
||||
<Text
|
||||
onClick={() => {
|
||||
<Flex alignItems="center" justifyContent="space-between" mb={2}>
|
||||
<Text variant="title">Notebooks</Text>
|
||||
<Button
|
||||
variant="anchor"
|
||||
fontSize="body"
|
||||
/* onClick={() => {
|
||||
if (mode === "write") {
|
||||
this.setState({ mode: "read" });
|
||||
return;
|
||||
@@ -89,16 +48,12 @@ class MoveDialog extends React.Component {
|
||||
setTimeout(() => {
|
||||
this._inputRef.focus();
|
||||
}, 0);
|
||||
}}
|
||||
sx={{
|
||||
display: type === "notes" ? "none" : "block",
|
||||
":hover": { color: "primary" },
|
||||
}}
|
||||
}} */
|
||||
>
|
||||
{mode === "read" ? <Icon.Plus /> : <Icon.Minus />}
|
||||
</Text>
|
||||
Create
|
||||
</Button>
|
||||
</Flex>
|
||||
<Input
|
||||
{/* <Input
|
||||
ref={(ref) => (this._inputRef = ref)}
|
||||
sx={{ display: mode === "write" ? "block" : "none" }}
|
||||
my={1}
|
||||
@@ -123,7 +78,7 @@ class MoveDialog extends React.Component {
|
||||
this.setState({ mode: "read" });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
/> */}
|
||||
<Box
|
||||
sx={{
|
||||
borderWidth: 1,
|
||||
@@ -133,20 +88,11 @@ class MoveDialog extends React.Component {
|
||||
overflowY: "auto",
|
||||
}}
|
||||
>
|
||||
{items.length ? (
|
||||
items.map((item) => {
|
||||
return (
|
||||
<Flex
|
||||
key={item.title + item.dateCreated}
|
||||
sx={{
|
||||
borderWidth: 1,
|
||||
padding: 2,
|
||||
borderBottomColor: "border",
|
||||
borderBottomStyle: "solid",
|
||||
cursor: "pointer",
|
||||
":hover": { borderBottomColor: "primary" },
|
||||
}}
|
||||
onClick={() => {
|
||||
{notebooks.map((notebook, index) => (
|
||||
<Flex
|
||||
variant="columnFill"
|
||||
key={notebook.id}
|
||||
/* onClick={() => {
|
||||
this.history.push({
|
||||
title,
|
||||
items,
|
||||
@@ -169,31 +115,59 @@ class MoveDialog extends React.Component {
|
||||
});
|
||||
this.selectedTopic = item.title;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Text sx={{ width: "80%" }} fontSize="body">
|
||||
{item.title}
|
||||
</Text>
|
||||
{item.totalNotes !== undefined && (
|
||||
<Text
|
||||
sx={{ width: "20%", textAlign: "right" }}
|
||||
fontSize="body"
|
||||
>
|
||||
{item.totalNotes + " Notes"}
|
||||
</Text>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<Text
|
||||
py={2}
|
||||
px={2}
|
||||
sx={{ textAlign: "center", fontStyle: "italic" }}
|
||||
}} */
|
||||
>
|
||||
Nothing here
|
||||
</Text>
|
||||
)}
|
||||
<Item
|
||||
icon={Icon.Notebook}
|
||||
title={notebook.title}
|
||||
totalNotes={notebook.totalNotes}
|
||||
onClick={() =>
|
||||
this.setState({
|
||||
currentOpenedIndex:
|
||||
this.state.currentOpenedIndex === index ? -1 : index,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Flex
|
||||
variant="columnFill"
|
||||
style={{
|
||||
display: currentOpenedIndex === index ? "flex" : "none",
|
||||
}}
|
||||
>
|
||||
{notebook.topics.map((topic) => (
|
||||
<Item
|
||||
key={topic.id}
|
||||
onClick={async () => {
|
||||
try {
|
||||
const nb = {
|
||||
id: notebook.id,
|
||||
topic: topic.id,
|
||||
};
|
||||
const note = db.notes.note(props.noteIds[0]).data;
|
||||
await db.notes.move(nb, ...props.noteIds);
|
||||
showNotesMovedToast(note, props.noteIds, nb);
|
||||
props.onMove();
|
||||
} catch (e) {
|
||||
showToast("error", e.message);
|
||||
console.error(e);
|
||||
} finally {
|
||||
props.onClose();
|
||||
}
|
||||
}}
|
||||
indent={1}
|
||||
icon={Icon.Topic}
|
||||
title={topic.title}
|
||||
totalNotes={topic.totalNotes}
|
||||
action={
|
||||
<Text color="primary" fontSize="body">
|
||||
Move here
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</Flex>
|
||||
</Flex>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
</Dialog>
|
||||
@@ -210,3 +184,35 @@ export function showMoveNoteDialog(noteIds) {
|
||||
/>
|
||||
));
|
||||
}
|
||||
|
||||
function Item(props) {
|
||||
const { icon: Icon, indent = 0, title, totalNotes, onClick, action } = props;
|
||||
return (
|
||||
<Flex
|
||||
p={2}
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
pl={!indent ? 2 : indent * 30}
|
||||
sx={{
|
||||
borderWidth: 1,
|
||||
borderBottomColor: "border",
|
||||
borderBottomStyle: "solid",
|
||||
cursor: "pointer",
|
||||
":hover": { borderBottomColor: "primary" },
|
||||
}}
|
||||
onClick={onClick}
|
||||
>
|
||||
<Flex alignItems="center" justifyContent="center">
|
||||
<Icon />
|
||||
<Text as="span" ml={1} fontSize="body">
|
||||
{title}
|
||||
</Text>
|
||||
</Flex>
|
||||
{action || (
|
||||
<Text sx={{ textAlign: "right" }} fontSize="body">
|
||||
{totalNotes + " Notes"}
|
||||
</Text>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ function PasswordDialog(props) {
|
||||
<Dialog
|
||||
isOpen={true}
|
||||
title={props.title}
|
||||
description={props.subtitle}
|
||||
icon={props.icon}
|
||||
positiveButton={{
|
||||
text: props.positiveButtonText,
|
||||
@@ -59,21 +60,24 @@ function getDialogData(type) {
|
||||
switch (type) {
|
||||
case "create_vault":
|
||||
return {
|
||||
title: "Set Up Your Vault",
|
||||
title: "Create Your Vault",
|
||||
subtitle: "Your vault will encrypt everything locally.",
|
||||
icon: Icon.Vault,
|
||||
positiveButtonText: "Done",
|
||||
positiveButtonText: "Create my vault",
|
||||
};
|
||||
case "unlock_vault":
|
||||
return {
|
||||
title: "Unlock Vault",
|
||||
title: "Unlock your Vault",
|
||||
subtitle: "Your vault will remain unlocked for 30 minutes.",
|
||||
icon: Icon.Unlock,
|
||||
positiveButtonText: "Unlock",
|
||||
positiveButtonText: "Unlock my vault",
|
||||
};
|
||||
case "unlock_note":
|
||||
return {
|
||||
title: "Unlock Note",
|
||||
title: "Unlock your Note",
|
||||
subtitle: "Unlocking will make this note openly available.",
|
||||
icon: Icon.Unlock,
|
||||
positiveButtonText: "Unlock",
|
||||
positiveButtonText: "Unlock this note",
|
||||
};
|
||||
default:
|
||||
return;
|
||||
@@ -81,10 +85,11 @@ function getDialogData(type) {
|
||||
}
|
||||
|
||||
export function showPasswordDialog(type, validate) {
|
||||
const { title, icon, positiveButtonText } = getDialogData(type);
|
||||
const { title, subtitle, icon, positiveButtonText } = getDialogData(type);
|
||||
return showDialog((perform) => (
|
||||
<PasswordDialog
|
||||
title={title}
|
||||
subtitle={subtitle}
|
||||
icon={icon}
|
||||
positiveButtonText={positiveButtonText}
|
||||
onCancel={() => perform(false)}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState } from "react";
|
||||
import { Text, Box } from "rebass";
|
||||
import { Text, Box, Button } from "rebass";
|
||||
import Input from "../inputs";
|
||||
import * as Icon from "../icons";
|
||||
import Dialog, { showDialog } from "./dialog";
|
||||
@@ -8,6 +8,7 @@ import EmailInput from "../inputs/email";
|
||||
import PasswordInput from "../inputs/password";
|
||||
import Dropper from "../dropper";
|
||||
import { useStore } from "../../stores/user-store";
|
||||
import { showLogInDialog } from "./logindialog";
|
||||
|
||||
const form = { error: true };
|
||||
function SignUpDialog(props) {
|
||||
@@ -19,19 +20,35 @@ function SignUpDialog(props) {
|
||||
return (
|
||||
<Dialog
|
||||
isOpen={true}
|
||||
title={"Sign Up"}
|
||||
icon={Icon.User}
|
||||
title={"Create a new Account"}
|
||||
description={"Sign up for a 14-day free trial (no credit card)."}
|
||||
icon={Icon.Signup}
|
||||
onCloseClick={onClose}
|
||||
negativeButton={{ onClick: onClose }}
|
||||
negativeButton={{ text: "I don't want to", onClick: onClose }}
|
||||
buttonsAlignment="center"
|
||||
positiveButton={{
|
||||
text: "Sign Up",
|
||||
text: "Create my account",
|
||||
loading: isSigningIn,
|
||||
disabled: isSigningIn,
|
||||
onClick: () => submit(setError, form, signup, onClose),
|
||||
}}
|
||||
footer={
|
||||
<>
|
||||
<Text textAlign="center" color="gray" mt={3}>
|
||||
Already have an account?
|
||||
</Text>
|
||||
<Button
|
||||
variant="anchor"
|
||||
mt={2}
|
||||
fontSize="body"
|
||||
onClick={showLogInDialog}
|
||||
>
|
||||
Sign in here.
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Box
|
||||
mt={1}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") submit(setError, form, signup, onClose);
|
||||
}}
|
||||
|
||||
@@ -13,9 +13,10 @@ function TopicDialog(props) {
|
||||
<Dialog
|
||||
isOpen={true}
|
||||
title={props.title}
|
||||
description="You can create as many topics as you want."
|
||||
icon={props.icon}
|
||||
positiveButton={{
|
||||
text: "Add",
|
||||
text: "Create topic",
|
||||
onClick: () => {
|
||||
props.onYes(ref.current.value);
|
||||
},
|
||||
@@ -37,7 +38,7 @@ function TopicDialog(props) {
|
||||
export function showTopicDialog(notebook) {
|
||||
return showDialog((perform) => (
|
||||
<TopicDialog
|
||||
title={"Topic"}
|
||||
title={"Create a Topic"}
|
||||
icon={Icon.Topic}
|
||||
onNo={() => {
|
||||
perform(false);
|
||||
|
||||
@@ -38,7 +38,7 @@ export const Plus = createIcon(Icons.mdiPlus);
|
||||
export const Minus = createIcon(Icons.mdiMinus);
|
||||
export const Notebook = createIcon(Icons.mdiBookOutline);
|
||||
export const ArrowLeft = createIcon(Icons.mdiArrowLeft);
|
||||
export const Move = createIcon(Icons.mdiArrowAll);
|
||||
export const Move = createIcon(Icons.mdiBookPlusMultipleOutline);
|
||||
export const Topic = createIcon(Icons.mdiFormatTitle);
|
||||
export const Alert = createIcon(Icons.mdiAlert);
|
||||
export const Vault = createIcon(Icons.mdiShieldOutline);
|
||||
@@ -54,6 +54,7 @@ export const Trash = createIcon(Icons.mdiTrashCanOutline);
|
||||
export const Search = createIcon(Icons.mdiMagnify);
|
||||
export const Menu = createIcon(Icons.mdiMenu);
|
||||
export const Login = createIcon(Icons.mdiLoginVariant);
|
||||
export const Signup = createIcon(Icons.mdiAccountPlusOutline);
|
||||
export const Logout = createIcon(Icons.mdiLogoutVariant);
|
||||
export const FocusMode = createIcon(Icons.mdiFullscreen);
|
||||
export const NormalMode = createIcon(Icons.mdiFullscreenExit);
|
||||
@@ -62,7 +63,7 @@ export const Home = createIcon(Icons.mdiHomeOutline);
|
||||
export const Restore = createIcon(Icons.mdiRecycle);
|
||||
export const Sync = createIcon(Icons.mdiSync);
|
||||
export const Loading = createIcon(Icons.mdiLoading, true);
|
||||
export const Export = createIcon(Icons.mdiExport);
|
||||
export const Export = createIcon(Icons.mdiExportVariant);
|
||||
export const AddToNotebook = createIcon(Icons.mdiBookPlusMultipleOutline);
|
||||
|
||||
/** Properties Icons */
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Flex, Box, Text } from "rebass";
|
||||
import * as Icon from "../icons";
|
||||
import TimeAgo from "timeago-react";
|
||||
import ListItem from "../list-item";
|
||||
import { confirm } from "../dialogs/confirm";
|
||||
import { confirm, showDeleteConfirmation } from "../dialogs/confirm";
|
||||
import { showMoveNoteDialog } from "../dialogs/movenotedialog";
|
||||
import { store, useStore } from "../../stores/note-store";
|
||||
import { store as editorStore } from "../../stores/editor-store";
|
||||
@@ -65,17 +65,31 @@ function menuItems(note, context) {
|
||||
},
|
||||
{
|
||||
visible: context?.type === "topic",
|
||||
title: "Remove",
|
||||
title: "Remove from topic",
|
||||
onClick: async () => {
|
||||
confirm(
|
||||
Icon.Topic,
|
||||
"Remove from Topic",
|
||||
"Are you sure you want to remove this note?"
|
||||
).then(async (res) => {
|
||||
confirm(Icon.Topic, {
|
||||
title: "Remove Note from Topic",
|
||||
subtitle: "Are you sure you want to remove the note from this topic?",
|
||||
yesText: "Remove note",
|
||||
noText: "Cancel",
|
||||
message: (
|
||||
<Text as="span">
|
||||
<Text as="span" color="primary">
|
||||
This action does not delete the note.
|
||||
</Text>{" "}
|
||||
The note will only be removed from this notebook. You will still
|
||||
be able to{" "}
|
||||
<Text as="span" color="primary">
|
||||
access it from Home and other places.
|
||||
</Text>
|
||||
</Text>
|
||||
),
|
||||
}).then(async (res) => {
|
||||
if (res) {
|
||||
console.log(context);
|
||||
await db.notebooks
|
||||
.notebook(context.notebook.id)
|
||||
.topics.topic(context.value)
|
||||
.notebook(context.value.id)
|
||||
.topics.topic(context.value.topic)
|
||||
.delete(note.id);
|
||||
store.setContext(context);
|
||||
}
|
||||
@@ -95,11 +109,7 @@ function menuItems(note, context) {
|
||||
});
|
||||
if (!res) return;
|
||||
}
|
||||
confirm(
|
||||
Icon.Trash,
|
||||
"Delete",
|
||||
"Are you sure you want to delete this note?"
|
||||
).then(async (res) => {
|
||||
showDeleteConfirmation("note").then(async (res) => {
|
||||
if (res) {
|
||||
await store.delete(note.id).then(() => showItemDeletedToast(note));
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import ListItem from "../list-item";
|
||||
import { store } from "../../stores/notebook-store";
|
||||
import * as Icon from "../icons";
|
||||
import { showEditNoteDialog } from "../dialogs/addnotebookdialog";
|
||||
import { confirm } from "../dialogs/confirm";
|
||||
import { confirm, showDeleteConfirmation } from "../dialogs/confirm";
|
||||
import { showItemDeletedToast, showUnpinnedToast } from "../../common/toasts";
|
||||
const pin = async (notebook, index) => {
|
||||
await store.pin(notebook, index);
|
||||
@@ -24,11 +24,7 @@ function menuItems(notebook, index) {
|
||||
title: "Delete",
|
||||
color: "red",
|
||||
onClick: () => {
|
||||
confirm(
|
||||
Icon.Trash,
|
||||
"Delete Notebook",
|
||||
"Are you sure you want to delete this notebook?"
|
||||
).then(async (res) => {
|
||||
showDeleteConfirmation("notebook").then(async (res) => {
|
||||
if (res) {
|
||||
await store
|
||||
.delete(notebook.id, index)
|
||||
|
||||
@@ -5,6 +5,7 @@ import { confirm } from "../dialogs/confirm";
|
||||
import * as Icon from "../icons";
|
||||
import { db } from "../../common";
|
||||
import { store } from "../../stores/notebook-store";
|
||||
import { Text } from "rebass";
|
||||
|
||||
const menuItems = (item) => [
|
||||
{
|
||||
@@ -19,11 +20,24 @@ const menuItems = (item) => [
|
||||
visible: item.title !== "General",
|
||||
color: "red",
|
||||
onClick: () => {
|
||||
confirm(
|
||||
Icon.Trash,
|
||||
"Delete Topic",
|
||||
"Are you sure you want to delete this topic?"
|
||||
).then(async (res) => {
|
||||
confirm(Icon.Trash, {
|
||||
title: "Delete topic",
|
||||
subtitle: "Are you sure you want to delete this topic?",
|
||||
yesText: "Delete topic",
|
||||
noText: "Cancel",
|
||||
message: (
|
||||
<>
|
||||
This action is{" "}
|
||||
<Text as="span" color="error">
|
||||
IRREVERSIBLE
|
||||
</Text>
|
||||
. Deleting this topic{" "}
|
||||
<Text as="span" color="primary">
|
||||
will not delete the notes contained in it.
|
||||
</Text>
|
||||
</>
|
||||
),
|
||||
}).then(async (res) => {
|
||||
if (res) {
|
||||
await db.notebooks.notebook(item.notebookId).topics.delete(item.id);
|
||||
store.setSelectedNotebookTopics(item.notebookId);
|
||||
|
||||
@@ -25,11 +25,24 @@ function menuItems(item, index) {
|
||||
title: "Delete",
|
||||
color: "red",
|
||||
onClick: () => {
|
||||
confirm(
|
||||
Icon.Trash,
|
||||
"Delete",
|
||||
`Are you sure you want to permanently delete this item?`
|
||||
).then(async (res) => {
|
||||
confirm(Icon.Trash, {
|
||||
title: `Permanently Delete ${toTitleCase(item.itemType)}`,
|
||||
subtitle: `Are you sure you want to permanently delete this ${item.itemType}?`,
|
||||
yesText: `Delete ${item.itemType}`,
|
||||
noText: "Cancel",
|
||||
message: (
|
||||
<>
|
||||
This action is{" "}
|
||||
<Text as="span" color="error">
|
||||
IRREVERSIBLE
|
||||
</Text>
|
||||
. You will{" "}
|
||||
<Text as="span" color="primary">
|
||||
not be able to recover this {item.itemType}.
|
||||
</Text>
|
||||
</>
|
||||
),
|
||||
}).then(async (res) => {
|
||||
if (res) {
|
||||
await store.delete(item.id, index);
|
||||
showPermanentDeleteToast(item, index);
|
||||
@@ -54,7 +67,7 @@ function TrashItem({ item, index }) {
|
||||
<Text as="span" mx={1}>
|
||||
•
|
||||
</Text>
|
||||
<Text color="primary">{toTitleCase(item.type)}</Text>
|
||||
<Text color="primary">{toTitleCase(item.itemType)}</Text>
|
||||
</Flex>
|
||||
}
|
||||
menuData={item}
|
||||
|
||||
@@ -7,7 +7,7 @@ class FontSizeFactory {
|
||||
subtitle: 16 * scaleFactor,
|
||||
body: 16 * scaleFactor,
|
||||
menu: 14 * scaleFactor,
|
||||
subBody: 11 * scaleFactor,
|
||||
subBody: 12 * scaleFactor,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ const routes = {
|
||||
<Notes
|
||||
context={{
|
||||
type: "topic",
|
||||
value: { id: notebook, topic: topicItem.title },
|
||||
value: { id: notebook, topic: topic },
|
||||
}}
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ function Topics(props) {
|
||||
context={{ notebookId }}
|
||||
placeholder={Flex}
|
||||
button={{
|
||||
content: "Add more topics",
|
||||
content: "Create a new topic",
|
||||
onClick: async () => {
|
||||
await showTopicDialog(notebookId);
|
||||
},
|
||||
|
||||
@@ -5,6 +5,7 @@ import { confirm } from "../components/dialogs/confirm";
|
||||
import { useStore, store } from "../stores/trash-store";
|
||||
import TrashPlaceholder from "../components/placeholders/trash-placeholder";
|
||||
import { showToast } from "../utils/toast";
|
||||
import { Text } from "rebass";
|
||||
|
||||
function Trash() {
|
||||
useEffect(() => store.refresh(), []);
|
||||
@@ -19,11 +20,24 @@ function Trash() {
|
||||
content: "Clear Trash",
|
||||
icon: Icon.Trash,
|
||||
onClick: function () {
|
||||
confirm(
|
||||
Icon.Trash,
|
||||
"Clear",
|
||||
`This action is irreversible. Are you sure you want to proceed?`
|
||||
).then(async (res) => {
|
||||
confirm(Icon.Trash, {
|
||||
title: "Clear Trash",
|
||||
subtitle: "Are you sure you want to clear all the trash?",
|
||||
yesText: "Clear trash",
|
||||
noText: "Cancel",
|
||||
message: (
|
||||
<>
|
||||
This action is{" "}
|
||||
<Text as="span" color="error">
|
||||
IRREVERSIBLE
|
||||
</Text>
|
||||
. You will{" "}
|
||||
<Text as="span" color="primary">
|
||||
not be able to recover any of these items.
|
||||
</Text>
|
||||
</>
|
||||
),
|
||||
}).then(async (res) => {
|
||||
if (res) {
|
||||
try {
|
||||
await clearTrash();
|
||||
|
||||
Reference in New Issue
Block a user