4 files added
4 files modified
| New file |
| | |
| | | --- |
| | | title: Reader Mode |
| | | tags: |
| | | - component |
| | | --- |
| | | |
| | | 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. |
| | | |
| | | ## Configuration |
| | | |
| | | Reader Mode is enabled by default. To disable it, you can remove the component from your layout configuration in `quartz.layout.ts`: |
| | | |
| | | ```ts |
| | | // Remove or comment out this line |
| | | Component.ReaderMode(), |
| | | ``` |
| | | |
| | | ## Usage |
| | | |
| | | The Reader Mode toggle appears as a button with a book icon. When clicked: |
| | | |
| | | - Sidebars are hidden |
| | | - Hovering over the content area reveals the sidebars temporarily |
| | | |
| | | Unlike Dark Mode, Reader Mode state is not persisted between page reloads but is maintained during SPA navigation within the site. |
| | | |
| | | ## Customization |
| | | |
| | | You can customize the appearance of Reader Mode through CSS variables and styles. The component uses the following classes: |
| | | |
| | | - `.readermode`: The toggle button |
| | | - `.readerIcon`: The book icon |
| | | - `[reader-mode="on"]`: Applied to the root element when Reader Mode is active |
| | | |
| | | Example customization in your custom CSS: |
| | | |
| | | ```scss |
| | | .readermode { |
| | | // Customize the button |
| | | svg { |
| | | stroke: var(--custom-color); |
| | | } |
| | | } |
| | | ``` |
| | |
| | | prenav: CustomEvent<{}> |
| | | nav: CustomEvent<{ url: FullSlug }> |
| | | themechange: CustomEvent<{ theme: "light" | "dark" }> |
| | | readermodechange: CustomEvent<{ mode: "on" | "off" }> |
| | | } |
| | | |
| | | type ContentIndex = Record<FullSlug, ContentDetails> |
| | |
| | | grow: true, |
| | | }, |
| | | { Component: Component.Darkmode() }, |
| | | { Component: Component.ReaderMode() }, |
| | | ], |
| | | }), |
| | | Component.Explorer(), |
| New file |
| | |
| | | // @ts-ignore |
| | | import readerModeScript from "./scripts/readermode.inline" |
| | | import styles from "./styles/readermode.scss" |
| | | import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types" |
| | | import { classNames } from "../util/lang" |
| | | |
| | | const ReaderMode: QuartzComponent = ({ displayClass }: QuartzComponentProps) => { |
| | | return ( |
| | | <button class={classNames(displayClass, "readermode")}> |
| | | <svg |
| | | xmlns="http://www.w3.org/2000/svg" |
| | | class="readerIcon" |
| | | viewBox="0 0 24 24" |
| | | fill="none" |
| | | stroke="currentColor" |
| | | stroke-width="2" |
| | | stroke-linecap="round" |
| | | stroke-linejoin="round" |
| | | > |
| | | <rect x="6" y="4" width="12" height="16" rx="1"></rect> |
| | | <line x1="9" y1="8" x2="15" y2="8"></line> |
| | | <line x1="9" y1="12" x2="15" y2="12"></line> |
| | | <line x1="9" y1="16" x2="13" y2="16"></line> |
| | | </svg> |
| | | </button> |
| | | ) |
| | | } |
| | | |
| | | ReaderMode.beforeDOMLoaded = readerModeScript |
| | | ReaderMode.css = styles |
| | | |
| | | export default (() => ReaderMode) satisfies QuartzComponentConstructor |
| | |
| | | import NotFound from "./pages/404" |
| | | import ArticleTitle from "./ArticleTitle" |
| | | import Darkmode from "./Darkmode" |
| | | import ReaderMode from "./ReaderMode" |
| | | import Head from "./Head" |
| | | import PageTitle from "./PageTitle" |
| | | import ContentMeta from "./ContentMeta" |
| | |
| | | TagContent, |
| | | FolderContent, |
| | | Darkmode, |
| | | ReaderMode, |
| | | Head, |
| | | PageTitle, |
| | | ContentMeta, |
| New file |
| | |
| | | let isReaderMode = false |
| | | |
| | | const emitReaderModeChangeEvent = (mode: "on" | "off") => { |
| | | const event: CustomEventMap["readermodechange"] = new CustomEvent("readermodechange", { |
| | | detail: { mode }, |
| | | }) |
| | | document.dispatchEvent(event) |
| | | } |
| | | |
| | | document.addEventListener("nav", () => { |
| | | const switchReaderMode = () => { |
| | | isReaderMode = !isReaderMode |
| | | const newMode = isReaderMode ? "on" : "off" |
| | | document.documentElement.setAttribute("reader-mode", newMode) |
| | | emitReaderModeChangeEvent(newMode) |
| | | } |
| | | |
| | | for (const readerModeButton of document.getElementsByClassName("readermode")) { |
| | | readerModeButton.addEventListener("click", switchReaderMode) |
| | | window.addCleanup(() => readerModeButton.removeEventListener("click", switchReaderMode)) |
| | | } |
| | | |
| | | // Set initial state |
| | | document.documentElement.setAttribute("reader-mode", isReaderMode ? "on" : "off") |
| | | }) |
| | |
| | | border: none; |
| | | width: 20px; |
| | | height: 20px; |
| | | margin: 0 10px; |
| | | margin: 0; |
| | | text-align: inherit; |
| | | flex-shrink: 0; |
| | | |
| New file |
| | |
| | | .readermode { |
| | | cursor: pointer; |
| | | padding: 0; |
| | | position: relative; |
| | | background: none; |
| | | border: none; |
| | | width: 20px; |
| | | height: 20px; |
| | | margin: 0; |
| | | text-align: inherit; |
| | | flex-shrink: 0; |
| | | |
| | | & svg { |
| | | position: absolute; |
| | | width: 20px; |
| | | height: 20px; |
| | | top: calc(50% - 10px); |
| | | stroke: var(--darkgray); |
| | | transition: opacity 0.1s ease; |
| | | } |
| | | } |
| | | |
| | | :root[reader-mode="on"] { |
| | | & .sidebar.left, |
| | | & .sidebar.right { |
| | | opacity: 0; |
| | | transition: opacity 0.2s ease; |
| | | |
| | | &:hover { |
| | | opacity: 1; |
| | | } |
| | | } |
| | | } |