Skip to content

Commit b34d521

Browse files
committed
feat: reader mode
1 parent bfd7234 commit b34d521

File tree

8 files changed

+139
-1
lines changed

8 files changed

+139
-1
lines changed

Diff for: docs/features/reader mode.md

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
---
2+
title: Reader Mode
3+
tags:
4+
- component
5+
---
6+
7+
Reader Mode is a feature that allows users to focus on the content by hiding the sidebars and other UI elements. When enabled, it provides a clean, distraction-free reading experience.
8+
9+
## Configuration
10+
11+
Reader Mode is enabled by default. To disable it, you can remove the component from your layout configuration in `quartz.layout.ts`:
12+
13+
```ts
14+
// Remove or comment out this line
15+
Component.ReaderMode(),
16+
```
17+
18+
## Usage
19+
20+
The Reader Mode toggle appears as a button with a book icon. When clicked:
21+
22+
- Sidebars are hidden
23+
- Hovering over the content area reveals the sidebars temporarily
24+
25+
Unlike Dark Mode, Reader Mode state is not persisted between page reloads but is maintained during SPA navigation within the site.
26+
27+
## Customization
28+
29+
You can customize the appearance of Reader Mode through CSS variables and styles. The component uses the following classes:
30+
31+
- `.readermode`: The toggle button
32+
- `.readerIcon`: The book icon
33+
- `[reader-mode="on"]`: Applied to the root element when Reader Mode is active
34+
35+
Example customization in your custom CSS:
36+
37+
```scss
38+
.readermode {
39+
// Customize the button
40+
svg {
41+
stroke: var(--custom-color);
42+
}
43+
}
44+
```

Diff for: index.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ interface CustomEventMap {
88
prenav: CustomEvent<{}>
99
nav: CustomEvent<{ url: FullSlug }>
1010
themechange: CustomEvent<{ theme: "light" | "dark" }>
11+
readermodechange: CustomEvent<{ mode: "on" | "off" }>
1112
}
1213

1314
type ContentIndex = Record<FullSlug, ContentDetails>

Diff for: quartz.layout.ts

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export const defaultContentPageLayout: PageLayout = {
3535
grow: true,
3636
},
3737
{ Component: Component.Darkmode() },
38+
{ Component: Component.ReaderMode() },
3839
],
3940
}),
4041
Component.Explorer(),

Diff for: quartz/components/ReaderMode.tsx

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// @ts-ignore
2+
import readerModeScript from "./scripts/readermode.inline"
3+
import styles from "./styles/readermode.scss"
4+
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
5+
import { classNames } from "../util/lang"
6+
7+
const ReaderMode: QuartzComponent = ({ displayClass }: QuartzComponentProps) => {
8+
return (
9+
<button class={classNames(displayClass, "readermode")}>
10+
<svg
11+
xmlns="http://www.w3.org/2000/svg"
12+
class="readerIcon"
13+
viewBox="0 0 24 24"
14+
fill="none"
15+
stroke="currentColor"
16+
stroke-width="2"
17+
stroke-linecap="round"
18+
stroke-linejoin="round"
19+
>
20+
<rect x="6" y="4" width="12" height="16" rx="1"></rect>
21+
<line x1="9" y1="8" x2="15" y2="8"></line>
22+
<line x1="9" y1="12" x2="15" y2="12"></line>
23+
<line x1="9" y1="16" x2="13" y2="16"></line>
24+
</svg>
25+
</button>
26+
)
27+
}
28+
29+
ReaderMode.beforeDOMLoaded = readerModeScript
30+
ReaderMode.css = styles
31+
32+
export default (() => ReaderMode) satisfies QuartzComponentConstructor

Diff for: quartz/components/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import FolderContent from "./pages/FolderContent"
44
import NotFound from "./pages/404"
55
import ArticleTitle from "./ArticleTitle"
66
import Darkmode from "./Darkmode"
7+
import ReaderMode from "./ReaderMode"
78
import Head from "./Head"
89
import PageTitle from "./PageTitle"
910
import ContentMeta from "./ContentMeta"
@@ -29,6 +30,7 @@ export {
2930
TagContent,
3031
FolderContent,
3132
Darkmode,
33+
ReaderMode,
3234
Head,
3335
PageTitle,
3436
ContentMeta,

Diff for: quartz/components/scripts/readermode.inline.ts

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
let isReaderMode = false
2+
3+
const emitReaderModeChangeEvent = (mode: "on" | "off") => {
4+
const event: CustomEventMap["readermodechange"] = new CustomEvent("readermodechange", {
5+
detail: { mode },
6+
})
7+
document.dispatchEvent(event)
8+
}
9+
10+
document.addEventListener("nav", () => {
11+
const switchReaderMode = () => {
12+
isReaderMode = !isReaderMode
13+
const newMode = isReaderMode ? "on" : "off"
14+
document.documentElement.setAttribute("reader-mode", newMode)
15+
emitReaderModeChangeEvent(newMode)
16+
}
17+
18+
for (const readerModeButton of document.getElementsByClassName("readermode")) {
19+
readerModeButton.addEventListener("click", switchReaderMode)
20+
window.addCleanup(() => readerModeButton.removeEventListener("click", switchReaderMode))
21+
}
22+
23+
// Set initial state
24+
document.documentElement.setAttribute("reader-mode", isReaderMode ? "on" : "off")
25+
})

Diff for: quartz/components/styles/darkmode.scss

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
border: none;
77
width: 20px;
88
height: 20px;
9-
margin: 0 10px;
9+
margin: 0;
1010
text-align: inherit;
1111
flex-shrink: 0;
1212

Diff for: quartz/components/styles/readermode.scss

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
.readermode {
2+
cursor: pointer;
3+
padding: 0;
4+
position: relative;
5+
background: none;
6+
border: none;
7+
width: 20px;
8+
height: 20px;
9+
margin: 0;
10+
text-align: inherit;
11+
flex-shrink: 0;
12+
13+
& svg {
14+
position: absolute;
15+
width: 20px;
16+
height: 20px;
17+
top: calc(50% - 10px);
18+
stroke: var(--darkgray);
19+
transition: opacity 0.1s ease;
20+
}
21+
}
22+
23+
:root[reader-mode="on"] {
24+
& .sidebar.left,
25+
& .sidebar.right {
26+
opacity: 0;
27+
transition: opacity 0.2s ease;
28+
29+
&:hover {
30+
opacity: 1;
31+
}
32+
}
33+
}

0 commit comments

Comments
 (0)