web: add support for top-level notes in notebooks

This commit is contained in:
Abdullah Atta
2023-03-08 14:53:37 +05:00
committed by Abdullah Atta
parent 9804c1fbb1
commit 3f55422d45
13 changed files with 357 additions and 200 deletions

View File

@@ -41,7 +41,7 @@ import { EVENTS } from "@notesnook/core/common";
export const CREATE_BUTTON_MAP = {
notes: {
title: "Make a note",
title: "Add a note",
onClick: () =>
hashNavigate("/notes/create", { addNonce: true, replace: true })
},
@@ -49,6 +49,10 @@ export const CREATE_BUTTON_MAP = {
title: "Create a notebook",
onClick: () => hashNavigate("/notebooks/create", { replace: true })
},
notebook: {
title: "Add a note",
onClick: () => hashNavigate(`/notes/create`, { replace: true })
},
topics: {
title: "Create a topic",
onClick: () => hashNavigate(`/topics/create`, { replace: true })

View File

@@ -35,7 +35,7 @@ import { usePersistentState } from "../../hooks/use-persistent-state";
type MoveDialogProps = { onClose: Perform; noteIds: string[] };
type NotebookReference = {
id: string;
topic: string;
topic?: string;
new: boolean;
op: "add" | "remove";
};
@@ -95,6 +95,19 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
}
}
}
for (const notebook of noteIds
.map((id) => db.relations?.to({ id, type: "note" }, "notebook"))
.flat()) {
if (!notebook) continue;
selected.push({
id: notebook.id,
op: "add",
new: false
});
}
setSelected(selected);
setIsMultiselect(false);
}, [noteIds, notebooks, setSelected, setIsMultiselect]);
@@ -128,19 +141,13 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
notestore.refresh();
const addedTopics = selected.filter((a) => a.op === "add").length;
const removedTopics = selected.filter(
(a) => a.op === "remove"
).length;
showToast(
"success",
`${pluralize(noteIds.length, "note", "notes")} added to ${pluralize(
addedTopics,
"topic",
"topics"
)} & removed from ${pluralize(removedTopics, "topic", "topics")}.`
);
const stringified = stringifySelected(selected);
if (stringified) {
showToast(
"success",
stringified.replace("Add", "Added").replace("remove", "removed")
);
}
onClose(true);
}
@@ -226,10 +233,12 @@ function NotebookItem(props: {
}) {
const { notebook, isSearching, onCreateItem } = props;
const { selected, setIsMultiselect, setSelected } = useSelectionStore();
const { selected, setIsMultiselect, setSelected, isMultiselect } =
useSelectionStore();
const [isCreatingNew, setIsCreatingNew] = useState(false);
const isSelected = isNotebookSelected(notebook, selected);
const index = findSelectionIndex(notebook, selected);
const isSelected = index > -1;
return (
<Box as="li" data-test-id="notebook">
@@ -263,10 +272,14 @@ function NotebookItem(props: {
e.preventDefault();
e.stopPropagation();
setIsMultiselect(true);
setSelected(
selectNotebook(notebook, selected, isSelected || false)
);
const isCtrlPressed = e.ctrlKey || e.metaKey;
if (isCtrlPressed) setIsMultiselect(true);
if (isMultiselect || isCtrlPressed) {
setSelected(selectMultiple(notebook, selected));
} else {
setSelected(selectSingle(notebook, selected));
}
}}
>
<SelectedCheck size={20} selected={isSelected} />
@@ -384,9 +397,9 @@ function TopicItem(props: { topic: Topic }) {
if (isCtrlPressed) setIsMultiselect(true);
if (isMultiselect || isCtrlPressed) {
setSelected(selectMultipleTopics(topic, selected));
setSelected(selectMultiple(topic, selected));
} else {
setSelected(selectSingleTopic(topic, selected));
setSelected(selectSingle(topic, selected));
}
}}
>
@@ -545,22 +558,24 @@ function SelectedCheck({
);
}
function createSelection(topic: Topic): NotebookReference {
function createSelection(topic: Topic | Notebook): NotebookReference {
return {
id: topic.notebookId,
topic: topic.id,
id: "notebookId" in topic ? topic.notebookId : topic.id,
topic: "notebookId" in topic ? topic.id : undefined,
op: "add",
new: true
};
}
function findSelectionIndex(
topic: Topic | NotebookReference,
topic: Topic | NotebookReference | Notebook,
array: NotebookReference[]
) {
return "op" in topic
? array.findIndex((a) => a.id === topic.id && a.topic === topic.topic)
: array.findIndex((a) => a.id === topic.notebookId && a.topic === topic.id);
: "notebookId" in topic
? array.findIndex((a) => a.id === topic.notebookId && a.topic === topic.id)
: array.findIndex((a) => a.id === topic.id && !a.topic);
}
function topicHasNotes(topic: Item, noteIds: string[]) {
@@ -568,36 +583,10 @@ function topicHasNotes(topic: Item, noteIds: string[]) {
return noteIds.some((id) => notes.indexOf(id) > -1);
}
// There are 3 cases:
// 1. Click on a notebook to select/deselect all its topics
// 2. Click on a topic to select/deselect it (and deselect all other topics)
// 3. Ctrl+click on a topic to enter multi select
function selectNotebook(
notebook: Notebook,
selected: NotebookReference[],
isNotebookSelected: boolean
function selectMultiple(
topic: Topic | Notebook,
selected: NotebookReference[]
) {
for (const topic of notebook.topics) {
const index = findSelectionIndex(topic, selected);
const item = selected[index];
// 1. first reset the item's selection state
// 2. set the new state
if (item?.new) selected.splice(index, 1);
else if (item && !item.new) item.op = "remove";
if (!isNotebookSelected) {
if (!item || item.new) selected.push(createSelection(topic));
else if (item && !item.new) item.op = "add";
}
}
return selected;
}
function selectMultipleTopics(topic: Topic, selected: NotebookReference[]) {
const index = findSelectionIndex(topic, selected);
const isSelected = index > -1;
const item = selected[index];
@@ -613,7 +602,7 @@ function selectMultipleTopics(topic: Topic, selected: NotebookReference[]) {
return selected;
}
function selectSingleTopic(topic: Topic, array: NotebookReference[]) {
function selectSingle(topic: Topic | Notebook, array: NotebookReference[]) {
const selected: NotebookReference[] = array.filter((ref) => !ref.new);
const index = findSelectionIndex(topic, array);
@@ -630,19 +619,6 @@ function selectSingleTopic(topic: Topic, array: NotebookReference[]) {
return selected;
}
function isNotebookSelected(notebook: Notebook, selected: NotebookReference[]) {
const selectedTopics = notebook.topics.filter((topic) => {
const index = findSelectionIndex(topic, selected);
return selected[index]?.op === "add";
});
return !selectedTopics.length
? false
: selectedTopics.length === notebook.topics.length
? true
: null;
}
function stringifySelected(suggestion: NotebookReference[]) {
const added = suggestion
.filter((a) => a.op === "add")
@@ -660,7 +636,7 @@ function stringifySelected(suggestion: NotebookReference[]) {
if (removed.length >= 1) {
parts.push("remove from");
parts.push(added[0]);
parts.push(removed[0]);
}
if (removed.length > 1) parts.push(`and ${removed.length - 1} others`);
@@ -668,7 +644,12 @@ function stringifySelected(suggestion: NotebookReference[]) {
}
function resolve(ref: NotebookReference) {
const topic = db.notebooks?.notebook(ref.id)?.topics.topic(ref.topic);
if (!topic) return undefined;
return topic._topic.title;
const notebook = db.notebooks?.notebook(ref.id);
if (!notebook) return undefined;
if (ref.topic) {
return notebook.topics.topic(ref.topic)?._topic?.title;
} else {
return notebook.title;
}
}

View File

@@ -61,7 +61,7 @@ type ListContainerProps = {
context?: Context;
refresh: () => void;
header?: JSX.Element;
placeholder: () => JSX.Element;
placeholder: JSX.Element;
isLoading?: boolean;
button?: {
onClick: () => void;
@@ -136,7 +136,7 @@ function ListContainer(props: ListContainerProps) {
<ListLoader />
) : (
<Flex variant="columnCenterFill" data-test-id="list-placeholder">
<props.placeholder />
{props.placeholder}
</Flex>
)}
</>

View File

@@ -28,6 +28,7 @@ import { db } from "../../common/db";
import { getTotalNotes } from "../../common";
import Reminder from "../reminder";
import type { Reminder as ReminderType } from "@notesnook/core/collections/reminders";
import { useMemo } from "react";
const SINGLE_LINE_HEIGHT = 1.4;
const DEFAULT_LINE_HEIGHT =
@@ -60,19 +61,26 @@ type ItemWrapper<TItem = Item> = (
props: ItemWrapperProps<TItem>
) => JSX.Element;
const NotesProfile: ItemWrapper = ({ index, item, type, context, compact }) => (
<Note
compact={compact}
index={index}
pinnable={!context}
item={item}
tags={getTags(item)}
notebook={getNotebook(item.notebooks as Item[], context?.type)}
reminder={getReminder(item.id)}
date={getDate(item, type)}
context={context}
/>
);
const NotesProfile: ItemWrapper = ({ index, item, type, context, compact }) => {
const references = useMemo(
() => getReferences(item.id, item.notebooks as Item[], context?.type),
[item, context]
);
return (
<Note
compact={compact}
index={index}
pinnable={!context}
item={item}
tags={getTags(item)}
references={references}
reminder={getReminder(item.id)}
date={getDate(item, type)}
context={context}
/>
);
};
const NotebooksProfile: ItemWrapper = ({ index, item, type }) => (
<Notebook
@@ -129,26 +137,36 @@ function getTags(item: Item) {
return tags || [];
}
type NotebookResult =
| {
id: string;
title: string;
dateEdited: number;
topic: { id: string; title: string };
}
| undefined;
type Reference = {
type: "topic" | "notebook";
url: string;
title: string;
};
function getNotebook(
function getReferences(
noteId: string,
notebooks: Item[],
contextType?: string
): NotebookResult | undefined {
if (contextType === "topic" || !notebooks?.length) return;
): { dateEdited: number; references: Reference[] } | undefined {
if (["topic", "notebook"].includes(contextType || "")) return;
return notebooks.reduce<NotebookResult>(function (
prev: NotebookResult,
curr
): NotebookResult {
if (prev) return prev;
const references: Reference[] = [];
let latestDateEdited = 0;
db.relations
?.to({ id: noteId, type: "note" }, "notebook")
?.forEach((notebook: any) => {
references.push({
type: "notebook",
url: `/notebooks/${notebook.id}`,
title: notebook.title
} as Reference);
if (latestDateEdited < notebook.dateEdited)
latestDateEdited = notebook.dateEdited;
});
notebooks?.forEach((curr) => {
const topicId = (curr as NotebookReference).topics[0];
const notebook = db.notebooks?.notebook(curr.id)?.data as NotebookType;
if (!notebook) return;
@@ -156,14 +174,16 @@ function getNotebook(
const topic = notebook.topics.find((t: Item) => t.id === topicId);
if (!topic) return;
return {
id: notebook.id,
title: notebook.title,
dateEdited: notebook.dateEdited,
topic: { id: topicId, title: topic.title }
} as NotebookResult;
},
undefined as NotebookResult);
references.push({
url: `/notebooks/${curr.id}/${topicId}`,
title: topic.title,
type: "topic"
});
if (latestDateEdited < (topic.dateEdited as number))
latestDateEdited = topic.dateEdited as number;
});
return { dateEdited: latestDateEdited, references: references.slice(0, 3) };
}
function getReminder(noteId: string) {

View File

@@ -138,7 +138,7 @@ function ListItem(props) {
backgroundColor: isSelected
? "shade"
: isMenuTarget
: isMenuTarget || isFocused
? "hover"
: background,
@@ -167,9 +167,11 @@ function ListItem(props) {
}}
data-test-id={`list-item`}
>
{!isCompact && props.header}
<Text
data-test-id={`title`}
variant={isSimple ? "body" : "subtitle"}
variant={isSimple || isCompact ? "body" : "subtitle"}
sx={{
whiteSpace: "nowrap",
overflow: "hidden",
@@ -182,8 +184,6 @@ function ListItem(props) {
{props.title}
</Text>
{!isCompact && props.header}
{!isSimple && !isCompact && props.body && (
<Text
as="p"

View File

@@ -49,7 +49,7 @@ import {
function Note(props) {
const {
tags,
notebook,
references,
item,
index,
context,
@@ -115,15 +115,16 @@ function Note(props) {
<Flex
sx={{ alignItems: "center", flexWrap: "wrap", gap: 1, mt: "small" }}
>
{notebook && (
{references?.references?.map((reference) => (
<IconTag
key={reference.url}
onClick={() => {
navigate(`/notebooks/${notebook.id}/${notebook.topic.id}`);
navigate(reference.url);
}}
text={`${notebook.title} ${notebook.topic.title}`}
icon={Icon.Notebook}
text={reference.title}
icon={reference.type === "topic" ? Icon.Topic : Icon.Notebook}
/>
)}
))}
{reminder && isReminderActive(reminder) && (
<IconTag
icon={Icon.Reminder}
@@ -260,7 +261,7 @@ export default React.memo(Note, function (prevProps, nextProps) {
prevItem.conflicted === nextItem.conflicted &&
prevItem.color === nextItem.color &&
prevProps.compact === nextProps.compact &&
prevProps.notebook?.dateEdited === nextProps.notebook?.dateEdited &&
prevProps.references?.dateEdited === nextProps.references?.dateEdited &&
prevProps.reminder?.dateModified === nextProps.reminder?.dateModified &&
JSON.stringify(prevProps.tags) === JSON.stringify(nextProps.tags) &&
JSON.stringify(prevProps.context) === JSON.stringify(nextProps.context)
@@ -543,7 +544,9 @@ function notebooksMenuItems({ note }) {
}
});
if (note && note.notebooks?.length > 0) {
const notebooks = db.relations?.to(note, "notebook");
if (note && (note.notebooks?.length > 0 || notebooks?.length > 0)) {
menuItems.push(
{
key: "remove-from-all-notebooks",
@@ -556,16 +559,28 @@ function notebooksMenuItems({ note }) {
},
{ key: "sep", type: "separator" }
);
note.notebooks.forEach((ref) => {
// if (prev) return prev;
// const topicId = (curr as NotebookReference).topics[0];
notebooks?.forEach((notebook) => {
menuItems.push({
key: notebook.id,
title: notebook.title,
icon: Icon.Notebook,
checked: true,
tooltip: "Click to remove from this notebook",
onClick: async () => {
await db.notes.removeFromNotebook({ id: notebook.id }, note.id);
store.refresh();
}
});
});
note.notebooks?.forEach((ref) => {
const notebook = db.notebooks?.notebook(ref.id);
if (!notebook) return;
const notebookMenuItems = [];
for (const topicId of ref.topics) {
if (!notebook.topics.topic(topicId)) continue;
const topic = notebook.topics.topic(topicId)._topic;
notebookMenuItems.push({
menuItems.push({
key: topicId,
title: topic.title,
icon: Icon.Topic,
@@ -580,13 +595,6 @@ function notebooksMenuItems({ note }) {
}
});
}
menuItems.push({
key: ref.id,
title: notebook.title,
icon: Icon.Notebook2,
items: notebookMenuItems
});
});
}

View File

@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useCallback, useEffect, useState } from "react";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import * as Icon from "../icons";
import { Flex, Text } from "@theme-ui/components";
import { useStore, store } from "../../stores/editor-store";
@@ -86,12 +86,25 @@ function Properties(props) {
const attachments = useAttachmentStore((store) =>
store.attachments.filter((a) => a.noteIds.includes(session.id))
);
const { id: sessionId, color, notebooks, sessionType, dateCreated } = session;
const {
id: sessionId,
color,
notebooks = [],
sessionType,
dateCreated
} = session;
const isPreviewMode = sessionType === "preview";
const reminders = db.relations.from(
{ id: session.id, type: "note" },
"reminder"
);
const allNotebooks = useMemo(
() => [
...notebooks.map((ref) => db.notebooks.notebook(ref.id)?.data),
...db.relations.to({ id: sessionId, type: "note" }, "notebook")
],
[sessionId, notebooks]
);
const changeState = useCallback(
function changeState(prop) {
@@ -246,22 +259,17 @@ function Properties(props) {
</>
)}
</Card>
{notebooks?.length > 0 && (
{allNotebooks?.length > 0 && (
<Card title="Notebooks">
{notebooks.map((ref) => {
const notebook = db.notebooks.notebook(ref.id)?._notebook;
if (!notebook) return null;
return (
<Notebook
key={ref.id}
item={notebook}
date={notebook.dateCreated}
totalNotes={getTotalNotes(notebook)}
simplified
/>
);
})}
{allNotebooks.map((notebook) => (
<Notebook
key={notebook.id}
item={notebook}
date={notebook.dateCreated}
totalNotes={getTotalNotes(notebook)}
simplified
/>
))}
</Card>
)}
{reminders?.length > 0 && (

View File

@@ -25,12 +25,16 @@ import { hashNavigate } from "../../navigation";
import { Flex, Text } from "@theme-ui/components";
import * as Icon from "../icons";
import { Multiselect } from "../../common/multi-select";
import { pluralize } from "../../utils/string";
import { confirm } from "../../common/dialog-controller";
import { useStore as useNotesStore } from "../../stores/note-store";
function Topic({ item, index, onClick }) {
const { id, notebookId } = item;
const topic = item;
const isOpened = useNotesStore(
(store) => store.context?.value?.topic === item.id
);
const totalNotes = useMemo(() => {
return db.notebooks.notebook(notebookId)?.topics.topic(id).totalNotes;
}, [id, notebookId]);
@@ -38,6 +42,8 @@ function Topic({ item, index, onClick }) {
return (
<ListItem
selectable
isFocused={isOpened}
isCompact
item={topic}
onClick={onClick}
title={topic.title}
@@ -49,9 +55,7 @@ function Topic({ item, index, onClick }) {
alignItems: "center"
}}
>
<Text variant="subBody">
{pluralize(totalNotes || 0, "note", "notes")}
</Text>
<Text variant="subBody">{totalNotes}</Text>
</Flex>
}
index={index}

View File

@@ -29,7 +29,6 @@ import { navigate } from "../navigation";
import Trash from "../views/trash";
import { store as notestore } from "../stores/note-store";
import { store as nbstore } from "../stores/notebook-store";
import { showToast } from "../utils/toast";
import Reminders from "../views/reminders";
const routes = {
@@ -59,9 +58,14 @@ const routes = {
const notebook = db.notebooks.notebook(notebookId);
if (!notebook) return false;
nbstore.setSelectedNotebook(notebookId);
notestore.setContext({
type: "notebook",
value: { id: notebookId }
});
return {
key: "topics",
type: "topics",
key: "notebook",
type: "notebook",
component: <Topics />,
buttons: {
back: {
@@ -69,7 +73,7 @@ const routes = {
action: () => navigate("/notebooks")
},
search: {
title: `Search ${notebook.title} topics`
title: `Search ${notebook.title} notes`
}
}
};
@@ -83,16 +87,10 @@ const routes = {
value: { id: notebookId, topic: topicId }
});
return {
key: "notes",
type: "notes",
key: "topic",
type: "notebook",
title: topic.title,
subtitle: notebook.title,
isEditable: true,
onChange: (title) => {
db.notebooks.notebook(notebookId).topics.add({ ...topic, title });
showToast("success", "Topic title updated!");
},
component: <Notes />,
component: <Topics />,
buttons: {
back: {
title: `Go back to ${notebook.title}`,

View File

@@ -187,7 +187,8 @@ class EditorStore extends BaseStore {
if (currentSession.context) {
const { type, value } = currentSession.context;
if (type === "topic") await db.notes.addToNotebook(value, id);
if (type === "topic" || type === "notebook")
await db.notes.addToNotebook(value, id);
else if (type === "color") await db.notes.note(id).color(value);
else if (type === "tag") await db.notes.note(id).tag(value);
// update the note.

View File

@@ -205,6 +205,12 @@ function notesFromContext(context) {
case "color":
notes = db.notes.colored(context.value);
break;
case "notebook": {
const notebook = db.notebooks.notebook(context?.value?.id);
if (!notebook) break;
notes = db.relations.from(notebook.data, "note");
break;
}
case "topic": {
const notebook = db.notebooks.notebook(context?.value?.id);
if (!notebook) break;

View File

@@ -27,7 +27,6 @@ import Config from "../utils/config";
class NotebookStore extends BaseStore {
notebooks = [];
selectedNotebookTopics = [];
selectedNotebookId = 0;
viewMode = Config.get("notebooks:viewMode", "detailed");
@@ -60,13 +59,7 @@ class NotebookStore extends BaseStore {
};
setSelectedNotebook = (id) => {
const topics = db.notebooks.notebook(id)?.topics?.all;
if (!topics) return;
this.set((state) => {
state.selectedNotebookTopics = groupArray(
topics,
db.settings.getGroupOptions("topics")
);
state.selectedNotebookId = id;
});
};

View File

@@ -17,49 +17,183 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useEffect, useState } from "react";
import { useEffect, useMemo, 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 { hashNavigate, navigate } from "../navigation";
import { Button, Flex, Text } from "@theme-ui/components";
import { Edit, RemoveShortcutLink, ShortcutLink } from "../components/icons";
import {
ChevronDown,
ChevronRight,
Edit,
RemoveShortcutLink,
ShortcutLink
} from "../components/icons";
import { getTotalNotes } from "../common";
import { formatDate } from "@notesnook/core/utils/date";
import { db } from "../common/db";
import { pluralize } from "../utils/string";
import { Allotment } from "allotment";
import { Plus } from "../components/icons";
import { useStore as useNotesStore } from "../stores/note-store";
import Placeholder from "../components/placeholders";
function Notebook() {
const [isCollapsed, setIsCollapsed] = useState(false);
function Topics() {
const selectedNotebookTopics = useNbStore(
(store) => store.selectedNotebookTopics
);
const selectedNotebookId = useNbStore((store) => store.selectedNotebookId);
const refresh = useNbStore((store) => store.setSelectedNotebook);
const notebooks = useNbStore((store) => store.notebooks);
const context = useNotesStore((store) => store.context);
const refreshContext = useNotesStore((store) => store.refreshContext);
const isCompact = useNotesStore((store) => store.viewMode === "compact");
useEffect(() => {
if (context && context.value && selectedNotebookId !== context.value.id)
refresh(context.value.id);
}, [selectedNotebookId, context, refresh]);
const selectedNotebook = useMemo(
() => db.notebooks?.notebook(selectedNotebookId)?.data,
[selectedNotebookId, notebooks]
);
if (!context) return null;
return (
<>
{context.type === "topic" && selectedNotebook ? (
<Flex sx={{ alignItems: "center", mx: 2, mb: 1 }}>
{[
{ title: "Notebooks", onClick: () => navigate(`/notebooks/`) },
{
title: selectedNotebook.title,
onClick: () => navigate(`/notebooks/${selectedNotebookId}`)
}
].map((crumb, index, array) => (
<>
<Button
variant="anchor"
sx={{
fontSize: "subBody",
textDecoration: "none",
color: "fontTertiary"
}}
onClick={crumb.onClick}
>
{crumb.title}
</Button>
{index === array.length - 1 ? null : (
<ChevronRight size={18} color="fontTertiary" />
)}
</>
))}
</Flex>
) : null}
<Allotment vertical>
<Allotment.Pane>
<Flex variant="columnFill" sx={{ height: "100%" }}>
<ListContainer
type="notes"
groupType={"notes"}
refresh={refreshContext}
compact={isCompact}
context={{ ...context, notes: undefined }}
items={context.notes}
placeholder={<Placeholder context="notes" />}
header={
context?.type === "topic" ? (
<></>
) : (
<NotebookHeader notebook={selectedNotebook} />
)
}
button={{
content: "Make a new note",
onClick: () =>
hashNavigate("/notes/create", {
addNonce: true,
replace: true
})
}}
/>
</Flex>
</Allotment.Pane>
<Allotment.Pane
preferredSize={250}
visible
maxSize={isCollapsed ? 30 : Infinity}
>
<Topics
selectedNotebook={selectedNotebook}
isCollapsed={isCollapsed}
setIsCollapsed={setIsCollapsed}
/>
</Allotment.Pane>
</Allotment>
</>
);
}
export default Notebook;
function Topics({ selectedNotebook, isCollapsed, setIsCollapsed }) {
const refresh = useNbStore((store) => store.setSelectedNotebook);
return (
<Flex variant="columnFill" sx={{ height: "100%" }}>
<Flex
sx={{
m: 1,
ml: 2,
alignItems: "center",
justifyContent: "space-between",
cursor: "pointer"
}}
onClick={() => setIsCollapsed((s) => !s)}
>
<Flex sx={{ alignItems: "center" }}>
{isCollapsed ? (
<ChevronRight size={16} color="fontTertiary" />
) : (
<ChevronDown size={16} color="fontTertiary" />
)}
<Text variant="subBody" sx={{ fontSize: 11 }}>
TOPICS
</Text>
</Flex>
<Button
variant="tool"
sx={{
p: "1px",
bg: "transparent",
visibility: isCollapsed ? "collapse" : "visible"
}}
onClick={(e) => {
hashNavigate(`/topics/create`);
}}
>
<Plus size={20} />
</Button>
</Flex>
<ListContainer
type="topics"
groupType="topics"
refresh={() => refresh(selectedNotebookId)}
items={selectedNotebookTopics}
context={{ notebookId: selectedNotebookId }}
placeholder={TopicsPlaceholder}
header={
<NotebookHeader
notebook={db.notebooks.notebook(selectedNotebookId).data}
/>
}
refresh={() => refresh(selectedNotebook.id)}
items={selectedNotebook.topics}
context={{
notebookId: selectedNotebook.id
}}
placeholder={<Placeholder context="topics" />}
header={<></>}
button={{
content: "Add a new topic",
onClick: () => hashNavigate(`/topics/create`)
}}
/>
</>
</Flex>
);
}
export default Topics;
function NotebookHeader({ notebook }) {
const { title, description, topics, dateEdited } = notebook;
@@ -73,7 +207,7 @@ function NotebookHeader({ notebook }) {
}, [shortcuts, notebook]);
return (
<Flex mx={2} my={2} sx={{ flexDirection: "column" }}>
<Flex mx={2} my={2} sx={{ flexDirection: "column", minWidth: 200 }}>
<Text variant="subBody">{formatDate(dateEdited)}</Text>
<Flex sx={{ alignItems: "center", justifyContent: "space-between" }}>
<Text variant="heading">{title}</Text>