a tool for shared writing and social publishing
0
fork

Configure Feed

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

implement undo/redo for title and description

+79 -18
+79 -18
components/Pages/PublicationMetadata.tsx
··· 1 1 import Link from "next/link"; 2 2 import { useLeafletPublicationData } from "components/PageSWRDataProvider"; 3 - import { AsyncValueInput, Input } from "components/Input"; 4 - import { useEffect, useState } from "react"; 5 - import { useDebouncedEffect } from "src/hooks/useDebouncedEffect"; 6 - import { updateLeafletDraftMetadata } from "actions/publications/updateLeafletDraftMetadata"; 3 + import { useRef } from "react"; 7 4 import { useReplicache } from "src/replicache"; 8 - import { 9 - AsyncValueAutosizeTextarea, 10 - AutosizeTextarea, 11 - } from "components/utils/AutosizeTextarea"; 5 + import { AsyncValueAutosizeTextarea } from "components/utils/AutosizeTextarea"; 12 6 import { Separator } from "components/Layout"; 13 7 import { AtUri } from "@atproto/syntax"; 14 8 import { PubLeafletDocument } from "lexicons/api"; ··· 30 24 let description = useSubscribe(rep, (tx) => 31 25 tx.get<string>("publication_description"), 32 26 ); 33 - let { permissions } = useEntitySetContext(); 34 - 35 27 let record = pub?.documents?.data as PubLeafletDocument.Record | null; 36 28 let publishedAt = record?.publishedAt; 37 29 ··· 58 50 Editor 59 51 </div> 60 52 </div> 61 - <AsyncValueAutosizeTextarea 62 - disabled={!permissions.write} 53 + <TextField 63 54 className="text-xl font-bold outline-hidden bg-transparent" 64 55 value={title} 65 - onChange={async (e) => { 56 + onChange={async (newTitle) => { 66 57 await rep?.mutate.updatePublicationDraft({ 67 - title: e.currentTarget.value, 58 + title: newTitle, 68 59 description, 69 60 }); 70 61 }} 71 62 placeholder="Untitled" 72 63 /> 73 - <AsyncValueAutosizeTextarea 74 - disabled={!permissions.write} 64 + <TextField 75 65 placeholder="add an optional description..." 76 66 className="italic text-secondary outline-hidden bg-transparent" 77 67 value={description} 78 - onChange={async (e) => { 68 + onChange={async (newDescription) => { 79 69 await rep?.mutate.updatePublicationDraft({ 80 - description: e.currentTarget.value, 81 70 title, 71 + description: newDescription, 82 72 }); 83 73 }} 84 74 /> ··· 100 90 <p className="text-sm text-tertiary pt-2">Draft</p> 101 91 )} 102 92 </div> 93 + ); 94 + }; 95 + 96 + export const TextField = ({ 97 + value, 98 + onChange, 99 + className, 100 + placeholder, 101 + }: { 102 + value: string; 103 + onChange: (v: string) => Promise<void>; 104 + className: string; 105 + placeholder: string; 106 + }) => { 107 + let { undoManager } = useReplicache(); 108 + let actionTimeout = useRef<number | null>(null); 109 + let { permissions } = useEntitySetContext(); 110 + let previousSelection = useRef<null | { start: number; end: number }>(null); 111 + let ref = useRef<HTMLTextAreaElement | null>(null); 112 + return ( 113 + <AsyncValueAutosizeTextarea 114 + ref={ref} 115 + disabled={!permissions.write} 116 + onSelect={(e) => { 117 + let start = e.currentTarget.selectionStart, 118 + end = e.currentTarget.selectionEnd; 119 + previousSelection.current = { start, end }; 120 + }} 121 + className={className} 122 + value={value} 123 + onBlur={async () => { 124 + if (actionTimeout.current) { 125 + undoManager.endGroup(); 126 + window.clearTimeout(actionTimeout.current); 127 + actionTimeout.current = null; 128 + } 129 + }} 130 + onChange={async (e) => { 131 + let newValue = e.currentTarget.value; 132 + let oldValue = value; 133 + let start = e.currentTarget.selectionStart, 134 + end = e.currentTarget.selectionEnd; 135 + await onChange(e.currentTarget.value); 136 + 137 + if (actionTimeout.current) { 138 + window.clearTimeout(actionTimeout.current); 139 + } else { 140 + undoManager.startGroup(); 141 + } 142 + 143 + actionTimeout.current = window.setTimeout(() => { 144 + undoManager.endGroup(); 145 + actionTimeout.current = null; 146 + }, 200); 147 + let previousStart = previousSelection.current?.start || null, 148 + previousEnd = previousSelection.current?.end || null; 149 + undoManager.add({ 150 + redo: async () => { 151 + await onChange(newValue); 152 + ref.current?.setSelectionRange(start, end); 153 + ref.current?.focus(); 154 + }, 155 + undo: async () => { 156 + await onChange(oldValue); 157 + ref.current?.setSelectionRange(previousStart, previousEnd); 158 + ref.current?.focus(); 159 + }, 160 + }); 161 + }} 162 + placeholder={placeholder} 163 + /> 103 164 ); 104 165 }; 105 166