mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-09-03 04:31:46 +02:00
Merge branch 'master' of https://github.com/thecodrr/notes-web
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -47,7 +47,7 @@ const invisibleRoutes = {
|
||||
|
||||
const RootNavigator = new Navigator(
|
||||
"RootNavigator",
|
||||
{ ...routes, ...invisibleRoutes },
|
||||
{ ...routes, ...bottomRoutes, ...invisibleRoutes },
|
||||
{
|
||||
backButtonEnabled: false
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
63
apps/web/src/utils/time.js
Normal file
63
apps/web/src/utils/time.js
Normal 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;
|
||||
};
|
||||
Reference in New Issue
Block a user