a tool for shared writing and social publishing

some styling updates

+72 -45
+5 -3
app/globals.css
··· 496 496 .footnote-ref { 497 497 counter-increment: footnote; 498 498 cursor: pointer; 499 - color: rgb(var(--accent-contrast)); 499 + color: rgb(var(--color-tertiary)); 500 500 opacity: 0.7; 501 501 } 502 502 .footnote-ref::after { 503 503 content: counter(footnote); 504 504 vertical-align: super; 505 + padding-left: 2px; 505 506 font-size: 75%; 507 + line-height: 0rem; 506 508 } 507 509 .footnote-ref ~ br.ProseMirror-trailingBreak { 508 - display: inline; 509 - width: 4px; 510 + display: inline; 511 + width: 4px; 510 512 } 511 513 .footnote-ref ~ img.ProseMirror-separator { 512 514 display: none;
+16 -3
components/Blocks/index.tsx
··· 14 14 import { v7 } from "uuid"; 15 15 16 16 import { Block } from "./Block"; 17 - import { useEffect } from "react"; 17 + import { useEffect, useState } from "react"; 18 18 import { addShortcut } from "src/shortcuts"; 19 19 import { useHandleDrop } from "./useHandleDrop"; 20 + import { useFootnoteContext } from "components/Footnotes/FootnoteContext"; 20 21 21 22 export function Blocks(props: { entityID: string }) { 22 23 let rep = useReplicache(); ··· 93 94 ), 94 95 ); 95 96 97 + let { footnotes } = useFootnoteContext(); 98 + 99 + let [areFootnotes, setAreFootnotes] = useState(false); 100 + 101 + useEffect(() => { 102 + setAreFootnotes(footnotes.length > 0); 103 + }, [footnotes.length]); 104 + 96 105 return ( 97 106 <div 98 - className={`blocks w-full flex flex-col outline-hidden h-fit min-h-full`} 107 + className={`blocks w-full flex flex-col outline-hidden ${areFootnotes ? "h-fit" : "min-h-full"}`} 99 108 onClick={async (e) => { 100 109 if (!permissions.write) return; 101 110 if (useUIState.getState().selectedBlocks.length > 1) return; ··· 167 176 lastVisibleBlock={lastVisibleBlock || undefined} 168 177 lastRootBlock={lastRootBlock || undefined} 169 178 entityID={props.entityID} 179 + areFootnotes={areFootnotes} 170 180 /> 171 181 </div> 172 182 ); ··· 226 236 lastRootBlock: Block | undefined; 227 237 lastVisibleBlock: Block | undefined; 228 238 entityID: string; 239 + areFootnotes: boolean; 229 240 }) => { 230 241 let { rep } = useReplicache(); 231 242 let entity_set = useEntitySetContext(); ··· 236 247 }); 237 248 238 249 if (!entity_set.permissions.write) return; 250 + if (props.areFootnotes) return; 251 + 239 252 return ( 240 253 <div 241 - className="blockListClickableBottomArea shrink-0 h-[50vh]" 254 + className="blockListClickableBottomArea grow shrink-0 h-[50vh]" 242 255 onClick={() => { 243 256 let newEntityID = v7(); 244 257 if (
+35 -16
components/Footnotes/FootnoteEditor.tsx
··· 12 12 useYJSValue, 13 13 trackUndoRedo, 14 14 } from "components/Blocks/TextBlock/mountProsemirror"; 15 - import { CloseTiny } from "components/Icons/CloseTiny"; 15 + import { DeleteTiny } from "components/Icons/DeleteTiny"; 16 16 import { FootnoteItemLayout } from "./FootnoteItemLayout"; 17 17 import { useEditorStates } from "src/state/useEditorState"; 18 18 import { useUIState } from "src/useUIState"; ··· 46 46 "Shift-Enter": (state, dispatch) => { 47 47 let hardBreak = schema.nodes.hard_break.create(); 48 48 if (dispatch) { 49 - dispatch( 50 - state.tr.replaceSelectionWith(hardBreak).scrollIntoView(), 51 - ); 49 + dispatch(state.tr.replaceSelectionWith(hardBreak).scrollIntoView()); 52 50 } 53 51 return true; 54 52 }, ··· 179 177 return { editorStates: rest }; 180 178 }); 181 179 }; 182 - }, [props.footnoteEntityID, value, props.editable, props.autoFocus, rep.undoManager, pageID]); 180 + }, [ 181 + props.footnoteEntityID, 182 + value, 183 + props.editable, 184 + props.autoFocus, 185 + rep.undoManager, 186 + pageID, 187 + ]); 183 188 184 189 return ( 185 190 <FootnoteItemLayout ··· 194 199 }} 195 200 trailing={ 196 201 props.editable && props.onDelete ? ( 197 - <button 198 - className="shrink-0 mt-0.5 text-tertiary hover:text-primary opacity-0 group-hover/footnote:opacity-100 transition-opacity" 199 - onClick={props.onDelete} 200 - title="Delete footnote" 201 - > 202 - <CloseTiny /> 203 - </button> 202 + <FootnoteDeleteButton 203 + footnoteEntityID={props.footnoteEntityID} 204 + onDelete={props.onDelete} 205 + /> 204 206 ) : undefined 205 207 } 206 208 > 207 - <div 208 - ref={mountRef} 209 - className="outline-hidden" 210 - /> 209 + <div ref={mountRef} className="outline-hidden" /> 211 210 </FootnoteItemLayout> 212 211 ); 213 212 } 214 213 214 + function FootnoteDeleteButton(props: { 215 + footnoteEntityID: string; 216 + onDelete: () => void; 217 + }) { 218 + let isActive = useUIState( 219 + (s) => 220 + s.focusedEntity?.entityType === "footnote" && 221 + s.focusedEntity.entityID === props.footnoteEntityID, 222 + ); 223 + 224 + return ( 225 + <button 226 + className={`shrink-0 mt-0.5 text-tertiary hover:text-accent-contrast transition-opacity ${isActive ? "opacity-100" : "opacity-0"}`} 227 + onClick={props.onDelete} 228 + title="Delete footnote" 229 + > 230 + <DeleteTiny /> 231 + </button> 232 + ); 233 + }
+4 -2
components/Footnotes/FootnoteItemLayout.tsx
··· 10 10 className?: string; 11 11 }) { 12 12 let indexClassName = 13 - "text-accent-contrast font-medium shrink-0 text-xs leading-normal no-underline hover:underline cursor-pointer"; 13 + "text-tertiary font-medium shrink-0 text-xs leading-normal no-underline hover:underline cursor-pointer"; 14 14 15 15 let indexContent = <>{props.index}.</>; 16 16 ··· 48 48 className?: string; 49 49 }) { 50 50 return ( 51 - <div className={`footnote-section px-3 sm:px-4 pb-2 ${props.className ?? ""}`}> 51 + <div 52 + className={`footnote-section px-3 sm:px-4 pb-2 ${props.className ?? ""}`} 53 + > 52 54 <hr className="border-border-light mb-3" /> 53 55 <div className="flex flex-col gap-2">{props.children}</div> 54 56 </div>
+11 -21
components/Toolbar/FootnoteButton.tsx
··· 5 5 import { insertFootnote } from "components/Blocks/TextBlock/insertFootnote"; 6 6 import { TooltipButton } from "components/Buttons"; 7 7 import { Props } from "components/Icons/Props"; 8 + import { ToolbarButton } from "."; 8 9 9 10 export function FootnoteButton() { 10 11 let rep = useReplicache(); ··· 12 13 let focusedBlock = useUIState((s) => s.focusedEntity); 13 14 14 15 return ( 15 - <TooltipButton 16 - tooltipContent={<div className="text-center">Footnote</div>} 17 - onMouseDown={async (e) => { 16 + <ToolbarButton 17 + tooltipContent={"Insert Footnote"} 18 + onClick={async (e) => { 18 19 e.preventDefault(); 19 20 if (!focusedBlock || focusedBlock.entityType !== "block") return; 20 21 let editorState = ··· 29 30 }} 30 31 > 31 32 <FootnoteIcon /> 32 - </TooltipButton> 33 + </ToolbarButton> 33 34 ); 34 35 } 35 36 ··· 43 44 xmlns="http://www.w3.org/2000/svg" 44 45 {...props} 45 46 > 46 - <text 47 - x="6" 48 - y="18" 49 - fontSize="14" 50 - fontWeight="bold" 47 + <path 48 + d="M21.3926 4.21564C21.503 4.21564 21.5926 4.30518 21.5926 4.41564V10.0352C21.5926 10.1456 21.503 10.2352 21.3926 10.2352H20.1584C20.0479 10.2352 19.9584 10.1456 19.9584 10.0352V5.74816C19.9584 5.73289 19.946 5.72052 19.9307 5.72052C19.9257 5.72052 19.9208 5.72187 19.9165 5.72444L18.909 6.32717C18.7757 6.40692 18.6063 6.31088 18.6063 6.15553V5.23309C18.6063 5.16341 18.6426 5.09876 18.702 5.06243L20.0397 4.24498C20.0711 4.22579 20.1072 4.21564 20.144 4.21564H21.3926Z" 51 49 fill="currentColor" 52 - fontFamily="serif" 53 - > 54 - f 55 - </text> 56 - <text 57 - x="14" 58 - y="12" 59 - fontSize="9" 50 + /> 51 + <path 52 + d="M13.6152 5C13.8361 5 14.0156 5.17948 14.0156 5.40039V10.4297C14.254 10.2111 14.5346 9.9596 15.1074 9.66016C15.6803 9.34771 16.1974 9.19924 16.9785 9.19922C17.7797 9.19927 18.356 9.39478 18.958 9.75879V10.0352C18.9581 10.6977 19.4957 11.2352 20.1582 11.2354H20.417C20.4857 11.3518 20.5525 11.4718 20.6133 11.5977C20.9909 12.353 21.1797 13.2326 21.1797 14.2354C21.1797 15.2379 21.1195 16.0091 20.6455 16.9775C20.2754 17.7337 19.8632 18.1974 19.2607 18.6299C18.766 18.9849 17.9146 19.2011 17.0684 19.2012C16.3263 19.2012 15.7474 19.0709 15.2266 18.8105C14.7059 18.5502 14.2825 18.231 13.957 17.8535V18.6299C13.957 18.8478 13.7823 19.026 13.5645 19.0303L12.2158 19.0566C11.992 19.0609 11.8076 18.8802 11.8076 18.6562V5.40039C11.8076 5.17954 11.9872 5.0001 12.208 5H13.6152ZM7.05469 9.16113C8.33062 9.1612 9.28775 9.48019 9.92578 10.1182C10.5637 10.7562 10.8828 11.57 10.8828 12.5596V18.6641C10.8828 18.885 10.7033 19.0645 10.4824 19.0645H9.00586C8.77508 19.0642 8.59172 18.869 8.60645 18.6387L8.65625 17.873C8.34379 18.2896 7.97256 18.6087 7.54297 18.8301C7.11324 19.0514 6.5394 19.1621 5.82324 19.1621C5.14633 19.162 4.56014 19.0514 4.06543 18.8301C3.57077 18.5957 3.18651 18.2766 2.91309 17.873C2.65267 17.4563 2.52246 16.9737 2.52246 16.4268C2.52256 15.6066 2.82203 14.9228 3.4209 14.376C4.03285 13.8161 4.92498 13.4711 6.09668 13.3408L8.65625 13.0674V12.3057C8.65625 11.9932 8.5194 11.7132 8.24609 11.4658C7.97268 11.2055 7.53625 11.0753 6.9375 11.0752C6.41668 11.0752 5.95996 11.2054 5.56934 11.4658C5.30375 11.64 5.0992 11.8786 4.95605 12.1816C4.85494 12.3957 4.61483 12.5278 4.39258 12.4463L3.24316 12.0234C3.04644 11.9511 2.9365 11.7382 3.0127 11.543C3.28354 10.8507 3.73231 10.2976 4.3584 9.88379C5.10064 9.40199 5.99995 9.16116 7.05469 9.16113ZM6.31152 15.0596C5.81698 15.1247 5.44557 15.268 5.19824 15.4893C4.95102 15.6975 4.82725 15.9647 4.82715 16.29C4.82715 16.5895 4.94437 16.8509 5.17871 17.0723C5.42601 17.2805 5.75841 17.3847 6.1748 17.3848C6.70866 17.3848 7.15883 17.2871 7.52344 17.0918C7.90102 16.8965 8.18098 16.6155 8.36328 16.251C8.55847 15.8864 8.65625 15.437 8.65625 14.9033C8.65611 14.8309 8.59244 14.7752 8.52051 14.7842L6.31152 15.0596ZM16.6758 11.1533C15.9644 11.1533 15.6758 11.2705 15.2852 11.5049C14.9076 11.7393 14.6016 12.0844 14.3672 12.54C14.1328 12.9827 14.0156 13.5297 14.0156 14.1807C14.0156 14.8317 14.1329 15.3851 14.3672 15.8408C14.6016 16.2835 14.9076 16.623 15.2852 16.8574C15.6756 17.0917 15.9645 17.209 16.6758 17.209C17.3871 17.2089 17.8897 16.9826 18.3584 16.4229C18.8401 15.863 19.081 15.1337 19.0811 14.2354C19.0811 13.3368 18.8402 12.6068 18.3584 12.0469C17.8897 11.4871 17.387 11.1535 16.6758 11.1533Z" 60 53 fill="currentColor" 61 - fontFamily="sans-serif" 62 - > 63 - n 64 - </text> 54 + /> 65 55 </svg> 66 56 ); 67 57 }
+1
components/Toolbar/FootnoteTextToolbar.tsx
··· 25 25 mark={schema.marks.strong} 26 26 icon={<BoldSmall />} 27 27 /> 28 + 28 29 <TextDecorationButton 29 30 tooltipContent={ 30 31 <div className="flex flex-col gap-1 justify-center">