a tool for shared writing and social publishing
1import { useEntity, useReplicache } from "src/replicache";
2import { ToolbarButton } from ".";
3import { Separator, ShortcutKey } from "components/Layout";
4import { metaKey } from "src/utils/metaKey";
5import { useUIState } from "src/useUIState";
6import { LockBlockButton } from "./LockBlockButton";
7import { TextAlignmentButton } from "./TextAlignmentToolbar";
8import {
9 ImageFullBleedButton,
10 ImageAltTextButton,
11 ImageCoverButton,
12} from "./ImageToolbar";
13import { DeleteSmall } from "components/Icons/DeleteSmall";
14import { getSortedSelection } from "components/SelectionManager/selectionState";
15
16export const BlockToolbar = (props: {
17 setToolbarState: (
18 state: "areYouSure" | "block" | "text-alignment" | "img-alt-text",
19 ) => void;
20}) => {
21 let focusedEntity = useUIState((s) => s.focusedEntity);
22 let focusedEntityType = useEntity(
23 focusedEntity?.entityType === "page"
24 ? focusedEntity.entityID
25 : focusedEntity?.parent || null,
26 "page/type",
27 );
28 let blockType = useEntity(
29 focusedEntity?.entityType === "block" ? focusedEntity?.entityID : null,
30 "block/type",
31 )?.data.value;
32
33 return (
34 <div className="flex items-center gap-2 justify-between w-full">
35 <div className="flex items-center gap-2">
36 <ToolbarButton
37 onClick={() => {
38 props.setToolbarState("areYouSure");
39 }}
40 tooltipContent="Delete Block"
41 >
42 <DeleteSmall />
43 </ToolbarButton>
44 <Separator classname="h-6!" />
45 <MoveBlockButtons />
46 {blockType === "image" && (
47 <>
48 <TextAlignmentButton setToolbarState={props.setToolbarState} />
49 <ImageFullBleedButton />
50 <ImageAltTextButton setToolbarState={props.setToolbarState} />
51 <ImageCoverButton />
52 {focusedEntityType?.data.value !== "canvas" && (
53 <Separator classname="h-6!" />
54 )}
55 </>
56 )}
57 {(blockType === "button" || blockType === "datetime") && (
58 <>
59 <TextAlignmentButton setToolbarState={props.setToolbarState} />
60 {focusedEntityType?.data.value !== "canvas" && (
61 <Separator classname="h-6!" />
62 )}
63 </>
64 )}
65
66 <LockBlockButton />
67 </div>
68 </div>
69 );
70};
71
72const MoveBlockButtons = () => {
73 let { rep } = useReplicache();
74 return (
75 <>
76 <ToolbarButton
77 hiddenOnCanvas
78 onClick={async () => {
79 if (!rep) return;
80 let [sortedBlocks, siblings] = await getSortedSelection(rep);
81 if (sortedBlocks.length > 1) return;
82 let block = sortedBlocks[0];
83 let previousBlock =
84 siblings?.[siblings.findIndex((s) => s.value === block.value) - 1];
85 if (previousBlock.value === block.listData?.parent) {
86 previousBlock =
87 siblings?.[
88 siblings.findIndex((s) => s.value === block.value) - 2
89 ];
90 }
91
92 if (
93 previousBlock?.listData &&
94 block.listData &&
95 block.listData.depth > 1 &&
96 !previousBlock.listData.path.find(
97 (f) => f.entity === block.listData?.parent,
98 )
99 ) {
100 let depth = block.listData.depth;
101 let newParent = previousBlock.listData.path.find(
102 (f) => f.depth === depth - 1,
103 );
104 if (!newParent) return;
105 if (useUIState.getState().foldedBlocks.includes(newParent.entity))
106 useUIState.getState().toggleFold(newParent.entity);
107 rep?.mutate.moveBlock({
108 block: block.value,
109 oldParent: block.listData?.parent,
110 newParent: newParent.entity,
111 position: { type: "end" },
112 });
113 } else {
114 rep?.mutate.moveBlockUp({
115 entityID: block.value,
116 parent: block.listData?.parent || block.parent,
117 });
118 }
119 }}
120 tooltipContent={
121 <div className="flex flex-col gap-1 justify-center">
122 <div className="text-center">Move Up</div>
123 <div className="flex gap-1">
124 <ShortcutKey>Shift</ShortcutKey> +{" "}
125 <ShortcutKey>{metaKey()}</ShortcutKey> +{" "}
126 <ShortcutKey> ↑ </ShortcutKey>
127 </div>
128 </div>
129 }
130 >
131 <MoveBlockUp />
132 </ToolbarButton>
133
134 <ToolbarButton
135 hiddenOnCanvas
136 onClick={async () => {
137 if (!rep) return;
138 let [sortedBlocks, siblings] = await getSortedSelection(rep);
139 if (sortedBlocks.length > 1) return;
140 let block = sortedBlocks[0];
141 let nextBlock = siblings
142 .slice(siblings.findIndex((s) => s.value === block.value) + 1)
143 .find(
144 (f) =>
145 f.listData &&
146 block.listData &&
147 !f.listData.path.find((f) => f.entity === block.value),
148 );
149 if (
150 nextBlock?.listData &&
151 block.listData &&
152 nextBlock.listData.depth === block.listData.depth - 1
153 ) {
154 if (useUIState.getState().foldedBlocks.includes(nextBlock.value))
155 useUIState.getState().toggleFold(nextBlock.value);
156 rep?.mutate.moveBlock({
157 block: block.value,
158 oldParent: block.listData?.parent,
159 newParent: nextBlock.value,
160 position: { type: "first" },
161 });
162 } else {
163 rep?.mutate.moveBlockDown({
164 entityID: block.value,
165 parent: block.listData?.parent || block.parent,
166 });
167 }
168 }}
169 tooltipContent={
170 <div className="flex flex-col gap-1 justify-center">
171 <div className="text-center">Move Down</div>
172 <div className="flex gap-1">
173 <ShortcutKey>Shift</ShortcutKey> +{" "}
174 <ShortcutKey>{metaKey()}</ShortcutKey> +{" "}
175 <ShortcutKey> ↓ </ShortcutKey>
176 </div>
177 </div>
178 }
179 >
180 <MoveBlockDown />
181 </ToolbarButton>
182 <Separator classname="h-6!" />
183 </>
184 );
185};
186
187const MoveBlockDown = () => {
188 return (
189 <svg
190 width="24"
191 height="24"
192 viewBox="0 0 24 24"
193 fill="none"
194 xmlns="http://www.w3.org/2000/svg"
195 >
196 <path
197 fillRule="evenodd"
198 clipRule="evenodd"
199 d="M18.3444 3.56272L3.89705 5.84775C3.48792 5.91246 3.20871 6.29658 3.27342 6.7057L3.83176 10.2358C3.89647 10.645 4.28058 10.9242 4.68971 10.8595L19.137 8.57444C19.5462 8.50973 19.8254 8.12561 19.7607 7.71649L19.2023 4.18635C19.1376 3.77722 18.7535 3.49801 18.3444 3.56272ZM3.70177 4.61309C2.69864 4.77175 1.9884 5.65049 2.01462 6.63905C1.6067 6.92894 1.37517 7.43373 1.45854 7.96083L2.02167 11.5213C2.19423 12.6123 3.21854 13.3568 4.30955 13.1843L15.5014 11.4142L15.3472 10.4394L16.6131 10.2392L17.2948 13.9166L15.3038 12.4752C14.9683 12.2322 14.4994 12.3073 14.2565 12.6428C14.0135 12.9783 14.0886 13.4472 14.4241 13.6902L18.5417 16.6712L21.5228 12.5536C21.7658 12.2181 21.6907 11.7492 21.3552 11.5063C21.0197 11.2634 20.5508 11.3385 20.3079 11.674L18.7926 13.7669L18.0952 10.0048L19.3323 9.80909C20.4233 9.63654 21.1679 8.61222 20.9953 7.52121L20.437 3.99107C20.2644 2.90007 19.2401 2.15551 18.1491 2.32807L3.70177 4.61309ZM12.5175 14.1726C12.8583 14.118 13.0904 13.7974 13.0358 13.4566C12.9812 13.1157 12.6606 12.8837 12.3198 12.9383L4.48217 14.1937C3.37941 14.3704 2.62785 15.4065 2.80232 16.5096L3.35244 19.9878C3.52716 21.0925 4.56428 21.8463 5.66893 21.6716L20.0583 19.3958C21.1618 19.2212 21.9155 18.186 21.7426 17.0822L21.6508 16.4961C21.5974 16.1551 21.2776 15.922 20.9366 15.9754C20.5956 16.0288 20.3624 16.3486 20.4158 16.6896L20.5077 17.2757C20.5738 17.6981 20.2854 18.0943 19.8631 18.1611L5.47365 20.437C5.05089 20.5038 4.65396 20.2153 4.5871 19.7925L4.03697 16.3143C3.9702 15.8921 4.25783 15.4956 4.67988 15.428L12.5175 14.1726ZM5.48645 8.13141C5.4213 7.72235 5.70009 7.33793 6.10914 7.27278L12.7667 6.21241C13.1757 6.14726 13.5602 6.42605 13.6253 6.83511C13.6905 7.24417 13.4117 7.62859 13.0026 7.69374L6.34508 8.75411C5.93602 8.81926 5.5516 8.54047 5.48645 8.13141Z"
200 fill="currentColor"
201 />
202 </svg>
203 );
204};
205
206const MoveBlockUp = () => {
207 return (
208 <svg
209 width="24"
210 height="24"
211 viewBox="0 0 24 24"
212 fill="none"
213 xmlns="http://www.w3.org/2000/svg"
214 >
215 <path
216 fillRule="evenodd"
217 clipRule="evenodd"
218 d="M4.12086 10.3069C3.69777 10.3744 3.30016 10.0858 3.23323 9.66265L2.68364 6.18782C2.61677 5.76506 2.90529 5.36813 3.32805 5.30127L17.7149 3.0258C18.1378 2.95892 18.5348 3.24759 18.6015 3.67049L18.7835 4.82361C18.8373 5.16457 19.1573 5.39736 19.4983 5.34356C19.8392 5.28975 20.072 4.96974 20.0182 4.62878L19.8363 3.47566C19.6619 2.37067 18.6246 1.61639 17.5197 1.79115L3.13278 4.06661C2.02813 4.24133 1.27427 5.27845 1.44899 6.3831L1.99857 9.85793C2.17346 10.9637 3.21238 11.7177 4.31788 11.5413L11.5185 10.392C11.8594 10.3376 12.0916 10.0171 12.0372 9.67628C11.9828 9.33542 11.6624 9.1032 11.3215 9.15761L4.12086 10.3069ZM19.9004 11.6151L5.45305 13.9001C5.04392 13.9649 4.76471 14.349 4.82942 14.7581L5.38775 18.2882C5.45246 18.6974 5.83658 18.9766 6.24571 18.9119L20.6931 16.6268C21.1022 16.5621 21.3814 16.178 21.3167 15.7689L20.7583 12.2388C20.6936 11.8296 20.3095 11.5504 19.9004 11.6151ZM5.25777 12.6655C4.21806 12.8299 3.49299 13.7679 3.57645 14.8C3.17867 15.1511 2.9637 15.6918 3.05264 16.2541L3.57767 19.5737C3.75023 20.6647 4.77455 21.4093 5.86556 21.2367L19.9927 19.0023C20.7197 18.8873 21.2751 18.3524 21.4519 17.6846C22.2223 17.3097 22.6921 16.4638 22.5513 15.5736L21.993 12.0435C21.8204 10.9525 20.7961 10.2079 19.7051 10.3805L17.9019 10.6657L17.3957 7.46986L19.3483 8.96297C19.6773 9.21457 20.148 9.1518 20.3996 8.82276C20.6512 8.49373 20.5885 8.02302 20.2594 7.77141L16.2213 4.68355L13.1334 8.72172C12.8818 9.05076 12.9445 9.52146 13.2736 9.77307C13.6026 10.0247 14.0733 9.96191 14.3249 9.63287L15.8945 7.58034L16.4203 10.9L5.25777 12.6655ZM7.66514 15.3252C7.25609 15.3903 6.97729 15.7748 7.04245 16.1838C7.1076 16.5929 7.49202 16.8717 7.90108 16.8065L14.5586 15.7461C14.9677 15.681 15.2465 15.2966 15.1813 14.8875C15.1162 14.4785 14.7317 14.1997 14.3227 14.2648L7.66514 15.3252Z"
219 fill="currentColor"
220 />
221 </svg>
222 );
223};