@@ -6,7 +6,14 @@ import { canvasIndex, LAYOUT_CHANGED } from "./canvasIndex";
66import { groundEditable , setGround , useGround } from "./canvasGround" ;
77import { ViewIcon } from "./CanvasTabBar" ;
88import { shareUrl , sheetPageUrl , type CanvasTab } from "./canvasUrl" ;
9- import { confirmTrash , openMenu , REVEAL , TRASH } from "./contextMenu" ;
9+ import {
10+ confirmTrash ,
11+ MenuItem ,
12+ MenuSeparator ,
13+ REVEAL ,
14+ RightClickMenu ,
15+ TRASH ,
16+ } from "./contextMenu" ;
1017import { DocModeSwitch } from "./DocTab" ;
1118import { FileText , LogoFigma , Plus } from "./geistIcons" ;
1219
@@ -39,8 +46,6 @@ export function CanvasStrip() {
3946 // Keyed by the page. The project's own view with no canvas has none, and the default ground.
4047 const page = activeTab . kind === "canvas" ? pageOf ( activeTab ) : undefined ;
4148 const ground = useGround ( editor , page ) ;
42- const [ target , setTarget ] = useState < CanvasTab | null > ( null ) ;
43- const menu = useRef < HTMLDivElement > ( null ) ;
4449 const [ renaming , setRenaming ] = useState ( ( ) => sessionStorage . getItem ( RENAME_KEY ) ) ;
4550 // A name typed lands as a layout change, which the tab reads through the index.
4651 const [ , relabel ] = useReducer ( ( n : number ) => n + 1 , 0 ) ;
@@ -62,7 +67,6 @@ export function CanvasStrip() {
6267 if ( editor && made . current ) openTab ( { kind : "canvas" , slug : made . current } ) ;
6368 if ( editor ) made . current = null ;
6469 } , [ editor , openTab ] ) ;
65- const own = target ?. kind === "canvas" && tab . kind !== "example" && canvasIndex ( ) . served ;
6670 const folder = async ( canvas : string , action : "reveal" | "delete" ) => {
6771 const response = await fetch ( `${ import . meta. env . BASE_URL } __sp/canvas-folder` , {
6872 method : "POST" ,
@@ -72,6 +76,45 @@ export function CanvasStrip() {
7276 if ( ! response . ok ) return alert ( await response . text ( ) ) ;
7377 if ( action === "delete" ) window . location . reload ( ) ;
7478 } ;
79+ // Right-click menu: Copy link copies the window's address for that view. A canvas of the
80+ // project's own also shows its folder, or bins it after asking.
81+ const menuFor = ( target : CanvasTab ) => (
82+ < >
83+ < MenuItem
84+ onSelect = { ( ) =>
85+ navigator . clipboard . writeText (
86+ shareUrl (
87+ new URL ( tabUrl ( { ...tab , view : target } ) , location . href ) . href ,
88+ canvasIndex ( ) . title ,
89+ ) ,
90+ )
91+ }
92+ >
93+ Copy link
94+ </ MenuItem >
95+ { target . kind === "canvas" && tab . kind !== "example" && canvasIndex ( ) . served && (
96+ < >
97+ < MenuItem onSelect = { ( ) => folder ( target . slug , "reveal" ) } > { REVEAL } </ MenuItem >
98+ < MenuSeparator />
99+ < MenuItem
100+ className = "sp-context-menu__danger"
101+ onSelect = { async ( ) => {
102+ if (
103+ await confirmTrash (
104+ shortName ( target . slug ) ,
105+ `${ canvasIndex ( ) . canvasesDir } /${ target . slug } ` ,
106+ "Its boards and everything pasted on it go with it." ,
107+ )
108+ )
109+ void folder ( target . slug , "delete" ) ;
110+ } }
111+ >
112+ { TRASH }
113+ </ MenuItem >
114+ </ >
115+ ) }
116+ </ >
117+ ) ;
75118 const rename = ( canvas : string , name : string ) => {
76119 sessionStorage . removeItem ( RENAME_KEY ) ;
77120 setRenaming ( null ) ;
@@ -86,18 +129,18 @@ export function CanvasStrip() {
86129 return (
87130 < nav className = "sp-canvas-tabs" aria-label = "Canvases" >
88131 { docsOf ( tab ) . map ( ( { name, slug } ) => (
89- < button
90- key = { slug }
91- type = "button"
92- className = "sp-canvas-tab"
93- aria-current = { activeTab . kind === "doc" && activeTab . slug === slug ? "page" : undefined }
94- title = { name }
95- onClick = { ( ) => openTab ( { kind : "doc" , slug } ) }
96- onContextMenu = { ( event ) => openMenu ( event , menu , ( ) => setTarget ( { kind : "doc" , slug } ) ) }
97- >
98- < FileText />
99- { name . replace ( / \. m d $ / i , "" ) }
100- </ button >
132+ < RightClickMenu key = { slug } menu = { menuFor ( { kind : "doc" , slug } ) } >
133+ < button
134+ type = "button"
135+ className = "sp-canvas-tab"
136+ aria-current = { activeTab . kind === "doc" && activeTab . slug === slug ? "page" : undefined }
137+ title = { name }
138+ onClick = { ( ) => openTab ( { kind : "doc" , slug } ) }
139+ >
140+ < FileText / >
141+ { name . replace ( / \. m d $ / i , "" ) }
142+ </ button >
143+ </ RightClickMenu >
101144 ) ) }
102145 { canvases . map ( ( canvas ) =>
103146 canvas === renaming ? (
@@ -121,23 +164,21 @@ export function CanvasStrip() {
121164 />
122165 </ label >
123166 ) : (
124- < button
125- key = { canvas }
126- type = "button"
127- className = "sp-canvas-tab"
128- aria-current = { canvas === here ? "page" : undefined }
129- data-working = { busy . includes ( canvas ) || undefined }
130- onClick = { ( ) => openTab ( { kind : "canvas" , slug : canvas } ) }
131- onDoubleClick = { ( ) =>
132- tab . kind !== "example" && canvasIndex ( ) . served && setRenaming ( canvas )
133- }
134- onContextMenu = { ( event ) =>
135- openMenu ( event , menu , ( ) => setTarget ( { kind : "canvas" , slug : canvas } ) )
136- }
137- >
138- < ViewIcon view = { { kind : "canvas" , slug : canvas } } />
139- { shortName ( canvas ) }
140- </ button >
167+ < RightClickMenu key = { canvas } menu = { menuFor ( { kind : "canvas" , slug : canvas } ) } >
168+ < button
169+ type = "button"
170+ className = "sp-canvas-tab"
171+ aria-current = { canvas === here ? "page" : undefined }
172+ data-working = { busy . includes ( canvas ) || undefined }
173+ onClick = { ( ) => openTab ( { kind : "canvas" , slug : canvas } ) }
174+ onDoubleClick = { ( ) =>
175+ tab . kind !== "example" && canvasIndex ( ) . served && setRenaming ( canvas )
176+ }
177+ >
178+ < ViewIcon view = { { kind : "canvas" , slug : canvas } } />
179+ { shortName ( canvas ) }
180+ </ button >
181+ </ RightClickMenu >
141182 ) ,
142183 ) }
143184 { tab . kind !== "example" && canvasIndex ( ) . served && (
@@ -186,61 +227,6 @@ export function CanvasStrip() {
186227 < LogoFigma />
187228 </ a >
188229 ) }
189- { /* Right-click menu: Copy link copies the window's address for that view. A canvas of the
190- project's own also shows its folder, or bins it after asking. */ }
191- < div
192- ref = { menu }
193- popover = "auto"
194- className = "sp-context-menu"
195- role = "menu"
196- onClickCapture = { ( event ) => event . currentTarget . hidePopover ( ) }
197- >
198- < button
199- type = "button"
200- role = "menuitem"
201- className = "sp-menu-row"
202- onClick = { ( ) =>
203- navigator . clipboard . writeText (
204- shareUrl (
205- new URL ( tabUrl ( { ...tab , view : target ! } ) , location . href ) . href ,
206- canvasIndex ( ) . title ,
207- ) ,
208- )
209- }
210- >
211- Copy link
212- </ button >
213- { own && (
214- < >
215- < button
216- type = "button"
217- role = "menuitem"
218- className = "sp-menu-row"
219- onClick = { ( ) => folder ( target . slug , "reveal" ) }
220- >
221- { REVEAL }
222- </ button >
223- < hr />
224- < button
225- type = "button"
226- role = "menuitem"
227- className = "sp-menu-row sp-context-menu__danger"
228- onClick = { async ( ) => {
229- if (
230- await confirmTrash (
231- shortName ( target . slug ) ,
232- `${ canvasIndex ( ) . canvasesDir } /${ target . slug } ` ,
233- "Its boards and everything pasted on it go with it." ,
234- )
235- )
236- void folder ( target . slug , "delete" ) ;
237- } }
238- >
239- { TRASH }
240- </ button >
241- </ >
242- ) }
243- </ div >
244230 </ nav >
245231 ) ;
246232}
0 commit comments