Skip to content

Commit

Permalink
feat: add bookmarks tab
Browse files Browse the repository at this point in the history
  • Loading branch information
LiprikON2 committed May 7, 2024
1 parent 9827191 commit 09f7ac2
Show file tree
Hide file tree
Showing 9 changed files with 178 additions and 36 deletions.
10 changes: 8 additions & 2 deletions src/renderer/layouts/LayoutReading.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React from "react";
import { Outlet } from "@tanstack/react-router";
import { IconList, IconSpeakerphone } from "@tabler/icons-react";
import { IconBookmarks, IconList, IconSpeakerphone } from "@tabler/icons-react";
import { observer } from "mobx-react-lite";

import { Toc, AppShell, LayoutMarkup, TextToSpeech } from "~/renderer/scenes";
import { Toc, AppShell, LayoutMarkup, TextToSpeech, Bookmarks } from "~/renderer/scenes";
import { getHomeMarkup } from "./sharedLayoutMarkup";

const readingLayoutMarkup: LayoutMarkup = {
Expand All @@ -21,6 +21,12 @@ const readingLayoutMarkup: LayoutMarkup = {
innerTabs: [],
Component: Toc,
},
{
name: "Bookmarks",
Icon: IconBookmarks,
innerTabs: [],
Component: Bookmarks,
},
],
navbarTopSection: null,

Expand Down
27 changes: 10 additions & 17 deletions src/renderer/scenes/Reading/Reading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,22 +88,15 @@ export const Reading = observer(() => {
});

