a tool for shared writing and social publishing
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

a huge ass commit for adding highlighting and choosing your own highlight colors

celine 3c01a81b 14196d08

+337 -38
+5 -1
app/globals.css
··· 12 12 13 13 --accent: 0, 0, 225; 14 14 --accent-text: 255, 255, 255; 15 + 16 + --highlight-1: 255, 177, 177; 17 + --highlight-2: 253, 245, 203; 18 + --highlight-3: 224, 244, 255; 15 19 } 16 20 17 21 html, ··· 93 97 } 94 98 95 99 .highlight { 96 - @apply bg-test; 97 100 @apply px-[3px]; 98 101 @apply py-[1px]; 99 102 @apply -mx-[3px]; 100 103 @apply -my-[1px]; 101 104 @apply rounded-[4px]; 105 + @apply box-decoration-clone; 102 106 }
+8 -2
components/TextBlock/RenderYJSFragment.tsx
··· 80 80 em?: {}; 81 81 underline?: {}; 82 82 strikethrough?: {}; 83 - highlight?: {}; 83 + highlight?: { color: string }; 84 84 link?: { href: string }; 85 85 }; 86 86 }; ··· 95 95 (props.style.textDecorationColor = theme.colors.tertiary); 96 96 } 97 97 if (d.attributes?.highlight) { 98 - props.className = "highlight"; 98 + (props.className = "highlight"), 99 + (props.style.backgroundColor = 100 + d.attributes?.highlight.color === "1" 101 + ? theme.colors["highlight-1"] 102 + : d.attributes.highlight.color === "2" 103 + ? theme.colors["highlight-2"] 104 + : theme.colors["highlight-3"]); 99 105 } 100 106 101 107 return props;
+5 -3
components/TextBlock/index.tsx
··· 427 427 } 428 428 429 429 function CommandHandler(props: { entityID: string }) { 430 - let cb = useEditorEventCallback((view, args: { mark: MarkType }) => { 431 - toggleMark(args.mark)(view.state, view.dispatch); 432 - }); 430 + let cb = useEditorEventCallback( 431 + (view, args: { mark: MarkType; attrs?: any }) => { 432 + toggleMark(args.mark, args.attrs)(view.state, view.dispatch); 433 + }, 434 + ); 433 435 useAppEventListener(props.entityID, "toggleMark", cb, []); 434 436 return null; 435 437 }
+17 -4
components/TextBlock/schema.ts
··· 43 43 } as MarkSpec, 44 44 highlight: { 45 45 attrs: { 46 - color: {}, 46 + color: { 47 + default: "1", 48 + }, 47 49 }, 48 - parseDOM: [{ style: `background-color: ${theme.colors.test}` }], 49 - toDOM() { 50 + parseDOM: [ 51 + { 52 + tag: "span", 53 + getAttrs(dom: HTMLElement) { 54 + return { 55 + color: dom.getAttribute("color"), 56 + }; 57 + }, 58 + }, 59 + ], 60 + toDOM(node) { 61 + let { color } = node.attrs; 50 62 return [ 51 63 "span", 52 64 { 53 - class: `highlight`, 65 + class: "highlight", 66 + style: `background-color: ${color === "1" ? theme.colors["highlight-1"] : color === "2" ? theme.colors["highlight-2"] : theme.colors["highlight-3"]}`, 54 67 }, 55 68 0, 56 69 ];
+27 -3
components/ThemeManager/ThemeProvider.tsx
··· 11 11 "--primary": string; 12 12 "--accent": string; 13 13 "--accent-text": string; 14 + "--highlight-1": string; 15 + "--highlight-2": string; 16 + "--highlight-3": string; 14 17 }; 15 18 16 19 export const ThemeDefaults = { ··· 19 22 "theme/primary": "#272727", 20 23 "theme/accent-background": "#0000FF", 21 24 "theme/accent-text": "#FFFFFF", 25 + "theme/highlight-1": "#FFE1DF", 26 + "theme/highlight-2": "#FFF5D2", 27 + "theme/highlight-3": "#F0F7FA", 22 28 }; 23 29 24 30 function setCSSVariableToColor(el: HTMLElement, name: string, value: Color) { ··· 30 36 }) { 31 37 let bgPage = useColorAttribute(props.entityID, "theme/page-background"); 32 38 let bgCard = useColorAttribute(props.entityID, "theme/card-background"); 33 - let bgCardAlpha = useEntity(props.entityID, "theme/card-background-alpha"); 34 39 let primary = useColorAttribute(props.entityID, "theme/primary"); 35 40 let accentBG = useColorAttribute(props.entityID, "theme/accent-background"); 36 41 let accentText = useColorAttribute(props.entityID, "theme/accent-text"); ··· 39 44 props.entityID, 40 45 "theme/background-image-repeat", 41 46 ); 47 + let highlight1 = useColorAttribute(props.entityID, "theme/highlight-1"); 48 + let highlight2 = useColorAttribute(props.entityID, "theme/highlight-2"); 49 + let highlight3 = useColorAttribute(props.entityID, "theme/highlight-3"); 50 + 42 51 useEffect(() => { 43 52 let el = document.querySelector(":root") as HTMLElement; 44 53 if (!el) return; ··· 46 55 setCSSVariableToColor(el, "--bg-card", bgCard); 47 56 el?.style.setProperty( 48 57 "--bg-card-alpha", 49 - (bgCardAlpha?.data.value || 1).toString(), 58 + bgCard.getChannelValue("alpha").toString(), 50 59 ); 51 60 setCSSVariableToColor(el, "--primary", primary); 52 61 setCSSVariableToColor(el, "--accent", accentBG); 53 62 setCSSVariableToColor(el, "--accent-text", accentText); 54 - }, [bgPage, bgCard, primary, accentBG, accentText, bgCardAlpha]); 63 + setCSSVariableToColor(el, "--highlight-1", highlight1); 64 + setCSSVariableToColor(el, "--highlight-2", highlight2); 65 + setCSSVariableToColor(el, "--highlight-3", highlight3); 66 + }, [ 67 + bgPage, 68 + bgCard, 69 + primary, 70 + accentBG, 71 + accentText, 72 + highlight1, 73 + highlight2, 74 + highlight3, 75 + ]); 55 76 return ( 56 77 <div 57 78 className="pageWrapper w-full bg-bg-page text-primary h-full flex flex-col bg-cover bg-center bg-no-repeat items-stretch" ··· 68 89 "--primary": colorToString(primary, "rgb"), 69 90 "--accent": colorToString(accentBG, "rgb"), 70 91 "--accent-text": colorToString(accentText, "rgb"), 92 + "--highlight-1": colorToString(highlight1, "rgb"), 93 + "--highlight-2": colorToString(highlight2, "rgb"), 94 + "--highlight-3": colorToString(highlight3, "rgb"), 71 95 } as CSSProperties 72 96 } 73 97 >
+9 -17
components/ThemeManager/ThemeSetter.tsx
··· 34 34 | "card" 35 35 | "accent" 36 36 | "accentText" 37 - | "text"; 37 + | "text" 38 + | "highlight-1" 39 + | "highlight-2" 40 + | "highlight-3"; 38 41 39 - function setColorAttribute( 42 + export function setColorAttribute( 40 43 rep: Replicache<ReplicacheMutators> | null, 41 44 entity: string, 42 45 ) { ··· 53 56 // I need to get these variables from replicache and then write them to the DB. I also need to parse them into a state that can be used here. 54 57 let pageValue = useColorAttribute(props.entityID, "theme/page-background"); 55 58 let cardValue = useColorAttribute(props.entityID, "theme/card-background"); 56 - let cardBGAlpha = useEntity(props.entityID, "theme/card-background-alpha"); 57 59 let primaryValue = useColorAttribute(props.entityID, "theme/primary"); 58 60 let accentBGValue = useColorAttribute( 59 61 props.entityID, ··· 171 173 <div className="themePageColor flex items-start "> 172 174 <ColorPicker 173 175 label="Page" 176 + alpha 174 177 value={cardValue} 175 - alpha={{ 176 - value: cardBGAlpha?.data.value || 1, 177 - onChange: (a) => { 178 - if (!rep) return; 179 - rep.mutate.assertFact({ 180 - entity: props.entityID, 181 - attribute: "theme/card-background-alpha", 182 - data: { type: "number", value: a }, 183 - }); 184 - }, 185 - }} 186 178 setValue={set("theme/card-background")} 187 179 thisPicker={"card"} 188 180 openPicker={openPicker} ··· 238 230 let thumbStyle = 239 231 "w-4 h-4 rounded-full border-2 border-white shadow-[0_0_0_1px_#8C8C8C,_inset_0_0_0_1px_#8C8C8C]"; 240 232 241 - const ColorPicker = (props: { 233 + export const ColorPicker = (props: { 242 234 label?: string; 243 235 value: Color; 244 - alpha?: { value: number; onChange: (alpha: number) => void }; 236 + alpha?: boolean; 245 237 setValue: (c: Color) => void; 246 238 openPicker: pickers; 247 239 thisPicker: pickers; ··· 602 594 ); 603 595 }; 604 596 605 - const SectionArrow = (props: { 597 + export const SectionArrow = (props: { 606 598 fill: string; 607 599 stroke: string; 608 600 className: string;
+185
components/Toolbar/HighlightButton.tsx
··· 1 + import { setEditorState, useEditorStates } from "src/state/useEditorState"; 2 + import { useUIState } from "src/useUIState"; 3 + import { schema } from "components/TextBlock/schema"; 4 + import { TextSelection } from "prosemirror-state"; 5 + import { toggleMarkInFocusedBlock } from "./TextDecorationButton"; 6 + import * as Popover from "@radix-ui/react-popover"; 7 + import { theme } from "../../tailwind.config"; 8 + 9 + import { 10 + ColorPicker as SpectrumColorPicker, 11 + parseColor, 12 + Color, 13 + ColorArea, 14 + ColorThumb, 15 + ColorSlider, 16 + Input, 17 + ColorField, 18 + SliderTrack, 19 + ColorSwatch, 20 + } from "react-aria-components"; 21 + import { 22 + ColorPicker, 23 + pickers, 24 + SectionArrow, 25 + setColorAttribute, 26 + } from "components/ThemeManager/ThemeSetter"; 27 + import { useEntity, useReplicache } from "src/replicache"; 28 + import { useMemo, useState } from "react"; 29 + import { useColorAttribute } from "components/ThemeManager/useColorAttribute"; 30 + import { useParams } from "next/navigation"; 31 + 32 + export const HighlightColorButton = (props: { 33 + color: "1" | "2" | "3"; 34 + setLastUsedHightlight: (color: "1" | "2" | "3") => void; 35 + }) => { 36 + let focusedBlock = useUIState((s) => s.focusedBlock); 37 + let focusedEditor = useEditorStates((s) => 38 + focusedBlock ? s.editorStates[focusedBlock.entityID] : null, 39 + ); 40 + let hasMark: boolean = false; 41 + if (focusedEditor) { 42 + let { to, from, $cursor } = focusedEditor.editor.selection as TextSelection; 43 + if ($cursor) 44 + hasMark = !!schema.marks.highlight.isInSet( 45 + focusedEditor.editor.storedMarks || $cursor.marks(), 46 + ); 47 + else 48 + hasMark = focusedEditor.editor.doc.rangeHasMark( 49 + from, 50 + to, 51 + schema.marks.highlight, 52 + ); 53 + } 54 + return ( 55 + <button 56 + onClick={(e) => { 57 + e.preventDefault(); 58 + toggleMarkInFocusedBlock(schema.marks.highlight, { 59 + color: props.color, 60 + }); 61 + schema.marks.highlight.create({ color: props.color }); 62 + props.setLastUsedHightlight(props.color); 63 + }} 64 + > 65 + <div 66 + className={`w-6 h-6 rounded-full border-2 border-white shadow-[0_0_0_1px_#8C8C8C]`} 67 + style={{ 68 + backgroundColor: 69 + props.color === "1" 70 + ? theme.colors["highlight-1"] 71 + : props.color === "2" 72 + ? theme.colors["highlight-2"] 73 + : theme.colors["highlight-3"], 74 + }} 75 + /> 76 + </button> 77 + ); 78 + }; 79 + 80 + export const HighlightColorSettings = () => { 81 + let { rep } = useReplicache(); 82 + let params = useParams(); 83 + let docID = params.doc_id as string; 84 + let set = useMemo(() => { 85 + return setColorAttribute(rep, docID); 86 + }, [rep, docID]); 87 + 88 + let [openPicker, setOpenPicker] = useState<pickers>("null"); 89 + 90 + let backgroundImage = useEntity(docID, "theme/background-image"); 91 + let backgroundRepeat = useEntity(docID, "theme/background-image-repeat"); 92 + 93 + let highlight1Value = useColorAttribute(docID, "theme/highlight-1"); 94 + let highlight2Value = useColorAttribute(docID, "theme/highlight-2"); 95 + let highlight3Value = useColorAttribute(docID, "theme/highlight-3"); 96 + 97 + return ( 98 + <Popover.Root> 99 + <Popover.Trigger>settings</Popover.Trigger> 100 + <Popover.Portal> 101 + <Popover.Content 102 + className="themeSetterWrapper z-20 w-80 h-fit max-h-[80vh] bg-white rounded-md border border-border flex" 103 + align="center" 104 + sideOffset={4} 105 + collisionPadding={16} 106 + > 107 + {/* how do i get the bg image? */} 108 + <div 109 + className="bg-bg-page w-full mx-2 p-3 pb-0 my-3 flex flex-col rounded-md border border-border" 110 + style={{ 111 + backgroundImage: `url(${backgroundImage?.data.src})`, 112 + backgroundRepeat: backgroundRepeat ? "repeat" : "no-repeat", 113 + backgroundSize: !backgroundRepeat 114 + ? "cover" 115 + : `calc(${backgroundRepeat.data.value}px / 2 )`, 116 + }} 117 + > 118 + <div className="flex flex-col -mb-[6px] z-10"> 119 + <div 120 + className="themeHighlightControls flex flex-col gap-2 h-full text-primary bg-bg-page p-2 rounded-md border border-primary shadow-[0_0_0_1px_rgb(var(--bg-card))]" 121 + style={{ backgroundColor: "rgba(var(--bg-card), 0.6)" }} 122 + > 123 + <ColorPicker 124 + label="Highlight 1" 125 + value={highlight1Value} 126 + setValue={set("theme/highlight-1")} 127 + thisPicker={"highlight-1"} 128 + openPicker={openPicker} 129 + setOpenPicker={setOpenPicker} 130 + closePicker={() => setOpenPicker("null")} 131 + /> 132 + <ColorPicker 133 + label="Highlight 2" 134 + value={highlight2Value} 135 + setValue={set("theme/highlight-2")} 136 + thisPicker={"highlight-2"} 137 + openPicker={openPicker} 138 + setOpenPicker={setOpenPicker} 139 + closePicker={() => setOpenPicker("null")} 140 + /> 141 + <ColorPicker 142 + label="Highlight 3" 143 + value={highlight3Value} 144 + setValue={set("theme/highlight-3")} 145 + thisPicker={"highlight-3"} 146 + openPicker={openPicker} 147 + setOpenPicker={setOpenPicker} 148 + closePicker={() => setOpenPicker("null")} 149 + /> 150 + </div> 151 + <SectionArrow 152 + fill={theme.colors["primary"]} 153 + stroke={theme.colors["bg-card"]} 154 + className="ml-2" 155 + /> 156 + </div> 157 + 158 + <div 159 + className="rounded-t-lg p-2 border border-border border-b-transparent shadow-md text-primary" 160 + style={{ 161 + backgroundColor: "rgba(var(--bg-card), var(--bg-card-alpha))", 162 + }} 163 + > 164 + <p className="font-bold">Pick your highlights!</p> 165 + <small className=""> 166 + This is what{" "} 167 + <span className="highlight bg-highlight-1"> 168 + Highlights look like 169 + </span> 170 + <br /> 171 + Make them{" "} 172 + <span className="highlight bg-highlight-2"> 173 + whatever you want! 174 + </span> 175 + <br /> 176 + <span className="highlight bg-highlight-3">Happy theming!</span> 177 + </small> 178 + </div> 179 + </div> 180 + <Popover.Arrow /> 181 + </Popover.Content> 182 + </Popover.Portal> 183 + </Popover.Root> 184 + ); 185 + };
+2 -2
components/Toolbar/TextDecorationButton.tsx
··· 37 37 ); 38 38 } 39 39 40 - export function toggleMarkInFocusedBlock(mark: MarkType) { 40 + export function toggleMarkInFocusedBlock(mark: MarkType, attrs?: any) { 41 41 let focusedBlock = useUIState.getState().focusedBlock; 42 42 if (!focusedBlock) return; 43 - publishAppEvent(focusedBlock.entityID, "toggleMark", { mark }); 43 + publishAppEvent(focusedBlock.entityID, "toggleMark", { mark, attrs }); 44 44 }
+61 -1
components/Toolbar/index.tsx
··· 26 26 TextBlockTypeButtons, 27 27 } from "./TextBlockTypeButtons"; 28 28 import { LinkButton, LinkEditor } from "./LinkButton"; 29 + import { 30 + HighlightColorButton, 31 + HighlightColorSettings, 32 + } from "./HighlightButton"; 33 + import { theme } from "../../tailwind.config"; 29 34 30 35 type textState = { 31 36 bold: boolean; ··· 65 70 66 71 export const TextToolbar = () => { 67 72 let [toolbarState, setToolbarState] = useState< 68 - "default" | "link" | "header" | "list" | "block" 73 + "default" | "highlight" | "link" | "header" | "list" | "block" 69 74 >("default"); 70 75 76 + let [lastUsedHighlight, setlastUsedHighlight] = useState<"1" | "2" | "3">( 77 + "1", 78 + ); 79 + 71 80 let state = useTextState(); 72 81 73 82 return ( ··· 95 104 mark={schema.marks.highlight} 96 105 icon={<HighlightSmall />} 97 106 /> 107 + <button 108 + onClick={() => { 109 + setToolbarState("highlight"); 110 + }} 111 + className={`w-6 h-6 rounded-full border-2 border-white shadow-[0_0_0_1px_#8C8C8C]`} 112 + style={{ 113 + backgroundColor: 114 + lastUsedHighlight === "1" 115 + ? theme.colors["highlight-1"] 116 + : lastUsedHighlight === "2" 117 + ? theme.colors["highlight-2"] 118 + : theme.colors["highlight-3"], 119 + }} 120 + /> 121 + 98 122 <Separator /> 99 123 {/* possibly link is only available if text is actively selected */} 100 124 <LinkButton setToolBarState={setToolbarState} /> ··· 114 138 )} 115 139 </ToolbarButton> 116 140 </> 141 + ) : toolbarState === "highlight" ? ( 142 + <HighlightToolbar 143 + onClose={() => setToolbarState("default")} 144 + setLastUsedHighlight={(color: "1" | "2" | "3") => 145 + setlastUsedHighlight(color) 146 + } 147 + /> 117 148 ) : toolbarState === "link" ? ( 118 149 <LinkEditor onClose={() => setToolbarState("default")} /> 119 150 ) : toolbarState === "header" ? ( ··· 131 162 > 132 163 <CloseTiny /> 133 164 </button> 165 + </div> 166 + ); 167 + }; 168 + 169 + const HighlightToolbar = (props: { 170 + onClose: () => void; 171 + setLastUsedHighlight: (color: "1" | "2" | "3") => void; 172 + }) => { 173 + return ( 174 + <div className="flex w-full justify-between items-center gap-4"> 175 + <div className="flex items-center gap-[6px]"> 176 + <ToolbarButton onClick={() => props.onClose()}> 177 + <HighlightSmall /> 178 + </ToolbarButton> 179 + <Separator /> 180 + <HighlightColorButton 181 + color="1" 182 + setLastUsedHightlight={props.setLastUsedHighlight} 183 + /> 184 + <HighlightColorButton 185 + color="2" 186 + setLastUsedHightlight={props.setLastUsedHighlight} 187 + /> 188 + <HighlightColorButton 189 + color="3" 190 + setLastUsedHightlight={props.setLastUsedHighlight} 191 + /> 192 + <HighlightColorSettings /> 193 + </div> 134 194 </div> 135 195 ); 136 196 };
+1 -1
src/eventBus.ts
··· 2 2 import { useEffect } from "react"; 3 3 4 4 type AppEvent = { 5 - toggleMark: { mark: MarkType }; 5 + toggleMark: { mark: MarkType; attrs?: any }; 6 6 }; 7 7 type Listener<T extends keyof AppEvent> = { 8 8 key: string;
+12 -4
src/replicache/attributes.ts
··· 57 57 type: "color", 58 58 cardinality: "one", 59 59 }, 60 - "theme/card-background-alpha": { 61 - type: "number", 62 - cardinality: "one", 63 - }, 64 60 "theme/primary": { 65 61 type: "color", 66 62 cardinality: "one", ··· 79 75 }, 80 76 "theme/background-image-repeat": { 81 77 type: "number", 78 + cardinality: "one", 79 + }, 80 + "theme/highlight-1": { 81 + type: "color", 82 + cardinality: "one", 83 + }, 84 + "theme/highlight-2": { 85 + type: "color", 86 + cardinality: "one", 87 + }, 88 + "theme/highlight-3": { 89 + type: "color", 82 90 cardinality: "one", 83 91 }, 84 92 } as const;
+5
tailwind.config.js
··· 37 37 "bg-page": "rgb(var(--bg-page))", 38 38 "bg-card": "rgb(var(--bg-card))", 39 39 40 + // HIGHLIGHT COLORS 41 + "highlight-1": "rgb(var(--highlight-1))", 42 + "highlight-2": "rgb(var(--highlight-2))", 43 + "highlight-3": "rgb(var(--highlight-3))", 44 + 40 45 //DO NOT USE IN PRODUCTION. Test colors to aid development, ie, setting bg color on element to see edges of div. DO. NOT. USE. IN. PRODUCTION 41 46 test: "#E18181", 42 47 "test-blue": "#48D1EF",