feat: reader mode
This commit is contained in:
		
							parent
							
								
									bfd72347cf
								
							
						
					
					
						commit
						b34d521293
					
				
							
								
								
									
										44
									
								
								docs/features/reader mode.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								docs/features/reader mode.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,44 @@ | ||||
| --- | ||||
| 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); | ||||
|   } | ||||
| } | ||||
| ``` | ||||
							
								
								
									
										1
									
								
								index.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								index.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -8,6 +8,7 @@ interface CustomEventMap { | ||||
|   prenav: CustomEvent<{}> | ||||
|   nav: CustomEvent<{ url: FullSlug }> | ||||
|   themechange: CustomEvent<{ theme: "light" | "dark" }> | ||||
|   readermodechange: CustomEvent<{ mode: "on" | "off" }> | ||||
| } | ||||
| 
 | ||||
| type ContentIndex = Record<FullSlug, ContentDetails> | ||||
|  | ||||
| @ -35,6 +35,7 @@ export const defaultContentPageLayout: PageLayout = { | ||||
|           grow: true, | ||||
|         }, | ||||
|         { Component: Component.Darkmode() }, | ||||
|         { Component: Component.ReaderMode() }, | ||||
|       ], | ||||
|     }), | ||||
|     Component.Explorer(), | ||||
|  | ||||
							
								
								
									
										32
									
								
								quartz/components/ReaderMode.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								quartz/components/ReaderMode.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | ||||
| // @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 | ||||
| @ -4,6 +4,7 @@ import FolderContent from "./pages/FolderContent" | ||||
| 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" | ||||
| @ -29,6 +30,7 @@ export { | ||||
|   TagContent, | ||||
|   FolderContent, | ||||
|   Darkmode, | ||||
|   ReaderMode, | ||||
|   Head, | ||||
|   PageTitle, | ||||
|   ContentMeta, | ||||
|  | ||||
							
								
								
									
										25
									
								
								quartz/components/scripts/readermode.inline.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								quartz/components/scripts/readermode.inline.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | ||||
| 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") | ||||
| }) | ||||
| @ -6,7 +6,7 @@ | ||||
|   border: none; | ||||
|   width: 20px; | ||||
|   height: 20px; | ||||
|   margin: 0 10px; | ||||
|   margin: 0; | ||||
|   text-align: inherit; | ||||
|   flex-shrink: 0; | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										33
									
								
								quartz/components/styles/readermode.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								quartz/components/styles/readermode.scss
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | ||||
| .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; | ||||
|     } | ||||
|   } | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user