a tool for shared writing and social publishing
1"use client"; 2import { ShortcutKey } from "./Layout"; 3import { Media } from "./Media"; 4import { Popover } from "./Popover"; 5import { metaKey } from "src/utils/metaKey"; 6import { useEntitySetContext } from "./EntitySetProvider"; 7import { useState } from "react"; 8import { ActionButton } from "components/ActionBar/ActionButton"; 9import { HelpSmall } from "./Icons/HelpSmall"; 10import { isMac } from "src/utils/isDevice"; 11import { useIsMobile } from "src/hooks/isMobile"; 12 13export const HelpPopover = (props: { noShortcuts?: boolean }) => { 14 let entity_set = useEntitySetContext(); 15 let isMobile = useIsMobile(); 16 17 return entity_set.permissions.write ? ( 18 <Popover 19 side={isMobile ? "top" : "right"} 20 align={isMobile ? "center" : "start"} 21 asChild 22 className="max-w-xs w-full" 23 trigger={<ActionButton icon={<HelpSmall />} label="About" />} 24 > 25 <div className="flex flex-col text-sm gap-2 text-secondary"> 26 {/* about links */} 27 <HelpLink text="📖 Leaflet Manual" url="https://about.leaflet.pub" /> 28 <HelpLink text="💡 Make with Leaflet" url="https://make.leaflet.pub" /> 29 <HelpLink 30 text="✨ Explore Publications" 31 url="https://leaflet.pub/discover" 32 /> 33 <HelpLink text="📣 Newsletter" url="https://buttondown.com/leaflet" /> 34 {/* contact links */} 35 <div className="columns-2 gap-2"> 36 <HelpLink 37 text="🦋 Bluesky" 38 url="https://bsky.app/profile/leaflet.pub" 39 /> 40 <HelpLink text="💌 Email" url="mailto:contact@leaflet.pub" /> 41 </div> 42 {/* keyboard shortcuts: desktop only */} 43 <Media mobile={false}> 44 {!props.noShortcuts && ( 45 <> 46 <hr className="text-border my-1" /> 47 <div className="flex flex-col gap-1"> 48 <Label>Text Shortcuts</Label> 49 <KeyboardShortcut name="Bold" keys={[metaKey(), "B"]} /> 50 <KeyboardShortcut name="Italic" keys={[metaKey(), "I"]} /> 51 <KeyboardShortcut name="Underline" keys={[metaKey(), "U"]} /> 52 <KeyboardShortcut 53 name="Highlight" 54 keys={[metaKey(), isMac() ? "Ctrl" : "Meta", "H"]} 55 /> 56 <KeyboardShortcut 57 name="Strikethrough" 58 keys={[metaKey(), isMac() ? "Ctrl" : "Meta", "X"]} 59 /> 60 <KeyboardShortcut name="Inline Link" keys={[metaKey(), "K"]} /> 61 62 <Label>Block Shortcuts</Label> 63 {/* shift + up/down arrows (or click + drag): select multiple blocks */} 64 <KeyboardShortcut 65 name="Move Block Up" 66 keys={["Shift", metaKey(), "↑"]} 67 /> 68 <KeyboardShortcut 69 name="Move Block Down" 70 keys={["Shift", metaKey(), "↓"]} 71 /> 72 {/* cmd/ctrl-a: first selects all text in a block; again selects all blocks on page */} 73 {/* cmd/ctrl + up/down arrows: go to beginning / end of doc */} 74 75 <Label>Canvas Shortcuts</Label> 76 <OtherShortcut name="Add Block" description="Double click" /> 77 <OtherShortcut name="Select Block" description="Long press" /> 78 79 <Label>Outliner Shortcuts</Label> 80 <KeyboardShortcut 81 name="Make List" 82 keys={[metaKey(), isMac() ? "Opt" : "Alt", "L"]} 83 /> 84 {/* tab / shift + tab: indent / outdent */} 85 <KeyboardShortcut 86 name="Toggle Checkbox" 87 keys={[metaKey(), "Enter"]} 88 /> 89 <KeyboardShortcut 90 name="Toggle Fold" 91 keys={[metaKey(), "Shift", "Enter"]} 92 /> 93 <KeyboardShortcut 94 name="Fold All" 95 keys={[metaKey(), isMac() ? "Opt" : "Alt", "Shift", "↑"]} 96 /> 97 <KeyboardShortcut 98 name="Unfold All" 99 keys={[metaKey(), isMac() ? "Opt" : "Alt", "Shift", "↓"]} 100 /> 101 </div> 102 </> 103 )} 104 </Media> 105 {/* links: terms and privacy */} 106 <hr className="text-border my-1" /> 107 {/* <HelpLink 108 text="Terms and Privacy Policy" 109 url="https://leaflet.pub/legal" 110 /> */} 111 <div> 112 <a href="https://leaflet.pub/legal" target="_blank"> 113 Terms and Privacy Policy 114 </a> 115 </div> 116 </div> 117 </Popover> 118 ) : null; 119}; 120 121const KeyboardShortcut = (props: { name: string; keys: string[] }) => { 122 return ( 123 <div className="flex gap-2 justify-between items-center"> 124 {props.name} 125 <div className="flex gap-1 items-center font-bold"> 126 {props.keys.map((key, index) => { 127 return <ShortcutKey key={index}>{key}</ShortcutKey>; 128 })} 129 </div> 130 </div> 131 ); 132}; 133 134const OtherShortcut = (props: { name: string; description: string }) => { 135 return ( 136 <div className="flex justify-between items-center"> 137 <span>{props.name}</span> 138 <span> 139 <strong>{props.description}</strong> 140 </span> 141 </div> 142 ); 143}; 144 145const Label = (props: { children: React.ReactNode }) => { 146 return <div className="text-tertiary font-bold pt-2 ">{props.children}</div>; 147}; 148 149const HelpLink = (props: { url: string; text: string }) => { 150 const [isHovered, setIsHovered] = useState(false); 151 const handleMouseEnter = () => { 152 setIsHovered(true); 153 }; 154 const handleMouseLeave = () => { 155 setIsHovered(false); 156 }; 157 return ( 158 <a 159 href={props.url} 160 target="_blank" 161 className="py-2 px-2 rounded-md flex flex-col gap-1 bg-border-light hover:bg-border hover:no-underline" 162 style={{ 163 backgroundColor: isHovered 164 ? "color-mix(in oklab, rgb(var(--accent-contrast)), rgb(var(--bg-page)) 85%)" 165 : "color-mix(in oklab, rgb(var(--accent-contrast)), rgb(var(--bg-page)) 75%)", 166 }} 167 onMouseEnter={handleMouseEnter} 168 onMouseLeave={handleMouseLeave} 169 > 170 <strong>{props.text}</strong> 171 </a> 172 ); 173};