mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-09-01 19:49:54 +02:00
feat: add notebook navigation (incomplete)
This commit is contained in:
@@ -4,3 +4,7 @@ import events from "events";
|
||||
|
||||
export const db = new Database(StorageInterface);
|
||||
export const ev = new events.EventEmitter();
|
||||
|
||||
export function sendNewNoteEvent() {
|
||||
ev.emit("onNewNote");
|
||||
}
|
||||
|
||||
73
apps/web/src/components/list-item/index.js
Normal file
73
apps/web/src/components/list-item/index.js
Normal file
@@ -0,0 +1,73 @@
|
||||
import React from "react";
|
||||
import { Flex, Box, Text } from "rebass";
|
||||
import * as Icon from "react-feather";
|
||||
import { ButtonPressedStyle } from "../../theme";
|
||||
import Dropdown, { DropdownTrigger, DropdownContent } from "../dropdown";
|
||||
import Menu from "../menu";
|
||||
|
||||
const ListItem = props => (
|
||||
<Flex
|
||||
onClick={e => {
|
||||
if (props.onClick) {
|
||||
props.onClick();
|
||||
}
|
||||
e.stopPropagation();
|
||||
}}
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
py={1}
|
||||
sx={{
|
||||
borderRadius: "default",
|
||||
marginBottom: 2,
|
||||
borderBottom: "1px solid",
|
||||
borderBottomColor: "navbg",
|
||||
cursor: "default",
|
||||
...ButtonPressedStyle
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<Flex flexDirection="row" justifyContent="space-between">
|
||||
<Text fontFamily="body" fontSize="title" fontWeight="bold">
|
||||
{props.title}
|
||||
</Text>
|
||||
</Flex>
|
||||
<Text
|
||||
display={props.body ? "flex" : "none"}
|
||||
variant="body"
|
||||
sx={{ marginBottom: 1 }}
|
||||
>
|
||||
{props.body}
|
||||
</Text>
|
||||
{props.subBody && props.subBody}
|
||||
<Text
|
||||
display={props.info ? "flex" : "none"}
|
||||
variant="body"
|
||||
fontSize={12}
|
||||
color="accent"
|
||||
>
|
||||
{props.info}
|
||||
</Text>
|
||||
</Box>
|
||||
<Dropdown
|
||||
style={{ zIndex: 999 }}
|
||||
ref={ref => (props.dropdownRefs[props.index] = ref)}
|
||||
>
|
||||
<DropdownTrigger>
|
||||
<Icon.MoreVertical
|
||||
size={24}
|
||||
strokeWidth={1.5}
|
||||
style={{ marginRight: -5 }}
|
||||
/>
|
||||
</DropdownTrigger>
|
||||
<DropdownContent style={{ zIndex: 999, marginLeft: -70 }}>
|
||||
<Menu
|
||||
dropdownRef={props.dropdownRefs[props.index]}
|
||||
menuItems={props.menuItems}
|
||||
data={props.menuData}
|
||||
/>
|
||||
</DropdownContent>
|
||||
</Dropdown>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
export default ListItem;
|
||||
49
apps/web/src/components/note/index.js
Normal file
49
apps/web/src/components/note/index.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import React from "react";
|
||||
import * as Icon from "react-feather";
|
||||
import TimeAgo from "timeago-react";
|
||||
import { db, ev } from "../../common";
|
||||
import { showSnack } from "../snackbar";
|
||||
import ListItem from "../list-item";
|
||||
|
||||
const dropdownRefs = [];
|
||||
const menuItems = [
|
||||
{ title: "Favorite", icon: Icon.Heart },
|
||||
{ title: "Share", icon: Icon.Share2 },
|
||||
{
|
||||
title: "Delete",
|
||||
icon: Icon.Trash,
|
||||
color: "red",
|
||||
onClick: note => {
|
||||
ev.emit("onClearNote", note.dateCreated);
|
||||
db.deleteNotes([note]).then(
|
||||
//TODO implement undo
|
||||
() => {
|
||||
showSnack("Note deleted!", Icon.Check);
|
||||
ev.emit("refreshNotes");
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
function sendOpenNoteEvent(note) {
|
||||
ev.emit("onOpenNote", note);
|
||||
}
|
||||
|
||||
const Note = ({ item, index }) => {
|
||||
const note = item;
|
||||
return (
|
||||
<ListItem
|
||||
title={note.title}
|
||||
body={note.headline}
|
||||
index={index}
|
||||
onClick={sendOpenNoteEvent.bind(this, note)}
|
||||
info={<TimeAgo datetime={note.dateCreated} />}
|
||||
menuData={note}
|
||||
menuItems={menuItems}
|
||||
dropdownRefs={dropdownRefs}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Note;
|
||||
64
apps/web/src/components/notebook/index.js
Normal file
64
apps/web/src/components/notebook/index.js
Normal file
@@ -0,0 +1,64 @@
|
||||
import React from "react";
|
||||
import { Flex, Text } from "rebass";
|
||||
import * as Icon from "react-feather";
|
||||
import ListItem from "../list-item";
|
||||
|
||||
const dropdownRefs = [];
|
||||
const menuItems = [
|
||||
{ title: "Favorite", icon: Icon.Heart },
|
||||
{ title: "Share", icon: Icon.Share2 },
|
||||
{
|
||||
title: "Delete",
|
||||
icon: Icon.Trash,
|
||||
color: "red"
|
||||
}
|
||||
];
|
||||
const Notebook = ({ item, index, onClick, onTopicClick }) => {
|
||||
const notebook = item;
|
||||
return (
|
||||
<ListItem
|
||||
onClick={onClick}
|
||||
title={notebook.title}
|
||||
body={notebook.description}
|
||||
subBody={
|
||||
<Flex sx={{ marginBottom: 1 }}>
|
||||
{notebook.topics.slice(1, 4).map(topic => (
|
||||
<Flex
|
||||
onClick={e => {
|
||||
onTopicClick(notebook, topic);
|
||||
e.stopPropagation();
|
||||
}}
|
||||
key={topic.dateCreated + topic.title}
|
||||
bg="accent"
|
||||
px={1}
|
||||
py={1}
|
||||
sx={{
|
||||
marginRight: 1,
|
||||
borderRadius: "default",
|
||||
color: "fontSecondary"
|
||||
}}
|
||||
>
|
||||
<Text className="unselectable" variant="body" fontSize={11}>
|
||||
{topic.title}
|
||||
</Text>
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
}
|
||||
info={
|
||||
<Text>
|
||||
{new Date(notebook.dateCreated).toDateString().substring(4)}
|
||||
<Text as="span" color="fontPrimary">
|
||||
{" • " + notebook.totalNotes} Notes
|
||||
</Text>
|
||||
</Text>
|
||||
}
|
||||
dropdownRefs={dropdownRefs}
|
||||
index={index}
|
||||
menuData={notebook}
|
||||
menuItems={menuItems}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Notebook;
|
||||
@@ -2,7 +2,7 @@ import React from "react";
|
||||
import { Flex, Box } from "rebass";
|
||||
import { Input } from "@rebass/forms";
|
||||
import * as Icon from "react-feather";
|
||||
import theme, { SHADOW } from "../../theme";
|
||||
import theme from "../../theme";
|
||||
import "./search.css";
|
||||
const Search = props => (
|
||||
<Flex
|
||||
|
||||
38
apps/web/src/components/topic/index.js
Normal file
38
apps/web/src/components/topic/index.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import React from "react";
|
||||
import { Text } from "rebass";
|
||||
import * as Icon from "react-feather";
|
||||
import ListItem from "../list-item";
|
||||
|
||||
const dropdownRefs = [];
|
||||
const menuItems = [
|
||||
{ title: "Favorite", icon: Icon.Heart },
|
||||
{ title: "Share", icon: Icon.Share2 },
|
||||
{
|
||||
title: "Delete",
|
||||
icon: Icon.Trash,
|
||||
color: "red"
|
||||
}
|
||||
];
|
||||
|
||||
const Topic = ({ item, index, onClick }) => {
|
||||
const topic = item;
|
||||
return (
|
||||
<ListItem
|
||||
onClick={onClick}
|
||||
title={topic.title}
|
||||
info={
|
||||
<Text>
|
||||
<Text as="span" color="fontPrimary">
|
||||
{topic.totalNotes} Notes
|
||||
</Text>
|
||||
</Text>
|
||||
}
|
||||
index={index}
|
||||
dropdownRefs={dropdownRefs}
|
||||
menuData={topic}
|
||||
menuItems={menuItems}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Topic;
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import React, { useState } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import Home from "../views/Home";
|
||||
import Notebooks from "../views/Notebooks";
|
||||
@@ -79,10 +79,13 @@ export function goBack(root) {
|
||||
}
|
||||
|
||||
function ThemedComponent(props) {
|
||||
const [title, setTitle] = useState(props.route.title);
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<Heading fontSize="heading">{props.route.title}</Heading>
|
||||
{props.route.component && <props.route.component />}
|
||||
<Heading fontSize="heading">{title}</Heading>
|
||||
{props.route.component && (
|
||||
<props.route.component changeTitle={setTitle} />
|
||||
)}
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,56 +1,22 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Flex, Box, Text } from "rebass";
|
||||
import { Flex, Text } from "rebass";
|
||||
import * as Icon from "react-feather";
|
||||
import { ButtonPressedStyle } from "../theme";
|
||||
import Dropdown, {
|
||||
DropdownTrigger,
|
||||
DropdownContent
|
||||
} from "../components/dropdown";
|
||||
import TimeAgo from "timeago-react";
|
||||
import { db, ev } from "../common";
|
||||
import { db, ev, sendNewNoteEvent } from "../common";
|
||||
import { Virtuoso as List } from "react-virtuoso";
|
||||
import { showSnack } from "../components/snackbar";
|
||||
import Menu from "../components/menu";
|
||||
import Button from "../components/button";
|
||||
import Search from "../components/search";
|
||||
import Note from "../components/note";
|
||||
|
||||
const dropdownRefs = [];
|
||||
const menuItems = [
|
||||
{ title: "Favorite", icon: Icon.Heart },
|
||||
{ title: "Share", icon: Icon.Share2 },
|
||||
{
|
||||
title: "Delete",
|
||||
icon: Icon.Trash,
|
||||
color: "red",
|
||||
onClick: note => {
|
||||
ev.emit("onClearNote", note.dateCreated);
|
||||
db.deleteNotes([note]).then(
|
||||
//TODO implement undo
|
||||
() => {
|
||||
showSnack("Note deleted!", Icon.Check);
|
||||
//TODO very crude but works.
|
||||
Home.onRefresh();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
function sendNewNoteEvent() {
|
||||
ev.emit("onNewNote");
|
||||
}
|
||||
function sendOpenNoteEvent(note) {
|
||||
ev.emit("onOpenNote", note);
|
||||
}
|
||||
function Home() {
|
||||
const [notes, setNotes] = useState([]);
|
||||
useEffect(() => {
|
||||
Home.onRefresh = () => {
|
||||
function onRefreshNotes() {
|
||||
setNotes(db.getNotes());
|
||||
};
|
||||
Home.onRefresh();
|
||||
}
|
||||
onRefreshNotes();
|
||||
ev.addListener("refreshNotes", onRefreshNotes);
|
||||
return () => {
|
||||
Home.onRefresh = undefined;
|
||||
ev.removeListener("refreshNotes", onRefreshNotes);
|
||||
};
|
||||
}, []);
|
||||
return (
|
||||
@@ -66,62 +32,7 @@ function Home() {
|
||||
overflowX: "hidden"
|
||||
}}
|
||||
totalCount={notes.length}
|
||||
item={index => {
|
||||
const note = notes[index];
|
||||
return (
|
||||
<Box
|
||||
onClick={e => {
|
||||
sendOpenNoteEvent(note);
|
||||
e.stopPropagation();
|
||||
}}
|
||||
py={3}
|
||||
sx={{
|
||||
borderRadius: "default",
|
||||
marginBottom: 2,
|
||||
borderBottom: "1px solid",
|
||||
borderBottomColor: "navbg",
|
||||
cursor: "default",
|
||||
...ButtonPressedStyle
|
||||
}}
|
||||
>
|
||||
<Flex flexDirection="row" justifyContent="space-between">
|
||||
<Text fontFamily="body" fontSize="title" fontWeight="bold">
|
||||
{note.title}
|
||||
</Text>
|
||||
<Dropdown
|
||||
style={{ zIndex: 999 }}
|
||||
ref={ref => (dropdownRefs[index] = ref)}
|
||||
>
|
||||
<DropdownTrigger>
|
||||
<Icon.MoreVertical
|
||||
size={20}
|
||||
strokeWidth={1.5}
|
||||
style={{ marginRight: -5 }}
|
||||
/>
|
||||
</DropdownTrigger>
|
||||
<DropdownContent style={{ zIndex: 999, marginLeft: -70 }}>
|
||||
<Menu
|
||||
dropdownRef={dropdownRefs[index]}
|
||||
menuItems={menuItems}
|
||||
data={note}
|
||||
/>
|
||||
</DropdownContent>
|
||||
</Dropdown>
|
||||
</Flex>
|
||||
<Text fontFamily="body" fontSize="body" sx={{ marginTop: 1 }}>
|
||||
{note.headline}
|
||||
</Text>
|
||||
<Text
|
||||
fontFamily="body"
|
||||
fontWeight="body"
|
||||
fontSize={12}
|
||||
color="accent"
|
||||
>
|
||||
<TimeAgo datetime={note.dateCreated} />
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
}}
|
||||
item={index => <Note index={index} item={notes[index]} />}
|
||||
/>
|
||||
<Button
|
||||
Icon={Icon.Plus}
|
||||
|
||||
@@ -1,48 +1,27 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Flex, Box, Text, Button as RebassButton } from "rebass";
|
||||
import { Input, Label } from "@rebass/forms";
|
||||
import { Input } from "@rebass/forms";
|
||||
import Button from "../components/button";
|
||||
import * as Icon from "react-feather";
|
||||
import theme, { ButtonPressedStyle, SHADOW, DIALOG_SHADOW } from "../theme";
|
||||
import theme, { DIALOG_SHADOW } from "../theme";
|
||||
import Search from "../components/search";
|
||||
import Modal from "react-modal";
|
||||
import { db, ev } from "../common";
|
||||
import { db } from "../common";
|
||||
import { showSnack } from "../components/snackbar";
|
||||
import { Virtuoso as List } from "react-virtuoso";
|
||||
import Dropdown, {
|
||||
DropdownTrigger,
|
||||
DropdownContent
|
||||
} from "../components/dropdown";
|
||||
import Menu from "../components/menu";
|
||||
import Notebook from "../components/notebook";
|
||||
import Topic from "../components/topic";
|
||||
import Note from "../components/note";
|
||||
|
||||
const inputRefs = [];
|
||||
const dropdownRefs = [];
|
||||
const menuItems = [
|
||||
{ title: "Favorite", icon: Icon.Heart },
|
||||
{ title: "Share", icon: Icon.Share2 },
|
||||
{
|
||||
title: "Delete",
|
||||
icon: Icon.Trash,
|
||||
color: "red",
|
||||
onClick: note => {
|
||||
ev.emit("onClearNote", note.dateCreated);
|
||||
db.deleteNotes([note]).then(
|
||||
//TODO implement undo
|
||||
async () => {
|
||||
showSnack("Note deleted!", Icon.Check);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const Notebooks = props => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [notebooks, setNotebooks] = useState([]);
|
||||
const [selected, setSelected] = useState({});
|
||||
useEffect(() => {
|
||||
Notebooks.onRefresh = () => {
|
||||
setNotebooks(db.getNotebooks());
|
||||
console.log(notebooks);
|
||||
};
|
||||
Notebooks.onRefresh();
|
||||
return () => {
|
||||
@@ -53,6 +32,11 @@ const Notebooks = props => {
|
||||
<Flex flexDirection="column" flex="1 1 auto">
|
||||
{notebooks.length > 0 ? (
|
||||
<Flex flexDirection="column" flex="1 1 auto">
|
||||
{selected.type === "topic" && (
|
||||
<Text variant="title" color="accent">
|
||||
{selected.title}
|
||||
</Text>
|
||||
)}
|
||||
<Search placeholder="Search" />
|
||||
<List
|
||||
style={{
|
||||
@@ -61,85 +45,50 @@ const Notebooks = props => {
|
||||
height: "auto",
|
||||
overflowX: "hidden"
|
||||
}}
|
||||
totalCount={notebooks.length}
|
||||
totalCount={
|
||||
selected.type === "notebook"
|
||||
? selected.topics.length
|
||||
: selected.type === "topic"
|
||||
? selected.notes.length
|
||||
: notebooks.length
|
||||
}
|
||||
item={index => {
|
||||
const notebook = notebooks[index];
|
||||
return (
|
||||
<Box
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
return selected.type === "notebook" ? (
|
||||
<Topic
|
||||
index={index}
|
||||
item={selected.topics[index]}
|
||||
onClick={() => {
|
||||
setSelected(selected.topics[index]);
|
||||
}}
|
||||
py={1}
|
||||
sx={{
|
||||
borderRadius: "default",
|
||||
marginBottom: 2,
|
||||
borderBottom: "1px solid",
|
||||
borderBottomColor: "navbg",
|
||||
cursor: "default",
|
||||
...ButtonPressedStyle
|
||||
/>
|
||||
) : selected.type === "topic" ? (
|
||||
<Note index={index} item={selected.notes[index]} />
|
||||
) : (
|
||||
<Notebook
|
||||
index={index}
|
||||
item={notebooks[index]}
|
||||
onClick={() => {
|
||||
props.changeTitle(notebooks[index].title);
|
||||
setSelected(notebooks[index]);
|
||||
}}
|
||||
>
|
||||
<Flex flexDirection="row" justifyContent="space-between">
|
||||
<Text fontFamily="body" fontSize="title" fontWeight="bold">
|
||||
{notebook.title}
|
||||
</Text>
|
||||
<Dropdown
|
||||
style={{ zIndex: 999 }}
|
||||
ref={ref => (dropdownRefs[index] = ref)}
|
||||
>
|
||||
<DropdownTrigger>
|
||||
<Icon.MoreVertical
|
||||
size={20}
|
||||
strokeWidth={1.5}
|
||||
style={{ marginRight: -5 }}
|
||||
/>
|
||||
</DropdownTrigger>
|
||||
<DropdownContent style={{ zIndex: 999, marginLeft: -70 }}>
|
||||
<Menu
|
||||
dropdownRef={dropdownRefs[index]}
|
||||
menuItems={menuItems}
|
||||
data={notebook}
|
||||
/>
|
||||
</DropdownContent>
|
||||
</Dropdown>
|
||||
</Flex>
|
||||
<Text variant="body" sx={{ marginBottom: 1 }}>
|
||||
{notebook.description}
|
||||
</Text>
|
||||
<Flex sx={{ marginBottom: 1 }}>
|
||||
{Object.keys(notebook.topics)
|
||||
.slice(0, 3)
|
||||
.map(topic => (
|
||||
<Flex
|
||||
bg="accent"
|
||||
px={1}
|
||||
py={1}
|
||||
sx={{
|
||||
marginRight: 1,
|
||||
borderRadius: "default",
|
||||
color: "fontSecondary"
|
||||
}}
|
||||
>
|
||||
<Text variant="body" fontSize={11}>
|
||||
{topic}
|
||||
</Text>
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
<Text variant="body" fontSize={12} color="accent">
|
||||
{new Date(notebook.dateCreated).toDateString().substring(4)}
|
||||
<Text as="span" color="fontPrimary">
|
||||
{" • " + notebook.totalNotes} Notes
|
||||
</Text>
|
||||
</Text>
|
||||
</Box>
|
||||
onTopicClick={(notebook, topic) => {
|
||||
props.changeTitle(notebook.title);
|
||||
setSelected(topic);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
Icon={Icon.Plus}
|
||||
onClick={() => setOpen(true)}
|
||||
content="Create a notebook"
|
||||
content={
|
||||
selected.type === "notebook"
|
||||
? "Add more topics"
|
||||
: selected.type === "topic"
|
||||
? "Make a new note"
|
||||
: "Create a notebook"
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
) : (
|
||||
@@ -244,9 +193,9 @@ const CreateNotebookDialog = props => {
|
||||
Topics (optional):
|
||||
</Text>
|
||||
<Box sx={{ maxHeight: 44 * 5, overflowY: "auto", marginBottom: 1 }}>
|
||||
{topics.map((item, index) => (
|
||||
{topics.map((value, index) => (
|
||||
<Flex
|
||||
key={item + index.toString()}
|
||||
key={index.toString()}
|
||||
flexDirection="row"
|
||||
sx={{ marginBottom: 1 }}
|
||||
>
|
||||
|
||||
@@ -6994,8 +6994,9 @@ normalize-url@^3.0.0, normalize-url@^3.0.1:
|
||||
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559"
|
||||
integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==
|
||||
|
||||
notes-core@../notes-core:
|
||||
version "0.1.0"
|
||||
"notes-core@https://github.com/thecodrr/notes-core.git":
|
||||
version "0.2.0"
|
||||
resolved "https://github.com/thecodrr/notes-core.git#281414d69b6bdc73b70e5347745ce8b797b55991"
|
||||
dependencies:
|
||||
fast-filter "^1.0.4"
|
||||
fuzzysearch "^1.0.3"
|
||||
|
||||
Reference in New Issue
Block a user