a tool for shared writing and social publishing
298
fork

Configure Feed

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

at 06e5d699fa2fbe87f83df02646cfa1625bcfc166 218 lines 7.0 kB view raw
1import { useEntitySetContext } from "components/EntitySetProvider"; 2import { generateKeyBetween } from "fractional-indexing"; 3import { useCallback, useEffect, useState } from "react"; 4import { useEntity, useReplicache } from "src/replicache"; 5import { useUIState } from "src/useUIState"; 6import { BlockProps } from "./Block"; 7import { v7 } from "uuid"; 8import { useSmoker } from "components/Toast"; 9 10import { Separator } from "components/Layout"; 11import { Input } from "components/Input"; 12import { isUrl } from "src/utils/isURL"; 13import { ButtonPrimary } from "components/Buttons"; 14import { BlockButtonSmall } from "components/Icons/BlockButtonSmall"; 15import { CheckTiny } from "components/Icons/CheckTiny"; 16import { LinkSmall } from "components/Icons/LinkSmall"; 17 18export const ButtonBlock = (props: BlockProps & { preview?: boolean }) => { 19 let { permissions } = useEntitySetContext(); 20 21 let text = useEntity(props.entityID, "button/text"); 22 let url = useEntity(props.entityID, "button/url"); 23 24 let isSelected = useUIState((s) => 25 s.selectedBlocks.find((b) => b.value === props.entityID), 26 ); 27 28 if (!url) { 29 if (!permissions.write) return null; 30 return <ButtonBlockSettings {...props} />; 31 } 32 33 return ( 34 <a 35 href={url?.data.value} 36 target="_blank" 37 className={`hover:outline-accent-contrast rounded-md! ${isSelected ? "block-border-selected border-0!" : "block-border border-transparent! border-0!"}`} 38 > 39 <ButtonPrimary role="link" type="submit"> 40 {text?.data.value} 41 </ButtonPrimary> 42 </a> 43 ); 44}; 45 46const ButtonBlockSettings = (props: BlockProps) => { 47 let { rep } = useReplicache(); 48 let smoker = useSmoker(); 49 let entity_set = useEntitySetContext(); 50 51 let isSelected = useUIState((s) => 52 s.selectedBlocks.find((b) => b.value === props.entityID), 53 ); 54 let isLocked = useEntity(props.entityID, "block/is-locked")?.data.value; 55 56 let [textValue, setTextValue] = useState(""); 57 let [urlValue, setUrlValue] = useState(""); 58 let text = textValue; 59 let url = urlValue; 60 61 let submit = async () => { 62 let entity = props.entityID; 63 if (!entity) { 64 entity = v7(); 65 await rep?.mutate.addBlock({ 66 permission_set: entity_set.set, 67 factID: v7(), 68 parent: props.parent, 69 type: "card", 70 position: generateKeyBetween(props.position, props.nextPosition), 71 newEntityID: entity, 72 }); 73 } 74 75 // if no valid url prefix, default to https 76 if ( 77 !urlValue.startsWith("http") && 78 !urlValue.startsWith("mailto") && 79 !urlValue.startsWith("tel:") 80 ) 81 url = `https://${urlValue}`; 82 83 // these mutations = simpler subset of addLinkBlock 84 if (!rep) return; 85 await rep.mutate.assertFact({ 86 entity: entity, 87 attribute: "block/type", 88 data: { type: "block-type-union", value: "button" }, 89 }); 90 await rep?.mutate.assertFact({ 91 entity: entity, 92 attribute: "button/text", 93 data: { 94 type: "string", 95 value: text, 96 }, 97 }); 98 await rep?.mutate.assertFact({ 99 entity: entity, 100 attribute: "button/url", 101 data: { 102 type: "string", 103 value: url, 104 }, 105 }); 106 }; 107 108 return ( 109 <div className="buttonBlockSettingsWrapper flex flex-col gap-2 w-full"> 110 <ButtonPrimary className="mx-auto"> 111 {text !== "" ? text : "Button"} 112 </ButtonPrimary> 113 114 <form 115 className={` 116 buttonBlockSettingsBorder 117 w-full bg-bg-page 118 text-tertiary hover:text-accent-contrast hover:cursor-pointer hover:p-0 119 flex flex-col gap-2 items-center justify-center hover:border-2 border-dashed rounded-lg 120 ${isSelected ? "border-2 border-tertiary p-0" : "border border-border p-px"} 121 `} 122 onSubmit={(e) => { 123 e.preventDefault(); 124 let rect = document 125 .getElementById("button-block-settings") 126 ?.getBoundingClientRect(); 127 if (!textValue) { 128 smoker({ 129 error: true, 130 text: "missing button text!", 131 position: { 132 y: rect ? rect.top : 0, 133 x: rect ? rect.left + 12 : 0, 134 }, 135 }); 136 return; 137 } 138 if (!urlValue) { 139 smoker({ 140 error: true, 141 text: "missing url!", 142 position: { 143 y: rect ? rect.top : 0, 144 x: rect ? rect.left + 12 : 0, 145 }, 146 }); 147 return; 148 } 149 if (!isUrl(urlValue)) { 150 smoker({ 151 error: true, 152 text: "invalid url!", 153 position: { 154 y: rect ? rect.top : 0, 155 x: rect ? rect.left + 12 : 0, 156 }, 157 }); 158 return; 159 } 160 submit(); 161 }} 162 > 163 <div className="buttonBlockSettingsContent w-full flex flex-col sm:flex-row gap-2 text-secondary px-2 py-3 sm:pb-3 pb-1"> 164 <div className="buttonBlockSettingsTitleInput flex gap-2 w-full sm:w-52"> 165 <BlockButtonSmall 166 className={`shrink-0 ${isSelected ? "text-tertiary" : "text-border"} `} 167 /> 168 <Separator /> 169 <Input 170 type="text" 171 autoFocus 172 className="w-full grow border-none outline-hidden bg-transparent" 173 placeholder="button text" 174 value={textValue} 175 disabled={isLocked} 176 onChange={(e) => setTextValue(e.target.value)} 177 onKeyDown={(e) => { 178 if ( 179 e.key === "Backspace" && 180 !e.currentTarget.value && 181 urlValue !== "" 182 ) 183 e.preventDefault(); 184 }} 185 /> 186 </div> 187 <div className="buttonBlockSettingsLinkInput grow flex gap-2 w-full"> 188 <LinkSmall 189 className={`shrink-0 ${isSelected ? "text-tertiary" : "text-border"} `} 190 /> 191 <Separator /> 192 <Input 193 type="text" 194 id="button-block-url-input" 195 className="w-full grow border-none outline-hidden bg-transparent" 196 placeholder="www.example.com" 197 value={urlValue} 198 disabled={isLocked} 199 onChange={(e) => setUrlValue(e.target.value)} 200 onKeyDown={(e) => { 201 if (e.key === "Backspace" && !e.currentTarget.value) 202 e.preventDefault(); 203 }} 204 /> 205 </div> 206 <button 207 id="button-block-settings" 208 type="submit" 209 className={`p-1 shrink-0 w-fit flex gap-2 items-center place-self-end ${isSelected && !isLocked ? "text-accent-contrast" : "text-accent-contrast sm:text-border"}`} 210 > 211 <div className="sm:hidden block">Save</div> 212 <CheckTiny /> 213 </button> 214 </div> 215 </form> 216 </div> 217 ); 218};