@@ -79,42 +79,37 @@ const patchEvents = computed(() =>
7979const formRef = useTemplateRef <HTMLElement >(' formRef' )
8080const commentTextAreaRef = useTemplateRef <HTMLElement >(' commentTextAreaRef' )
8181
82+ // Those `watchEffect()`s should run once each time the respective elements get created
8283watchEffect (() => {
83- const commentEl = commentTextAreaRef .value ?.shadowRoot ?.querySelector (' textarea' )
84- const els = [commentEl ].filter (Boolean )
85-
86- els .forEach ((el ) => {
84+ formRef .value &&
8785 useEventListener (
88- el ,
86+ formRef . value ,
8987 ' keydown' ,
9088 (ev ) => {
91- if (ev .key === ' Escape' ) {
92- patchCommentForm .value [selectedRevision .id ] = {
93- comment: patchCommentForm .value [selectedRevision .id ]?.comment || ' ' ,
94- status: ' off' ,
95- }
96- }
9789 if (ev .key === ' Enter' && (ev .ctrlKey || ev .metaKey )) {
9890 submitPatchCommentForm ()
91+ } else if (ev .key === ' Escape' ) {
92+ pausePatchCommenting ()
93+ } else if (ev .key === ' p' && ev .altKey ) {
94+ togglePreviewMarkdown ()
9995 }
10096 },
10197 { passive: true },
10298 )
99+ })
100+ watchEffect (() => {
101+ if (patchCommentForm .value [selectedRevision .id ]?.status === ' editing' ) {
102+ const el = commentTextAreaRef .value
103+ setTimeout (() => el ?.focus (), 0 ) // Vue.nextTick isn't cutting it
103104 useEventListener (el , ' focus' , alignViewportWithForm , { passive: true })
104105 useEventListener (el , ' input' , alignViewportWithForm , { passive: true })
105- })
106+ }
106107})
107108
108109function alignViewportWithForm() {
109110 formRef .value ?.scrollIntoView ({ block: ' nearest' , behavior: ' instant' })
110111}
111112
112- watchEffect (() => {
113- if (patchCommentForm .value [selectedRevision .id ]?.status === ' editing' ) {
114- setTimeout (() => commentTextAreaRef .value ?.focus (), 0 ) // Vue.nextTick isn't cutting it
115- }
116- })
117-
118113interface VscodeTextAreaEvent {
119114 target: { _value: string }
120115}
@@ -125,6 +120,13 @@ function updatePatchCommentFormComment(ev: VscodeTextAreaEvent) {
125120 }
126121}
127122
123+ function pausePatchCommenting() {
124+ patchCommentForm .value [selectedRevision .id ] = {
125+ comment: patchCommentForm .value [selectedRevision .id ]?.comment || ' ' ,
126+ status: ' off' ,
127+ }
128+ }
129+
128130function submitPatchCommentForm() {
129131 const commentIdsBefore = commentRefs .value ?.map ((commentRef ) => commentRef .$attrs .id )
130132
@@ -153,6 +155,22 @@ function discardPatchCommentForm() {
153155 delete patchCommentForm .value [selectedRevision .id ]
154156}
155157
158+ function togglePreviewMarkdown() {
159+ const selectedRevForm = patchCommentForm .value [selectedRevision .id ]
160+ if (selectedRevForm ?.status === ' editing' ) {
161+ selectedRevForm .status = ' previewing'
162+
163+ if (formRef .value ) {
164+ formRef .value .tabIndex = 0 // allows <form>, otherwise non-tabbable, to be `focus()`ed
165+ formRef .value .focus () // make form keyboard shortcuts still work after toggling MD preview
166+ formRef .value .tabIndex = - 1 // clean-up
167+ }
168+ } else if (selectedRevForm ?.status === ' previewing' ) {
169+ selectedRevForm .status = ' editing'
170+ setTimeout (() => commentTextAreaRef .value ?.focus (), 0 )
171+ }
172+ }
173+
156174// TODO: show "edited" indicators + timestamp (on hover) or full-blown list of edits, for each revision, comment, etc anything that has edits
157175 </script >
158176
@@ -162,17 +180,28 @@ function discardPatchCommentForm() {
162180 <h2 v-if =" showHeading" class =" text-lg font-normal mt-0 mb-4" >Activity</h2 >
163181 <EventList >
164182 <EventItem
165- v-if =" patchCommentForm[selectedRevision.id]?.status === 'editing'"
183+ v-if ="
184+ patchCommentForm[selectedRevision.id]?.status === 'editing' ||
185+ patchCommentForm[selectedRevision.id]?.status === 'previewing'
186+ "
166187 :when =" NaN"
167188 codicon =" codicon-comment"
168189 >
169190 <form
170191 @submit.prevent
171192 ref =" formRef"
172193 name =" Edit patch title and description"
173- class =" pb-2 flex flex-col gap-y-3"
194+ class =" font-mono text-sm leading-[unset] pb-2 flex flex-col gap-y-3 outline-none"
195+ :class =" { 'w-fit': patchCommentForm[selectedRevision.id]?.status !== 'previewing' }"
196+ style ="
197+ min-width : min (
198+ 100%,
199+ 68ch
200+ ); /* results in allowing 65 chars before resizing to be wider */
201+ "
174202 >
175203 <vscode-text-area
204+ v-if =" patchCommentForm[selectedRevision.id]?.status === 'editing'"
176205 ref =" commentTextAreaRef"
177206 :value =" patchCommentForm[selectedRevision.id]?.comment"
178207 @input =" updatePatchCommentFormComment"
@@ -183,25 +212,75 @@ function discardPatchCommentForm() {
183212 >
184213 New Patch Comment:
185214 </vscode-text-area >
215+
216+ <div
217+ v-if =" patchCommentForm[selectedRevision.id]?.status === 'previewing'"
218+ class =" p-1 border border-dashed border-[var(--vscode-focusBorder,var(--vscode-commandCenter-debuggingBackground))] max-w-fit flex flex-col gap-y-4 group"
219+ >
220+ <Markdown
221+ :source =" patchCommentForm[selectedRevision.id]?.comment || ''"
222+ class =" text-sm"
223+ />
224+ </div >
225+
186226 <div class =" reset-font opacity-[0.65]"
187227 >Target Revision: <pre class =" ml-1" >{{ shortenHash(selectedRevision.id) }}</pre >
188228 </div >
189229
190- <div class =" w-full flex flex-row-reverse justify-start gap-x-2" >
191- <vscode-button
192- appearance =" primary"
193- title =" Save New Comment to Radicle"
194- @click =" submitPatchCommentForm"
195- >
196- Comment
197- </vscode-button >
198- <vscode-button
199- appearance =" secondary"
200- title =" Stop Editing and Discard Current Changes"
201- @click =" discardPatchCommentForm"
202- >
203- Discard
204- </vscode-button >
230+ <div class =" w-full flex flex-row-reverse justify-between" >
231+ <div class =" flex flex-row-reverse justify-start gap-x-2" >
232+ <vscode-button
233+ @click =" submitPatchCommentForm"
234+ appearance =" primary"
235+ title =" Save New Comment to Radicle"
236+ >
237+ <!-- eslint-disable-next-line vue/no-deprecated-slot-attribute -->
238+ <span slot =" start" class =" codicon codicon-save" ></span >
239+ Comment
240+ </vscode-button >
241+ <vscode-button
242+ @click =" pausePatchCommenting"
243+ appearance =" secondary"
244+ title =" Pause Editing, Preserving Current Changes for Later (Escape)"
245+ >
246+ <!-- eslint-disable-next-line vue/no-deprecated-slot-attribute -->
247+ <span class =" codicon codicon-coffee" ></span >
248+ </vscode-button >
249+ <vscode-button
250+ @click =" discardPatchCommentForm"
251+ appearance =" secondary"
252+ title =" Stop Editing and Discard Current Changes"
253+ >
254+ <!-- eslint-disable-next-line vue/no-deprecated-slot-attribute -->
255+ <span slot =" start" class =" codicon codicon-discard" ></span >
256+ Discard
257+ </vscode-button >
258+ </div >
259+ <div class =" flex flex-row-reverse justify-start gap-x-2" >
260+ <vscode-button
261+ @click =" togglePreviewMarkdown"
262+ :appearance ="
263+ patchCommentForm[selectedRevision.id]?.status === 'previewing'
264+ ? 'primary'
265+ : 'secondary'
266+ "
267+ :title ="
268+ patchCommentForm[selectedRevision.id]?.status === 'previewing'
269+ ? 'Stop Previewing as Rendered Markdown and Return to Editing (Alt + P)'
270+ : 'Preview Changes as Rendered Markdown (Alt + P)'
271+ "
272+ class =" self-center"
273+ >
274+ <span
275+ :class =" [
276+ 'codicon',
277+ patchCommentForm[selectedRevision.id]?.status === 'previewing'
278+ ? 'codicon-edit'
279+ : 'codicon-markdown',
280+ ]"
281+ ></span >
282+ </vscode-button >
283+ </div >
205284 </div >
206285 </form >
207286 </EventItem >
@@ -370,11 +449,6 @@ function discardPatchCommentForm() {
370449</template >
371450
372451<style scoped>
373- form {
374- @apply w-fit font-mono text-sm leading- [unset ];
375- min-width : min (100% , 68ch ); /* results to allowing 65 chars before resizing to be wider */
376- }
377-
378452.reset-font {
379453 font-family : var (--vscode-font-family );
380454 font-size : var (--vscode-font-size );
0 commit comments