a tool for shared writing and social publishing
at update/reader 389 lines 17 kB view raw
1import { useEditorStates } from "src/state/useEditorState"; 2import { useUIState } from "src/useUIState"; 3import { schema } from "components/Blocks/TextBlock/schema"; 4import { TextSelection } from "prosemirror-state"; 5import { 6 TextDecorationButton, 7 toggleMarkInFocusedBlock, 8} from "./TextDecorationButton"; 9import * as Popover from "@radix-ui/react-popover"; 10import * as Tooltip from "@radix-ui/react-tooltip"; 11import { theme } from "../../tailwind.config"; 12import { 13 pickers, 14 SectionArrow, 15 setColorAttribute, 16} from "components/ThemeManager/ThemeSetter"; 17import { ColorPicker } from "components/ThemeManager/Pickers/ColorPicker"; 18import { useEntity, useReplicache } from "src/replicache"; 19import { useEffect, useMemo, useState } from "react"; 20import { useColorAttribute } from "components/ThemeManager/useColorAttribute"; 21import { rangeHasMark } from "src/utils/prosemirror/rangeHasMark"; 22 23import { Separator, ShortcutKey } from "components/Layout"; 24import { ToolbarButton } from "."; 25import { NestedCardThemeProvider } from "components/ThemeManager/ThemeProvider"; 26import { Props } from "components/Icons/Props"; 27import { PopoverArrow } from "components/Icons/PopoverArrow"; 28import { ArrowRightTiny } from "components/Icons/ArrowRightTiny"; 29import { PaintSmall } from "components/Icons/PaintSmall"; 30import { isMac } from "src/utils/isDevice"; 31 32export const HighlightButton = (props: { 33 lastUsedHighlight: string; 34 setToolbarState: (s: "highlight") => void; 35}) => { 36 return ( 37 <div className="flex items-center gap-1"> 38 <TextDecorationButton 39 tooltipContent={ 40 <div className="flex flex-col gap-1 justify-center"> 41 <div className="text-center bg-border-light w-fit rounded-md px-0.5 mx-auto "> 42 Highlight 43 </div> 44 <div className="flex gap-1"> 45 {isMac() ? ( 46 <> 47 <ShortcutKey></ShortcutKey> +{" "} 48 <ShortcutKey> Ctrl </ShortcutKey> +{" "} 49 <ShortcutKey> H </ShortcutKey> 50 </> 51 ) : ( 52 <> 53 <ShortcutKey> Ctrl </ShortcutKey> +{" "} 54 <ShortcutKey> Meta </ShortcutKey> +{" "} 55 <ShortcutKey> H </ShortcutKey> 56 </> 57 )} 58 </div> 59 </div> 60 } 61 attrs={{ color: props.lastUsedHighlight }} 62 mark={schema.marks.highlight} 63 icon={ 64 <HighlightSmall 65 highlightColor={ 66 props.lastUsedHighlight === "1" 67 ? theme.colors["highlight-1"] 68 : props.lastUsedHighlight === "2" 69 ? theme.colors["highlight-2"] 70 : theme.colors["highlight-3"] 71 } 72 /> 73 } 74 /> 75 76 <ToolbarButton 77 tooltipContent="Change Highlight Color" 78 onClick={() => { 79 props.setToolbarState("highlight"); 80 }} 81 className="-ml-1" 82 > 83 <ArrowRightTiny /> 84 </ToolbarButton> 85 </div> 86 ); 87}; 88 89export const HighlightToolbar = (props: { 90 onClose: () => void; 91 lastUsedHighlight: "1" | "2" | "3"; 92 setLastUsedHighlight: (color: "1" | "2" | "3") => void; 93 pageID: string; 94}) => { 95 let focusedBlock = useUIState((s) => s.focusedEntity); 96 let focusedEditor = useEditorStates((s) => 97 focusedBlock ? s.editorStates[focusedBlock.entityID] : null, 98 ); 99 let [initialRender, setInitialRender] = useState(true); 100 useEffect(() => { 101 setInitialRender(false); 102 }, []); 103 104 useEffect(() => { 105 // we're not returning initialRender in the dependancy array on purpose! although admittedly, can't remember why not... 106 if (initialRender) return; 107 if (focusedEditor) props.onClose(); 108 }, [focusedEditor, props]); 109 110 return ( 111 <div className="flex w-full justify-between items-center gap-4 text-secondary"> 112 <div className="flex items-center gap-[6px]"> 113 <HighlightColorButton 114 color="1" 115 lastUsedHighlight={props.lastUsedHighlight} 116 setLastUsedHightlight={props.setLastUsedHighlight} 117 /> 118 <HighlightColorButton 119 color="2" 120 lastUsedHighlight={props.lastUsedHighlight} 121 setLastUsedHightlight={props.setLastUsedHighlight} 122 /> 123 <HighlightColorButton 124 color="3" 125 lastUsedHighlight={props.lastUsedHighlight} 126 setLastUsedHightlight={props.setLastUsedHighlight} 127 /> 128 129 <Separator classname="h-6!" /> 130 <HighlightColorSettings pageID={props.pageID} /> 131 </div> 132 </div> 133 ); 134}; 135 136export const HighlightColorButton = (props: { 137 color: "1" | "2" | "3"; 138 lastUsedHighlight: "1" | "2" | "3"; 139 setLastUsedHightlight: (color: "1" | "2" | "3") => void; 140}) => { 141 let focusedBlock = useUIState((s) => s.focusedEntity); 142 let focusedEditor = useEditorStates((s) => 143 focusedBlock ? s.editorStates[focusedBlock.entityID] : null, 144 ); 145 let hasMark: boolean = false; 146 if (focusedEditor) { 147 let { to, from, $cursor } = focusedEditor.editor.selection as TextSelection; 148 149 let mark = rangeHasMark( 150 focusedEditor.editor, 151 schema.marks.highlight, 152 from, 153 to, 154 ); 155 if ($cursor) 156 hasMark = !!schema.marks.highlight.isInSet( 157 focusedEditor.editor.storedMarks || $cursor.marks(), 158 ); 159 else { 160 hasMark = !!mark; 161 } 162 } 163 return ( 164 <button 165 onMouseDown={(e) => { 166 e.preventDefault(); 167 toggleMarkInFocusedBlock(schema.marks.highlight, { 168 color: props.color, 169 }); 170 props.setLastUsedHightlight(props.color); 171 }} 172 > 173 <div 174 className={`w-6 h-6 rounded-md flex items-center justify-center ${props.lastUsedHighlight === props.color ? "bg-border" : ""}`} 175 > 176 <div 177 className={`w-5 h-5 rounded-full border-2 border-white shadow-[0_0_0_1px_#8C8C8C]`} 178 style={{ 179 backgroundColor: 180 props.color === "1" 181 ? theme.colors["highlight-1"] 182 : props.color === "2" 183 ? theme.colors["highlight-2"] 184 : theme.colors["highlight-3"], 185 }} 186 /> 187 </div> 188 </button> 189 ); 190}; 191 192export const HighlightColorSettings = (props: { pageID: string }) => { 193 let { rep, rootEntity } = useReplicache(); 194 let set = useMemo(() => { 195 return setColorAttribute(rep, rootEntity); 196 }, [rep, rootEntity]); 197 198 let [openPicker, setOpenPicker] = useState<pickers>("null"); 199 200 let backgroundImage = useEntity(rootEntity, "theme/background-image"); 201 let backgroundRepeat = useEntity(rootEntity, "theme/background-image-repeat"); 202 let pageBGImage = useEntity(props.pageID, "theme/card-background-image"); 203 let pageBGRepeat = useEntity( 204 props.pageID, 205 "theme/card-background-image-repeat", 206 ); 207 let pageBGOpacity = useEntity( 208 props.pageID, 209 "theme/card-background-image-opacity", 210 ); 211 212 let highlight1Value = useColorAttribute(rootEntity, "theme/highlight-1"); 213 let highlight2Value = useColorAttribute(rootEntity, "theme/highlight-2"); 214 let highlight3Value = useColorAttribute(rootEntity, "theme/highlight-3"); 215 216 return ( 217 <Popover.Root> 218 <Tooltip.Root> 219 <Popover.Trigger asChild> 220 <Tooltip.Trigger 221 className={`rounded-md active:bg-border active:text-primary text-secondary hover:bg-border hover:text-primary`} 222 onMouseDown={(e) => { 223 e.preventDefault(); 224 }} 225 > 226 <PaintSmall /> 227 </Tooltip.Trigger> 228 </Popover.Trigger> 229 230 <Tooltip.Portal> 231 <NestedCardThemeProvider> 232 <Tooltip.Content 233 sideOffset={6} 234 alignOffset={12} 235 className="z-10 bg-border rounded-md py-1 px-[6px] font-bold text-secondary text-sm" 236 > 237 Change Highlight Colors 238 <Tooltip.Arrow asChild width={16} height={8} viewBox="0 0 16 8"> 239 <PopoverArrow 240 arrowFill={theme.colors["border"]} 241 arrowStroke="transparent" 242 /> 243 </Tooltip.Arrow> 244 </Tooltip.Content> 245 </NestedCardThemeProvider> 246 </Tooltip.Portal> 247 248 <Popover.Portal> 249 <NestedCardThemeProvider> 250 <Popover.Content 251 className="themeSetterWrapper z-20 w-80 h-fit max-h-[80vh] bg-white rounded-md border border-border flex" 252 align="center" 253 sideOffset={8} 254 collisionPadding={16} 255 > 256 <div 257 className="bg-bg-leaflet w-full m-2 p-3 pb-0 flex flex-col rounded-md border border-border" 258 style={{ 259 backgroundImage: `url(${backgroundImage?.data.src})`, 260 backgroundRepeat: backgroundRepeat ? "repeat" : "no-repeat", 261 backgroundPosition: "center", 262 backgroundSize: !backgroundRepeat 263 ? "cover" 264 : `calc(${backgroundRepeat.data.value}px / 2 )`, 265 }} 266 > 267 <div className="flex flex-col -mb-[6px] z-10"> 268 <div 269 className="themeHighlightControls flex flex-col gap-2 h-full text-primary bg-bg-leaflet p-2 rounded-md border border-primary shadow-[0_0_0_1px_rgb(var(--bg-page))]" 270 style={{ backgroundColor: "rgba(var(--bg-page), 0.6)" }} 271 > 272 <ColorPicker 273 label="Highlight 1" 274 value={highlight1Value} 275 setValue={(color) => { 276 set("theme/highlight-1")( 277 color.withChannelValue("alpha", 1), 278 ); 279 }} 280 thisPicker={"highlight-1"} 281 openPicker={openPicker} 282 setOpenPicker={setOpenPicker} 283 closePicker={() => setOpenPicker("null")} 284 /> 285 <ColorPicker 286 label="Highlight 2" 287 value={highlight2Value} 288 setValue={set("theme/highlight-2")} 289 thisPicker={"highlight-2"} 290 openPicker={openPicker} 291 setOpenPicker={setOpenPicker} 292 closePicker={() => setOpenPicker("null")} 293 /> 294 <ColorPicker 295 label="Highlight 3" 296 value={highlight3Value} 297 setValue={set("theme/highlight-3")} 298 thisPicker={"highlight-3"} 299 openPicker={openPicker} 300 setOpenPicker={setOpenPicker} 301 closePicker={() => setOpenPicker("null")} 302 /> 303 </div> 304 <SectionArrow 305 fill={theme.colors["primary"]} 306 stroke={theme.colors["bg-page"]} 307 className="ml-2" 308 /> 309 </div> 310 311 <div 312 className="rounded-t-lg p-2 relative border border-border border-b-transparent shadow-md text-primary" 313 style={{ 314 backgroundColor: 315 "rgba(var(--bg-page), var(--bg-page-alpha))", 316 }} 317 > 318 <div 319 className="background absolute top-0 right-0 bottom-0 left-0 z-0 rounded-t-lg" 320 style={{ 321 backgroundImage: `url(${pageBGImage?.data.src})`, 322 backgroundRepeat: pageBGRepeat ? "repeat" : "no-repeat", 323 backgroundPosition: "center", 324 backgroundSize: !pageBGRepeat 325 ? "cover" 326 : `calc(${pageBGRepeat.data.value}px / 2 )`, 327 opacity: pageBGOpacity?.data.value || 1, 328 }} 329 /> 330 <div className="relative flex flex-col"> 331 <p className="font-bold">Pick your highlights!</p> 332 <small className=""> 333 This is what{" "} 334 <span className="highlight bg-highlight-1"> 335 Highlights look like 336 </span> 337 <br /> 338 Make them{" "} 339 <span className="highlight bg-highlight-2"> 340 whatever you want! 341 </span> 342 <br /> 343 <span className="highlight bg-highlight-3"> 344 Happy theming! 345 </span> 346 </small> 347 </div> 348 </div> 349 </div> 350 <Popover.Arrow asChild width={16} height={8} viewBox="0 0 16 8"> 351 <PopoverArrow 352 arrowFill={theme.colors["white"]} 353 arrowStroke={theme.colors["border"]} 354 /> 355 </Popover.Arrow> 356 </Popover.Content> 357 </NestedCardThemeProvider> 358 </Popover.Portal> 359 </Tooltip.Root> 360 </Popover.Root> 361 ); 362}; 363 364const HighlightSmall = (props: { highlightColor: string } & Props) => { 365 let { highlightColor, ...svgProps } = props; 366 return ( 367 <svg 368 width="24" 369 height="24" 370 viewBox="0 0 24 24" 371 fill="none" 372 xmlns="http://www.w3.org/2000/svg" 373 {...svgProps} 374 > 375 <path 376 fillRule="evenodd" 377 clipRule="evenodd" 378 d="M20.5 12C20.5 16.6944 16.6944 20.5 12 20.5C7.30558 20.5 3.5 16.6944 3.5 12C3.5 7.93802 6.34926 4.54153 10.1582 3.70008L9.84791 4.18297C9.77021 4.3039 9.72889 4.44461 9.72888 4.58836L9.72851 10.0094L8.63101 11.7402C8.62242 11.7538 8.61431 11.7675 8.60666 11.7815C8.60016 11.7908 8.59388 11.8004 8.58781 11.8101C8.38051 12.1418 8.46302 12.6162 8.76836 13.1359L7.24363 15.6498C6.74131 16.4779 7.3311 17.5381 8.29965 17.548L11.6224 17.5818C12.2379 17.5881 12.8114 17.2706 13.1328 16.7456L13.4985 16.148C13.9182 16.1939 14.2769 16.1567 14.5378 16.0308C14.6916 15.9756 14.8266 15.8704 14.9178 15.7265L16.0178 13.9918L20.4981 11.8165C20.4994 11.8775 20.5 11.9387 20.5 12ZM20.3169 10.2369L15.1709 12.7355C15.0456 12.7963 14.9397 12.8909 14.8651 13.0086L14.3138 13.878C14.2071 13.7494 14.0889 13.6201 13.9603 13.4913C13.554 13.0742 13.0329 12.6692 12.5492 12.3837C12.4992 12.3542 12.4466 12.3239 12.3919 12.2929C12.0663 12.101 11.7406 11.938 11.4239 11.8053C11.2273 11.719 11.024 11.6386 10.8321 11.5784C10.7486 11.5522 10.6549 11.5258 10.5563 11.505L11.1119 10.6287C11.188 10.5086 11.2285 10.3694 11.2285 10.2272L11.2289 4.80862L12.0696 3.50028C12.4231 3.50311 12.7716 3.52754 13.1136 3.57229C13.0973 3.59203 13.082 3.61297 13.0679 3.63511L12.2981 4.84369C12.2341 4.94402 12.2002 5.06052 12.2002 5.17948V7.82698C12.2002 8.17215 12.48 8.45198 12.8252 8.45198C13.1704 8.45198 13.4502 8.17215 13.4502 7.82698V5.36164L14.1222 4.3067C14.225 4.14534 14.2445 3.95474 14.1919 3.7853C17.2663 4.60351 19.6556 7.10172 20.3169 10.2369ZM22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12ZM12.6338 9.14563C12.979 9.14563 13.2588 9.42545 13.2588 9.77063V10.2416C13.2588 10.5868 12.979 10.8666 12.6338 10.8666C12.2886 10.8666 12.0088 10.5868 12.0088 10.2416V9.77063C12.0088 9.42545 12.2886 9.14563 12.6338 9.14563ZM12.7875 14.4388C12.5032 14.1651 12.1383 13.8821 11.7882 13.6755C11.5214 13.5181 11.1621 13.3284 10.8268 13.1808C10.733 13.1395 10.6444 13.103 10.5629 13.072L8.75514 16.0525L11.6391 16.0819C11.727 16.0828 11.809 16.0374 11.8549 15.9625L12.7875 14.4388Z" 379 fill="currentColor" 380 /> 381 <path 382 fillRule="evenodd" 383 clipRule="evenodd" 384 d="M20.5 12C20.5 16.6944 16.6944 20.5 12 20.5C7.30558 20.5 3.5 16.6944 3.5 12C3.5 7.93801 6.34926 4.54152 10.1582 3.70007L9.84791 4.18296C9.77021 4.30389 9.72889 4.4446 9.72888 4.58835L9.72851 10.0093L8.63101 11.7402C8.62242 11.7537 8.61431 11.7675 8.60666 11.7814C8.60016 11.7908 8.59388 11.8004 8.58781 11.8101C8.38051 12.1418 8.46302 12.6162 8.76836 13.1359L7.24363 15.6498C6.74131 16.4779 7.3311 17.5381 8.29965 17.5479L11.6224 17.5818C12.2379 17.5881 12.8114 17.2705 13.1328 16.7455L13.4985 16.148C13.9182 16.1939 14.2769 16.1567 14.5378 16.0308C14.6916 15.9756 14.8266 15.8704 14.9178 15.7265L16.0178 13.9918L20.4981 11.8164C20.4994 11.8775 20.5 11.9387 20.5 12ZM12.7875 14.4388C12.5032 14.1651 12.1383 13.8821 11.7882 13.6755C11.5214 13.5181 11.1621 13.3284 10.8268 13.1808C10.733 13.1395 10.6444 13.1029 10.5629 13.072L8.75514 16.0525L11.6391 16.0819C11.727 16.0828 11.809 16.0374 11.8549 15.9624L12.7875 14.4388Z" 385 fill={highlightColor} 386 /> 387 </svg> 388 ); 389};