This commit is contained in:
alihamuh
2020-03-03 09:24:07 +05:00
11 changed files with 283 additions and 129 deletions

View File

@@ -77,19 +77,19 @@ const NavMenuItem = props => {
};
function App() {
const [selectedIndex, setSelectedIndex] = usePersistentState(
"navSelectedIndex",
0
const [selectedKey, setSelectedKey] = usePersistentState(
"navSelectedKey",
"home"
);
const [show, setShow] = usePersistentState("navContainerState", true);
const isSideMenuOpen = useStore(store => store.isSideMenuOpen);
const refreshColors = useStore(store => store.refreshColors);
const setSelectedContext = useNotesStore(store => store.setSelectedContext);
useEffect(() => {
RootNavigator.navigate(Object.keys(RootNavigator.routes)[selectedIndex]);
RootNavigator.navigate(selectedKey);
refreshColors();
console.log(colors);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const colors = useStore(store => store.colors);
@@ -129,24 +129,24 @@ function App() {
{Object.values(routes).map((item, index) => (
<NavMenuItem
onSelected={async () => {
if (selectedIndex === index) {
if (selectedKey === item.key) {
setShow(!show);
return;
}
if (RootNavigator.navigate(item.key)) {
setSelectedIndex(index);
setSelectedKey(item.key);
}
}}
key={item.key}
item={item}
selected={selectedIndex === index}
selected={selectedKey === item.key}
/>
))}
{colors.map(color => {
return (
<NavMenuItem
onSelected={async () => {
setSelectedIndex(-1);
setSelectedKey(undefined);
setSelectedContext({ type: "color", value: color.title });
RootNavigator.navigate("color", {
title: toTitleCase(color.title),
@@ -172,11 +172,12 @@ function App() {
return item.onClick();
}
if (RootNavigator.navigate(item.key)) {
setSelectedIndex(index);
setSelectedKey(item.key);
}
}}
key={item.key}
item={item}
selected={selectedKey === item.key}
/>
))}
</Box>

View File

@@ -1,17 +1,35 @@
import React, { useEffect } from "react";
import "./editor.css";
import ReactQuill from "./react-quill";
import { Flex, Box } from "rebass";
import { Flex, Box, Text } from "rebass";
import TitleBox from "./title-box";
import * as Icon from "react-feather";
import Properties from "../properties";
import { useStore, SESSION_STATES } from "../../stores/editor-store";
import { timeConverter } from "../../utils/time";
import { countWords } from "../../utils/string";
import { useTheme } from "emotion-theming";
const TextSeperator = props => {
const theme = useTheme();
return (
<Text as="span" mx={1}>
<Icon.Circle size={6} fill={theme.colors.fontTertiary} />
</Text>
);
};
function Editor() {
const title = useStore(store => store.session.title);
const dateEdited = useStore(store => store.session.dateEdited);
const text = useStore(store => store.session.content.text);
const isSaving = useStore(store => store.session.isSaving);
const delta = useStore(store => store.session.content.delta);
const sessionState = useStore(store => store.session.state);
const setSession = useStore(store => store.setSession);
const saveSession = useStore(store => store.saveSession);
const reopenLastSession = useStore(store => store.reopenLastSession);
useEffect(() => {
// move the toolbar outside (easiest way)
const toolbar = document.querySelector(".ql-toolbar.ql-snow");
@@ -21,6 +39,10 @@ function Editor() {
}
}, []);
useEffect(() => {
reopenLastSession();
}, [reopenLastSession]);
return (
<Flex width={["0%", "0%", "100%"]} sx={{ position: "relative" }}>
<Flex className="editor" flex="1 1 auto" flexDirection="column">
@@ -32,7 +54,25 @@ function Editor() {
state.session.title = title;
})
}
sx={{
paddingTop: 2,
paddingBottom: dateEdited > 0 ? 0 : 2
}}
/>
{dateEdited > 0 && (
<Text
fontSize={"subBody"}
mx={2}
color="fontTertiary"
sx={{ display: "flex", alignItems: "center", marginBottom: 2 }}
>
{timeConverter(dateEdited)}
<TextSeperator />
{countWords(text) + " words"}
<TextSeperator />
{isSaving ? "Saving" : "Saved"}
</Text>
)}
<Box id="toolbar" display={["none", "flex", "flex"]} />
<ReactQuill
refresh={sessionState === SESSION_STATES.new}

View File

@@ -15,7 +15,7 @@ export default class TitleBox extends React.Component {
}
render() {
const { title, setTitle } = this.props;
const { title, setTitle, sx } = this.props;
return (
<Input
ref={ref => (this.inputRef = ref)}
@@ -25,12 +25,12 @@ export default class TitleBox extends React.Component {
fontWeight="heading"
fontSize="heading"
display={["none", "flex", "flex"]}
px={2}
sx={{
borderWidth: 0,
":focus": { outline: "none" }
":focus": { outline: "none" },
...sx
}}
py={2}
px={2}
value={title}
onChange={e => {
setTitle(e.target.value);

View File

@@ -9,10 +9,10 @@ import useContextMenu from "../../utils/useContextMenu";
const ActionsMenu = props => (
<Menu
id={props.id}
dropdownRef={props.dropdownRefs[props.index]}
menuItems={props.menuItems}
data={props.menuData}
style={props.style}
closeMenu={props.closeMenu}
/>
);
@@ -23,100 +23,99 @@ const ListItem = props => {
`contextMenu${props.index}`
);
return (
<Flex
ref={parentRef}
alignItems="center"
justifyContent="space-between"
py={2}
bg={props.pinned || isSelected ? "shade" : "background"}
px={2}
sx={{
//position: "relative",
marginTop: props.pinned ? 4 : 0,
borderBottom: "1px solid",
borderBottomColor: "navbg",
cursor: "default",
":hover": {
borderBottomColor: "primary",
cursor: "pointer"
}
//TODO add onpressed reaction
}}
>
{props.pinned && (
<Flex
bg="primary"
sx={{
borderRadius: 35,
width: 30,
height: 30,
position: "absolute",
top: 10,
left: 0,
marginTop: 0,
boxShadow: "2px 1px 3px #00000066"
}}
mx={2}
alignItems="center"
justifyContent="center"
>
<Box
bg="static"
sx={{
borderRadius: 5,
width: 5,
height: 5
}}
/>
</Flex>
)}
<Box
onClick={() => {
//e.stopPropagation();
if (props.onClick) {
props.onClick();
}
}}
<Flex ref={parentRef}>
<Flex
flex="1 1 auto"
alignItems="center"
justifyContent="space-between"
bg={props.pinned || isSelected ? "shade" : "background"}
px={2}
sx={{
flex: "1 1 auto",
paddingTop: props.pinned ? 4 : 0,
position: "relative",
marginTop: props.pinned ? 4 : 0,
paddingTop: props.pinned ? 0 : 2,
paddingBottom: 2,
borderBottom: "1px solid",
borderBottomColor: "navbg",
cursor: "default",
":hover": {
borderBottomColor: "primary",
cursor: "pointer"
}
//TODO add onpressed reaction
}}
>
<Flex flexDirection="row" justifyContent="space-between">
<Text fontFamily={"heading"} fontSize="title" fontWeight={"bold"}>
{props.title}
</Text>
</Flex>
<Text
display={props.body ? "flex" : "none"}
variant="body"
{props.pinned && (
<Flex
bg="primary"
sx={{
position: "absolute",
top: -15,
left: 0,
borderRadius: 35,
width: 30,
height: 30,
boxShadow: "2px 1px 3px #00000066"
}}
mx={2}
alignItems="center"
justifyContent="center"
>
<Box
bg="static"
sx={{
borderRadius: 5,
width: 5,
height: 5
}}
/>
</Flex>
)}
<Box
onClick={() => {
//e.stopPropagation();
if (props.onClick) {
props.onClick();
}
}}
sx={{
marginBottom: 1,
flex: "1 1 auto",
paddingTop: props.pinned ? 4 : 0,
":hover": {
cursor: "pointer"
},
flexWrap: "wrap"
}
}}
>
{props.body}
</Text>
{props.subBody && props.subBody}
<Text
display={props.info ? "flex" : "none"}
variant="body"
fontSize={11}
color="fontTertiary"
sx={{ marginTop: 2 }}
>
{props.info}
</Text>
</Box>
{props.menuItems && props.dropdownRefs && (
<>
{" "}
<Flex flexDirection="row" justifyContent="space-between">
<Text fontFamily={"heading"} fontSize="title" fontWeight={"bold"}>
{props.title}
</Text>
</Flex>
<Text
display={props.body ? "flex" : "none"}
variant="body"
sx={{
marginBottom: 1,
":hover": {
cursor: "pointer"
},
flexWrap: "wrap"
}}
>
{props.body}
</Text>
{props.subBody && props.subBody}
<Text
display={props.info ? "flex" : "none"}
variant="body"
fontSize={11}
color="fontTertiary"
sx={{ marginTop: 2 }}
>
{props.info}
</Text>
</Box>
{props.menuItems && props.dropdownRefs && (
<Dropdown
style={{ zIndex: 1, marginRight: -4 }}
ref={ref => (props.dropdownRefs[props.index] = ref)}
@@ -131,19 +130,25 @@ const ListItem = props => {
</Text>
</DropdownTrigger>
<DropdownContent style={{ zIndex: 2, marginLeft: -130 }}>
<ActionsMenu {...props} />
<ActionsMenu
{...props}
closeMenu={() => props.dropdownRefs[props.index].hide()}
/>
</DropdownContent>
</Dropdown>
<ActionsMenu
{...props}
id={`contextMenu${props.index}`}
style={{
position: "absolute",
display: "none",
zIndex: 999
}}
/>
</>
)}
</Flex>
{props.menuItems && props.dropdownRefs && (
<ActionsMenu
{...props}
id={`contextMenu${props.index}`}
style={{
position: "absolute",
display: "none",
zIndex: 999
}}
closeMenu={() => closeContextMenu()}
/>
)}
</Flex>
);

View File

@@ -34,8 +34,8 @@ function Menu(props) {
onClick={e => {
e.stopPropagation();
Dropdown.closeLastOpened();
if (props.dropdownRef) {
props.dropdownRef.hide();
if (props.closeMenu) {
props.closeMenu();
}
if (item.onClick) {
item.onClick(props.data);

View File

@@ -47,7 +47,7 @@ const invisibleRoutes = {
const RootNavigator = new Navigator(
"RootNavigator",
{ ...routes, ...invisibleRoutes },
{ ...routes, ...bottomRoutes, ...invisibleRoutes },
{
backButtonEnabled: false
}

View File

@@ -10,6 +10,7 @@ const SESSION_STATES = {
const DEFAULT_SESSION = {
state: "",
isSaving: false,
title: "",
timeout: 0,
id: "",
@@ -17,6 +18,7 @@ const DEFAULT_SESSION = {
favorite: false,
tags: [],
colors: [],
dateEdited: 0,
content: {
text: "",
delta: {
@@ -25,17 +27,27 @@ const DEFAULT_SESSION = {
}
};
function saveLastOpenedNote(id) {
if (!id) return localStorage.removeItem("lastOpenedNote");
localStorage.setItem("lastOpenedNote", id);
}
function editorStore(set, get) {
return {
session: DEFAULT_SESSION,
reopenLastSession: function() {
const id = localStorage.getItem("lastOpenedNote");
if (!id) return;
get().openSession(db.notes.note(id).data);
},
openSession: async function(note) {
clearTimeout(get().session.timeout);
const content = {
text: note.content.text,
delta: await db.notes.note(note).delta()
};
noteStore.getState().setSelectedNote(note.id);
set(state => {
clearTimeout(state.session.timeout);
state.session = {
...DEFAULT_SESSION,
id: note.id,
@@ -44,12 +56,17 @@ function editorStore(set, get) {
favorite: note.favorite,
colors: note.colors,
tags: note.tags,
dateEdited: note.dateEdited,
content,
state: SESSION_STATES.new
};
});
saveLastOpenedNote(note.id);
},
saveSession: function(oldSession) {
set(state => {
state.session.isSaving = true;
});
const { session } = get();
const { title, id, content, pinned, favorite, tags, colors } = session;
let note = {
@@ -67,40 +84,46 @@ function editorStore(set, get) {
updateContext("colors", colors);
appStore.getState().refreshColors();
}
set(state => {
if (state.session.id === "") {
noteStore.getState().setSelectedNote(id);
}
state.session.id = id;
noteStore.getState().refresh();
let notesState = noteStore.getState();
if (get().session.id === "") {
noteStore.getState().setSelectedNote(id);
}
// we update favorites only if favorite has changed
if (!oldSession || oldSession.favorite !== session.favorite) {
noteStore.getState().refreshList(LIST_TYPES.fav);
}
set(state => {
state.session.id = id;
state.session.isSaving = false;
});
notesState.refresh();
saveLastOpenedNote(id);
// we update favorites only if favorite has changed
if (!oldSession || oldSession.favorite !== session.favorite) {
notesState.refreshList(LIST_TYPES.fav);
}
});
},
setSession: function(session) {
const oldSession = get().session;
clearTimeout(oldSession.timeout);
set(state => {
state.session.state = SESSION_STATES.stale;
session(state);
clearTimeout(state.session.timeout);
state.session.timeout = setTimeout(() => {
get().saveSession(oldSession);
}, 500);
});
},
newSession: function(context = {}) {
clearTimeout(get().session.timeout);
set(state => {
clearTimeout(state.session.timeout);
state.session = {
...DEFAULT_SESSION,
...context,
state: SESSION_STATES.new
};
});
saveLastOpenedNote();
},
setColor: function(color) {
setTagOrColor(get().session, "colors", color, "color", get().setSession);

View File

@@ -14,7 +14,8 @@ function notebookStore(set) {
let notebook = await db.notebooks.add(nb);
if (notebook) {
set(state => {
state.notebooks.push(nb);
//TODO investigate ways to improve perf
state.notebooks = db.notebooks.all;
});
}
},
@@ -28,7 +29,9 @@ function notebookStore(set) {
update: function() {},
pin: async function(notebook, index) {
await db.notebooks.notebook(notebook).pin();
set(state => (state.notebooks[index].pinned = !notebook.pinned));
set(state => {
state.notebooks[index].pinned = !notebook.pinned;
});
}
};
}

View File

@@ -1,7 +1,8 @@
import { useState } from "react";
export const usePersistentState = (key, def) => {
const defState = JSON.parse(window.localStorage.getItem(key) || def);
let value = window.localStorage.getItem(key) || def;
const defState = tryParse(value);
const [k, setKey] = useState(defState);
const _setKey = s => {
setKey(s);
@@ -9,3 +10,11 @@ export const usePersistentState = (key, def) => {
};
return [k, _setKey];
};
function tryParse(val) {
try {
return JSON.parse(val);
} catch (e) {
return val;
}
}

View File

@@ -1,3 +1,13 @@
export function toTitleCase(str) {
return str[0].toUpperCase() + str.substring(1);
}
/**
*
* @param {String} str
*/
export function countWords(str) {
str = str.trim();
if (!str.length) return 0;
return str.split(/\W+\S/).length + (str[str.length] === " " ? 0 : 1);
}

View File

@@ -0,0 +1,63 @@
const days = [
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday"
];
const months = [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec"
];
export const timeConverter = timestamp => {
if (!timestamp) return;
var d = new Date(timestamp), // Convert the passed timestamp to milliseconds
yyyy = d.getFullYear(),
dd = ("0" + d.getDate()).slice(-2), // Add leading 0.
currentDay = d.getDay(),
hh = d.getHours(),
h = hh,
min = ("0" + d.getMinutes()).slice(-2), // Add leading 0.
ampm = "AM",
time;
if (hh > 12) {
h = hh - 12;
ampm = "PM";
} else if (hh === 12) {
h = 12;
ampm = "PM";
} else if (hh === 0) {
h = 12;
}
// ie: 2013-02-18, 8:35 AM
time =
days[currentDay] +
" " +
dd +
" " +
months[d.getMonth()] +
", " +
yyyy +
", " +
h +
":" +
min +
" " +
ampm;
return time;
};