mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-23 06:59:31 +01:00
feat: add header in notebook view
This commit is contained in:
@@ -123,6 +123,8 @@ import {
|
||||
mdiFirefox,
|
||||
mdiAppleSafari,
|
||||
mdiBugOutline,
|
||||
mdiLinkVariant,
|
||||
mdiLinkVariantOff,
|
||||
} from "@mdi/js";
|
||||
import { useTheme } from "emotion-theming";
|
||||
import { AnimatedFlex } from "../animated";
|
||||
@@ -149,6 +151,7 @@ function createIcon(name, rotate = false) {
|
||||
const [isHovering, setIsHovering] = useState();
|
||||
return (
|
||||
<AnimatedFlex
|
||||
flexShrink={0}
|
||||
id={props.id}
|
||||
title={props.title}
|
||||
variant={props.variant}
|
||||
@@ -312,3 +315,5 @@ export const Anonymous = createIcon(mdiIncognito);
|
||||
export const CloudLock = createIcon(mdiCloudLockOutline);
|
||||
export const Timebomb = createIcon(mdiBomb);
|
||||
export const Issue = createIcon(mdiBugOutline);
|
||||
export const ShortcutLink = createIcon(mdiLinkVariant);
|
||||
export const RemoveShortcutLink = createIcon(mdiLinkVariantOff);
|
||||
|
||||
@@ -12,7 +12,7 @@ import Announcements from "../announcements";
|
||||
import useAnnouncements from "../../utils/use-announcements";
|
||||
|
||||
function ListContainer(props) {
|
||||
const { type, groupType, items, context, refresh } = props;
|
||||
const { type, groupType, items, context, refresh, header } = props;
|
||||
const [announcements, removeAnnouncement] = useAnnouncements();
|
||||
const profile = useMemo(() => ListProfiles[type], [type]);
|
||||
const shouldSelectAll = useSelectionStore((store) => store.shouldSelectAll);
|
||||
@@ -27,9 +27,12 @@ function ListContainer(props) {
|
||||
return (
|
||||
<Flex variant="columnFill">
|
||||
{!props.items.length && props.placeholder ? (
|
||||
<Flex variant="columnCenterFill">
|
||||
{props.isLoading ? <Icon.Loading rotate /> : <props.placeholder />}
|
||||
</Flex>
|
||||
<>
|
||||
{header}
|
||||
<Flex variant="columnCenterFill">
|
||||
{props.isLoading ? <Icon.Loading rotate /> : <props.placeholder />}
|
||||
</Flex>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Flex variant="columnFill" data-test-id="note-list">
|
||||
@@ -43,7 +46,9 @@ function ListContainer(props) {
|
||||
components={{
|
||||
Scroller: CustomScrollbarsVirtualList,
|
||||
Header: () =>
|
||||
announcements.length ? (
|
||||
header ? (
|
||||
header
|
||||
) : announcements.length ? (
|
||||
<Announcements
|
||||
announcements={announcements}
|
||||
removeAnnouncement={removeAnnouncement}
|
||||
|
||||
@@ -48,7 +48,7 @@ function Header(props) {
|
||||
(store) => store.toggleSelectionMode
|
||||
);
|
||||
|
||||
if (!title && !subtitle) return null;
|
||||
// if (!subtitle) return null;
|
||||
return (
|
||||
<Flex mx={2} flexDirection="column" justifyContent="center">
|
||||
<Flex alignItems="center" justifyContent="space-between">
|
||||
@@ -75,11 +75,14 @@ function Header(props) {
|
||||
size={30}
|
||||
/>
|
||||
)}
|
||||
<RouteTitle
|
||||
title={title}
|
||||
isEditable={isEditable}
|
||||
onChange={onChange}
|
||||
/>
|
||||
{title && (
|
||||
<RouteTitle
|
||||
subtitle={subtitle}
|
||||
title={title}
|
||||
isEditable={isEditable}
|
||||
onChange={onChange}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
<SelectionOptions options={SELECTION_OPTIONS_MAP[type]} />
|
||||
{!isSelectionMode && (
|
||||
@@ -111,18 +114,6 @@ function Header(props) {
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
{subtitle && (
|
||||
<Text
|
||||
variant="title"
|
||||
color="primary"
|
||||
sx={{
|
||||
marginBottom: 2,
|
||||
cursor: "normal",
|
||||
}}
|
||||
>
|
||||
{subtitle}
|
||||
</Text>
|
||||
)}
|
||||
{isSelectionMode && (
|
||||
<Flex
|
||||
mb={2}
|
||||
@@ -165,7 +156,7 @@ function SelectionOptions(props) {
|
||||
);
|
||||
}
|
||||
|
||||
function RouteTitle({ title, isEditable, onChange }) {
|
||||
function RouteTitle({ title, subtitle, isEditable, onChange }) {
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const ref = useRef();
|
||||
useEffect(() => {
|
||||
@@ -173,45 +164,48 @@ function RouteTitle({ title, isEditable, onChange }) {
|
||||
}, [title]);
|
||||
|
||||
return (
|
||||
<Input
|
||||
ref={ref}
|
||||
variant="heading"
|
||||
data-test-id="routeHeader"
|
||||
color={"text"}
|
||||
title={title}
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
textOverflow: isEditing ? "initial" : "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
<Flex flexDirection="column">
|
||||
{subtitle && <Text variant="subBody">{subtitle}</Text>}
|
||||
<Input
|
||||
ref={ref}
|
||||
variant="clean"
|
||||
data-test-id="routeHeader"
|
||||
color={"text"}
|
||||
title={title}
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
textOverflow: isEditing ? "initial" : "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
|
||||
p: 0,
|
||||
m: 0,
|
||||
fontWeight: "bold",
|
||||
fontFamily: "heading",
|
||||
fontSize: "heading",
|
||||
border: "none",
|
||||
bg: isEditing ? "bgSecondary" : "transparent",
|
||||
p: 0,
|
||||
m: 0,
|
||||
fontWeight: "bold",
|
||||
fontFamily: "heading",
|
||||
fontSize: subtitle ? "subheading" : "heading",
|
||||
border: "none",
|
||||
bg: isEditing ? "bgSecondary" : "transparent",
|
||||
|
||||
":focus-visible": { outline: "none" },
|
||||
}}
|
||||
onDoubleClick={(e) => {
|
||||
setIsEditing(isEditable && true);
|
||||
e.target.focus();
|
||||
}}
|
||||
onKeyUp={(e) => {
|
||||
if (e.key === "Escape") {
|
||||
e.target.value = title;
|
||||
setIsEditing(false);
|
||||
} else if (e.key === "Enter") {
|
||||
":focus-visible": { outline: "none" },
|
||||
}}
|
||||
onDoubleClick={(e) => {
|
||||
setIsEditing(isEditable && true);
|
||||
e.target.focus();
|
||||
}}
|
||||
onKeyUp={(e) => {
|
||||
if (e.key === "Escape") {
|
||||
e.target.value = title;
|
||||
setIsEditing(false);
|
||||
} else if (e.key === "Enter") {
|
||||
if (onChange) onChange(e.target.value);
|
||||
setIsEditing(false);
|
||||
}
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
if (onChange) onChange(e.target.value);
|
||||
setIsEditing(false);
|
||||
}
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
if (onChange) onChange(e.target.value);
|
||||
setIsEditing(false);
|
||||
}}
|
||||
readOnly={!isEditing}
|
||||
/>
|
||||
}}
|
||||
readOnly={!isEditing}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -44,12 +44,6 @@ const routes = {
|
||||
return {
|
||||
key: "topics",
|
||||
type: "topics",
|
||||
title: notebook.title,
|
||||
isEditable: true,
|
||||
onChange: (title) => {
|
||||
db.notebooks.add({ id: notebookId, title });
|
||||
showToast("success", "Notebook title updated!");
|
||||
},
|
||||
component: <Topics />,
|
||||
buttons: {
|
||||
back: {
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import React from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import ListContainer from "../components/list-container";
|
||||
import { useStore as useNbStore } from "../stores/notebook-store";
|
||||
import { useStore as useAppStore } from "../stores/app-store";
|
||||
import { hashNavigate } from "../navigation";
|
||||
import TopicsPlaceholder from "../components/placeholders/topics-placeholder";
|
||||
import { Button, Flex, Text } from "rebass";
|
||||
import { Edit, RemoveShortcutLink, ShortcutLink } from "../components/icons";
|
||||
import { getTotalNotes } from "../common";
|
||||
import { formatDate } from "notes-core/utils/date";
|
||||
import { db } from "../common/db";
|
||||
|
||||
function Topics() {
|
||||
const selectedNotebookTopics = useNbStore(
|
||||
@@ -20,6 +26,11 @@ function Topics() {
|
||||
items={selectedNotebookTopics}
|
||||
context={{ notebookId: selectedNotebookId }}
|
||||
placeholder={TopicsPlaceholder}
|
||||
header={
|
||||
<NotebookHeader
|
||||
notebook={db.notebooks.notebook(selectedNotebookId).data}
|
||||
/>
|
||||
}
|
||||
button={{
|
||||
content: "Add a new topic",
|
||||
onClick: () => hashNavigate(`/topics/create`),
|
||||
@@ -29,3 +40,60 @@ function Topics() {
|
||||
);
|
||||
}
|
||||
export default Topics;
|
||||
|
||||
function NotebookHeader({ notebook }) {
|
||||
const { title, description, topics, dateEdited } = notebook;
|
||||
const [isShortcut, setIsShortcut] = useState(false);
|
||||
const menuPins = useAppStore((store) => store.menuPins);
|
||||
const pinItemToMenu = useAppStore((store) => store.pinItemToMenu);
|
||||
useEffect(() => {
|
||||
setIsShortcut(menuPins.findIndex((p) => p.id === notebook.id) > -1);
|
||||
}, [menuPins, notebook]);
|
||||
|
||||
return (
|
||||
<Flex flexDirection="column" mx={2} my={2}>
|
||||
<Text variant="subBody">{formatDate(dateEdited)}</Text>
|
||||
<Flex justifyContent="space-between" alignItems="center">
|
||||
<Text variant="heading">{title}</Text>
|
||||
<Flex>
|
||||
<Button
|
||||
variant="tool"
|
||||
sx={{ borderRadius: 100 }}
|
||||
mr={1}
|
||||
p={0}
|
||||
width={30}
|
||||
height={30}
|
||||
title={isShortcut ? "Remove shortcut" : "Create shortcut"}
|
||||
onClick={() => pinItemToMenu(notebook)}
|
||||
>
|
||||
{isShortcut ? (
|
||||
<RemoveShortcutLink size={16} />
|
||||
) : (
|
||||
<ShortcutLink size={16} />
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
variant="tool"
|
||||
sx={{ borderRadius: 100 }}
|
||||
p={0}
|
||||
width={30}
|
||||
height={30}
|
||||
title="Edit notebook"
|
||||
onClick={() => hashNavigate(`/notebooks/${notebook.id}/edit`)}
|
||||
>
|
||||
<Edit size={16} />
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
{description && (
|
||||
<Text variant="body" fontSize="subtitle">
|
||||
{description}
|
||||
</Text>
|
||||
)}
|
||||
<Text as="em" variant="subBody" mt={2}>
|
||||
{topics.length} topic, {getTotalNotes(notebook)} notes
|
||||
</Text>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user