return (
<>
{/* {bookReadStore.isManualBookmarked ? (
<button onClick={bookReadStore.removeManualBookmark}>del bookmark</button>
) : (
<button onClick={bookReadStore.addManualBookmark}>add bookmark</button>
)} */}
<BookUi
title={bookReadStore.metadata.title}
uiState={bookReadStore.uiState}
bookmarked={bookReadStore.isManualBookmarked}
onAddBookmark={bookReadStore.addManualBookmark}
onRemoveBookmark={bookReadStore.removeManualBookmark}
>
{!bookReadStore.isReady && "loading..."}
<book-web-component ref={useMergedRef(bookComponentCallbackRef, eventsRef)} />
</BookUi>
</>
<BookUi
title={bookReadStore.metadata.title}
uiState={bookReadStore.uiState}
bookmarked={bookReadStore.isManualBookmarked}
onAddBookmark={bookReadStore.addManualBookmark}
onRemoveBookmark={bookReadStore.removeManualBookmark}
>
{!bookReadStore.isReady && "loading..."}
<book-web-component ref={useMergedRef(bookComponentCallbackRef, eventsRef)} />
</BookUi>
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
.navLink {
border-end-start-radius: var(--mantine-radius-md);
border-start-start-radius: var(--mantine-radius-md);

@media (max-width: $mantine-breakpoint-sm) {
border-radius: var(--mantine-radius-md);
}

border: rem(1px) transparent solid;
&[data-active="true"] {
/* margin-right: 0.5px; */

border-right: transparent;
@mixin dark {
border-color: var(--nl-bg);
background: linear-gradient(90deg, var(--nl-bg) 0%, var(--mantine-color-dark-6) 100%);
}

@mixin light {
border-color: var(--mantine-color-default-border);
background: linear-gradient(90deg, var(--nl-bg) 0%, var(--mantine-color-gray-1) 100%);
}
}

&:not([data-active="true"]):hover {
border-right: rem(1px) var(--mantine-color-default-border) solid;
}
}

.label {
display: block;
line-height: 1.5;
}

.icon {
width: rem(18);
height: rem(18);
}
50 changes: 50 additions & 0 deletions src/renderer/scenes/Reading/components/Bookmarks/Bookmarks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from "react";
import { NavLink, Stack, Text, rem } from "@mantine/core";
import { IconBookmark } from "@tabler/icons-react";
import { observer } from "mobx-react-lite";

import { type Structure, useBookReadStore } from "~/renderer/stores";
import classes from "./Bookmarks.module.css";
import { action } from "mobx";

interface BookmarksProps {
autoscrollTargetRef: (node: any) => void;
}

export const Bookmarks = observer(({ autoscrollTargetRef }: BookmarksProps) => {
const bookReadStore = useBookReadStore();

const navTo = action((sectionId: Structure["sectionId"], elementIndex: number) => {
if (!sectionId || !bookReadStore.isReady) return;
bookReadStore.bookComponent.navToLink(sectionId, { elementIndex });
});

if (!bookReadStore.bookmarks.length)
return (
<Text c="dimmed" size="sm" px="sm">
No bookmarks saved yet.
</Text>
);
return bookReadStore.bookmarks.map(
({ active, sectionId, label, elementIndex, elementSection }) => (
<NavLink
key={`${elementSection}-${elementIndex}`}
my={2}
leftSection={
<Stack gap={2} align="center" miw={24}>
<IconBookmark className={classes.icon} stroke={1.5} />
<Text c="dimmed" size={rem(11)}>
{elementSection}-{elementIndex}
</Text>
</Stack>
}
className={classes.navLink}
classNames={{ label: classes.label }}
label={label}
active={active}
ref={active ? autoscrollTargetRef : null}
onClick={() => navTo(sectionId, elementIndex)}
/>
)
);
});
1 change: 1 addition & 0 deletions src/renderer/scenes/Reading/components/Bookmarks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./Bookmarks";
4 changes: 2 additions & 2 deletions src/renderer/scenes/Reading/components/Toc/hooks/useTocNav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { useMemo } from "react";
import { action } from "mobx";

import { useBookReadStore, type Structure } from "~/renderer/stores";
import { TocChildProps } from "../components";
import { TocState } from "../../BookWebComponent";
import type { TocChildProps } from "../components";
import type { TocState } from "../../../scenes/BookWebComponent";

const checkIfHasSelectChild = (tocProp: TocChildProps): boolean => {
if (tocProp.isSelected) return true;
Expand Down
1 change: 1 addition & 0 deletions src/renderer/scenes/Reading/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./Bookmarks";
export * from "./BookUi";
export * from "./TextToSpeech";
export * from "./Toc";
Original file line number Diff line number Diff line change
Expand Up @@ -474,26 +474,26 @@ export default class BookWebComponent extends HTMLElement {
let [sectionName, markerId] = target.href.split("#").pop().split(",");
markerId = "#" + markerId;

const isLinkValid = this.navToLink(sectionName, markerId);
const isLinkValid = this.navToLink(sectionName, { markerId });

// Opens link in external browser
if (!isLinkValid && target.href) window.open(target.href, "_blank");
}

navToLink(sectionId: string, markerId?: string) {
navToLink(sectionId: string, position?: Position) {
const sectionIndex = this.book.sectionNames.findIndex(
(sectionName) => sectionName === sectionId
);
const isLinkValid = sectionIndex !== -1;

if (isLinkValid) {
const position = { markerId };

if (this.state.book.currentSection === sectionIndex) this.navigateToPosition(position);
else this.loadSection(sectionIndex, position);
const doesSectionExist = sectionIndex !== -1;

if (doesSectionExist) {
const isWithingCurrentSection = sectionIndex === this.state.book.currentSection;
if (isWithingCurrentSection) {
if (position) this.navigateToPosition(position);
else this.navigateToPosition({ elementIndex: 0 });
} else this.loadSection(sectionIndex, position);
}

return isLinkValid;
return doesSectionExist;
}

/**
Expand Down Expand Up @@ -660,7 +660,7 @@ export default class BookWebComponent extends HTMLElement {
}

private navigateToPosition({ sectionPage, elementIndex, elementSelector }: Position) {
if (elementSelector || elementIndex) {
if (elementSelector || elementIndex || elementIndex === 0) {
this.shiftToElement({ elementIndex, elementSelector });
} else if (sectionPage) {
if (!sectionPage.isFromBack) {
Expand Down Expand Up @@ -689,7 +689,7 @@ export default class BookWebComponent extends HTMLElement {
{ element, elementIndex, elementSelector }: Position,
callback = () => this.onSectionShift()
) {
if (elementIndex) {
if (elementIndex || elementIndex === 0) {
element = this.getElementByIndex(elementIndex);
} else if (elementSelector) {
element = this.contentElem.querySelector(elementSelector);
Expand Down
57 changes: 55 additions & 2 deletions src/renderer/stores/BookReadStore/BookReadStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,39 @@ export class BookReadStore {
);
}

getSectionTitle(sectionIndex: number, toc = this.toc, root = true): string {
const { sectionNames } = this.contentState;

let descendantSectionTitle;
for (const tocEntry of toc) {
const tocEntryChildren = tocEntry?.children;
if (tocEntryChildren) {
descendantSectionTitle = this.getSectionTitle(
sectionIndex,
tocEntryChildren,
false
);
if (descendantSectionTitle) break;
}
}

const tocEntry = toc.find((tocEntry) => tocEntry.sectionId === sectionNames[sectionIndex]);
const sectionTitle = tocEntry?.name;

if (descendantSectionTitle) {
// Use the deep-nested title if possible
return descendantSectionTitle;
} else if (sectionTitle) {
return sectionTitle;
} else if (root && sectionIndex >= 0 && sectionIndex < sectionNames.length) {
// Untitled sections try to use previous section's title
const prevSectionTitle = this.getSectionTitle(sectionIndex - 1, toc);
return prevSectionTitle;
} else {
return "Unknown chapter";
}
}

load() {
const { elementIndex } = this.autobookmark;
const { sectionNames } = this.contentState;
Expand Down Expand Up @@ -222,12 +255,32 @@ export class BookReadStore {
this.bookmarkablePositions = bookmarkablePositions;
}

get manualBookmarks() {
const interactionState = this.getBookInteraction(this.bookKey);
return interactionState.bookmarks.manual;
}

get bookmarks() {
const currentManualBookmarks = this.currentManualBookmarks;
return this.manualBookmarks
.map((manualBookmark) => ({
...manualBookmark,
sectionId: this.contentState.sectionNames[manualBookmark.elementSection],
label: this.getSectionTitle(manualBookmark.elementSection),
active: _.some(currentManualBookmarks, (currentManualBookmark) =>
_.isEqual(currentManualBookmark, manualBookmark)
),
}))
.toSorted(
(a, b) => a.elementSection - b.elementSection || a.elementIndex - b.elementIndex
);
}

/**
* Returns all manual bookmarks on current page
*/
get currentManualBookmarks(): Bookmark[] {
const interactionState = this.getBookInteraction(this.bookKey);
const { manual } = interactionState.bookmarks;
const manual = this.manualBookmarks;

const currentManuakBookmarks = _.intersectionWith(
manual,
Expand Down

0 comments on commit 09f7ac2

Please sign in to comment.