@@ -69,7 +69,8 @@ export const fitInlineTableWidth = (root) => {
6969 // the fullscreen viewport so the table can render edge-to-edge.
7070 const doc = root . ownerDocument
7171 const fsEl = doc . fullscreenElement || doc . webkitFullscreenElement
72- if ( fsEl && fsEl . contains ( root ) ) {
72+ const maximised = root . classList . contains ( 'lsp-mdt-max' )
73+ if ( ( fsEl && fsEl . contains ( root ) ) || maximised ) {
7374 scrolls . forEach ( s => {
7475 s . style . maxWidth = '100%'
7576 if ( s . scrollWidth > s . clientWidth + 1 ) {
@@ -118,6 +119,20 @@ const bindResizeRefit = (root) => {
118119// in-place editing is enabled — the overflow exists in read-only mode too.
119120export const prepareInlineRenderer = ( root ) => {
120121 if ( ! root ) return
122+ // Re-renders (e.g. after a structural op) create a fresh renderer DOM.
123+ // If a previous renderer for this block was maximised, its reparented
124+ // node is now orphaned under <body>. Remove it so orphans don't pile up.
125+ const blockId = root . getAttribute ( 'data-blockid' )
126+ const doc = root . ownerDocument
127+ if ( blockId ) {
128+ doc . querySelectorAll ( '.lsp-mdtable-renderer.lsp-mdt-max' ) . forEach ( stale => {
129+ if ( stale === root ) return
130+ if ( stale . getAttribute ( 'data-blockid' ) !== blockId ) return
131+ const ph = stale . __mdtMaxPlaceholder
132+ if ( ph && ph . parentNode ) ph . remove ( )
133+ stale . remove ( )
134+ } )
135+ }
121136 fitInlineTableWidth ( root )
122137 // The ref can fire before Logseq has placed the element / laid out its
123138 // ancestors; remeasure after the next frame once layout has settled.
@@ -421,7 +436,12 @@ const ICONS = {
421436 sortColDesc : SVG ( '<path d="M11 5h9M11 10h6M11 15h3"/><polyline points="4 16 7 19 10 16"/><line x1="7" y1="5" x2="7" y2="19"/>' ) ,
422437 pin : SVG ( '<line x1="12" y1="17" x2="12" y2="22"/><path d="M9 3h6l-1 6 3 3v2H7v-2l3-3-1-6z"/>' ) ,
423438 fullscreen : SVG ( '<polyline points="4 9 4 4 9 4"/><polyline points="20 9 20 4 15 4"/><polyline points="4 15 4 20 9 20"/><polyline points="20 15 20 20 15 20"/>' ) ,
424- exitFullscreen : SVG ( '<polyline points="9 4 9 9 4 9"/><polyline points="15 4 15 9 20 9"/><polyline points="4 15 9 15 9 20"/><polyline points="20 15 15 15 15 20"/>' )
439+ exitFullscreen : SVG ( '<polyline points="9 4 9 9 4 9"/><polyline points="15 4 15 9 20 9"/><polyline points="4 15 9 15 9 20"/><polyline points="20 15 15 15 15 20"/>' ) ,
440+ // Maximise = expand to Logseq's window bounds (covers sidebars/blocks).
441+ // Distinct glyph from fullscreen so the two are distinguishable in the bar:
442+ // a framed box with inward arrows.
443+ maximise : SVG ( '<rect x="3" y="3" width="18" height="18" rx="1"/><polyline points="8 13 8 16 11 16"/><polyline points="16 11 16 8 13 8"/>' ) ,
444+ exitMaximise : SVG ( '<rect x="3" y="3" width="18" height="18" rx="1"/><polyline points="13 8 16 8 16 11"/><polyline points="11 16 8 16 8 13"/>' )
425445}
426446
427447// Overlay host for popovers/toolbars: when the renderer is in native
@@ -505,6 +525,77 @@ const fullScreenItem = (root, opts, after) => {
505525 }
506526}
507527
528+ // Maximise = expand the renderer to fill Logseq's window (covering sidebars
529+ // and other blocks), without entering OS-level fullscreen. Implementation:
530+ // reparent the renderer to <body> so ancestor transforms / overflow / stacking
531+ // contexts can't constrain a `position: fixed; inset: 0` overlay. A comment
532+ // placeholder marks the original slot so we can restore it on exit. The DOM
533+ // node itself isn't recreated, so editing hooks, drag handlers and the
534+ // pinned toolbar all keep working.
535+ const isMaximised = ( root ) => root . classList . contains ( 'lsp-mdt-max' )
536+
537+ const maximiseRenderer = ( root ) => {
538+ if ( isMaximised ( root ) ) return
539+ const doc = root . ownerDocument
540+ const parent = root . parentNode
541+ if ( ! parent || parent === doc . body ) return
542+ const placeholder = doc . createComment ( 'lsp-mdt-max-placeholder' )
543+ parent . insertBefore ( placeholder , root )
544+ root . __mdtMaxPlaceholder = placeholder
545+ doc . body . appendChild ( root )
546+ root . classList . add ( 'lsp-mdt-max' )
547+ bindMaximiseEsc ( doc )
548+ // Refit so the table can use the full window width.
549+ try { fitInlineTableWidth ( root ) } catch ( e ) { /* noop */ }
550+ }
551+
552+ const unmaximiseRenderer = ( root ) => {
553+ if ( ! isMaximised ( root ) ) return
554+ root . classList . remove ( 'lsp-mdt-max' )
555+ const ph = root . __mdtMaxPlaceholder
556+ if ( ph && ph . parentNode ) {
557+ ph . parentNode . insertBefore ( root , ph )
558+ ph . remove ( )
559+ }
560+ root . __mdtMaxPlaceholder = null
561+ try { fitInlineTableWidth ( root ) } catch ( e ) { /* noop */ }
562+ }
563+
564+ // One Esc listener per document exits whichever renderer is currently
565+ // maximised. Cheap to leave installed for the life of the document.
566+ const maximiseEscDocs = new WeakSet ( )
567+ const bindMaximiseEsc = ( doc ) => {
568+ if ( maximiseEscDocs . has ( doc ) ) return
569+ maximiseEscDocs . add ( doc )
570+ doc . addEventListener ( 'keydown' , ( e ) => {
571+ if ( e . key !== 'Escape' ) return
572+ const max = doc . querySelector ( '.lsp-mdtable-renderer.lsp-mdt-max' )
573+ if ( ! max ) return
574+ // Don't fight an open menu / popover — let its own Esc handler run first.
575+ if ( doc . querySelector ( '.lsp-mdt-menu' ) ) return
576+ e . preventDefault ( )
577+ e . stopPropagation ( )
578+ unmaximiseRenderer ( max )
579+ removeToolbar ( doc )
580+ } , true )
581+ }
582+
583+ // Maximise toggle item; mirrors fullScreenItem's shape.
584+ const maximiseItem = ( root , opts , after ) => {
585+ const max = isMaximised ( root )
586+ const L = opts . menuLabels || { }
587+ return {
588+ icon : max ? ICONS . exitMaximise : ICONS . maximise ,
589+ label : max ? ( L . exitMaximise || 'Exit maximise' ) : ( L . maximise || 'Maximise' ) ,
590+ enabled : true ,
591+ action : ( ) => {
592+ if ( max ) unmaximiseRenderer ( root )
593+ else maximiseRenderer ( root )
594+ if ( after ) after ( ! max )
595+ }
596+ }
597+ }
598+
508599// Pin/Unpin toggle item; `after(nowPinned)` lets the surface refresh.
509600const pinItem = ( opts , after ) => {
510601 const L = opts . menuLabels || { }
@@ -601,6 +692,7 @@ const buildToolbar = (root, opts, cell) => {
601692 const aCol = Array . from ( aTr . querySelectorAll ( 'th,td' ) ) . indexOf ( cell )
602693 const all = items . concat ( [ { sep : true } ,
603694 fullScreenItem ( root , opts , ( ) => buildToolbar ( root , opts , cell ) ) ,
695+ maximiseItem ( root , opts , ( ) => buildToolbar ( root , opts , cell ) ) ,
604696 pinItem ( opts , ( ) => removeToolbar ( doc ) ) ] ) // pinned bar's toggle = unpin
605697
606698 const bar = doc . createElement ( 'div' )
@@ -637,6 +729,7 @@ const openContextMenu = (root, opts, cell, ev) => {
637729 const { items, ord } = buildItems ( root , opts , cell )
638730 const all = items . concat ( [ { sep : true } ,
639731 fullScreenItem ( root , opts ) ,
732+ maximiseItem ( root , opts ) ,
640733 pinItem ( opts , ( now ) => { if ( now ) buildToolbar ( root , opts , cell ) ; else removeToolbar ( doc ) } ) ] )
641734
642735 const menu = doc . createElement ( 'div' )
0 commit comments