diff --git a/apps/web/src/app.js b/apps/web/src/app.js index 515ab46d8..18baf2c94 100644 --- a/apps/web/src/app.js +++ b/apps/web/src/app.js @@ -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) => ( { - 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 ( { - 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} /> ))} diff --git a/apps/web/src/components/editor/index.js b/apps/web/src/components/editor/index.js index e23f5484d..f8e3c936c 100644 --- a/apps/web/src/components/editor/index.js +++ b/apps/web/src/components/editor/index.js @@ -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 ( + + + + ); +}; 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 ( @@ -32,7 +54,25 @@ function Editor() { state.session.title = title; }) } + sx={{ + paddingTop: 2, + paddingBottom: dateEdited > 0 ? 0 : 2 + }} /> + {dateEdited > 0 && ( + + {timeConverter(dateEdited)} + + {countWords(text) + " words"} + + {isSaving ? "Saving" : "Saved"} + + )} (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); diff --git a/apps/web/src/components/list-item/index.js b/apps/web/src/components/list-item/index.js index f009fb0b1..c682d4dc2 100644 --- a/apps/web/src/components/list-item/index.js +++ b/apps/web/src/components/list-item/index.js @@ -9,10 +9,10 @@ import useContextMenu from "../../utils/useContextMenu"; const ActionsMenu = props => ( ); @@ -23,100 +23,99 @@ const ListItem = props => { `contextMenu${props.index}` ); return ( - - {props.pinned && ( - - - - )} - { - //e.stopPropagation(); - if (props.onClick) { - props.onClick(); - } - }} + + - - - {props.title} - - - + + + )} + { + //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} - - {props.subBody && props.subBody} - - {props.info} - - - {props.menuItems && props.dropdownRefs && ( - <> - {" "} + + + {props.title} + + + + {props.body} + + {props.subBody && props.subBody} + + {props.info} + + + {props.menuItems && props.dropdownRefs && ( (props.dropdownRefs[props.index] = ref)} @@ -131,19 +130,25 @@ const ListItem = props => { - + props.dropdownRefs[props.index].hide()} + /> - - + )} + + {props.menuItems && props.dropdownRefs && ( + closeContextMenu()} + /> )} ); diff --git a/apps/web/src/components/menu/index.js b/apps/web/src/components/menu/index.js index fbb16a547..21b0df188 100644 --- a/apps/web/src/components/menu/index.js +++ b/apps/web/src/components/menu/index.js @@ -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); diff --git a/apps/web/src/navigation/navigators/rootnavigator.js b/apps/web/src/navigation/navigators/rootnavigator.js index 5b8b07257..ab89e785b 100644 --- a/apps/web/src/navigation/navigators/rootnavigator.js +++ b/apps/web/src/navigation/navigators/rootnavigator.js @@ -47,7 +47,7 @@ const invisibleRoutes = { const RootNavigator = new Navigator( "RootNavigator", - { ...routes, ...invisibleRoutes }, + { ...routes, ...bottomRoutes, ...invisibleRoutes }, { backButtonEnabled: false } diff --git a/apps/web/src/stores/editor-store.js b/apps/web/src/stores/editor-store.js index 9a7c0abc8..eadb2348d 100644 --- a/apps/web/src/stores/editor-store.js +++ b/apps/web/src/stores/editor-store.js @@ -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); diff --git a/apps/web/src/stores/notebook-store.js b/apps/web/src/stores/notebook-store.js index 3fa473d14..2310b6c4d 100644 --- a/apps/web/src/stores/notebook-store.js +++ b/apps/web/src/stores/notebook-store.js @@ -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; + }); } }; } diff --git a/apps/web/src/utils/hooks.js b/apps/web/src/utils/hooks.js index 7aec1d3f1..a454cc291 100644 --- a/apps/web/src/utils/hooks.js +++ b/apps/web/src/utils/hooks.js @@ -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; + } +} diff --git a/apps/web/src/utils/string.js b/apps/web/src/utils/string.js index 9953d7717..d2e7f4752 100644 --- a/apps/web/src/utils/string.js +++ b/apps/web/src/utils/string.js @@ -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); +} diff --git a/apps/web/src/utils/time.js b/apps/web/src/utils/time.js new file mode 100644 index 000000000..b6550ff22 --- /dev/null +++ b/apps/web/src/utils/time.js @@ -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; +};