Skip to content

Commit 2cd422b

Browse files
committed
feat(menu): add Maximise button next to Full screen
Adds a Maximise toggle in both the right-click menu and the pinned toolbar, alongside Full screen. Maximise expands the renderer to fill Logseq's window (covering sidebars and other blocks) without entering OS-level fullscreen. Implementation reparents the renderer to <body> with `position: fixed; inset: 0` so ancestor transforms / overflow / stacking contexts can't constrain the overlay. A comment-node placeholder marks the original slot for restoration. The DOM node itself isn't recreated, so inline editing, drag handlers and the pinned toolbar keep working. Esc exits maximise (one delegated listener per document). On re-mount after a structural edit, any orphaned maximised renderer for the same blockId is cleaned up. fitInlineTableWidth treats maximise like fullscreen so the table renders edge-to-edge.
1 parent 0c1111f commit 2cd422b

4 files changed

Lines changed: 120 additions & 3 deletions

File tree

src/index.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,24 @@ if (isInBrowser) {
260260
padding: 8px 12px !important; font-size: 14px;
261261
}
262262
.lsp-mdtable-renderer:fullscreen .lsp-mdtable-text { font-size: 14px; }
263+
/* Maximise: same look as fullscreen, but bounded by Logseq's window
264+
rather than the OS desktop. The renderer is reparented to <body>
265+
so ancestor transforms/overflow can't constrain this overlay. */
266+
.lsp-mdtable-renderer.lsp-mdt-max {
267+
position: fixed; inset: 0; z-index: 2147483645;
268+
margin: 0; padding: 24px 32px;
269+
background: var(--ls-primary-background-color, #fff);
270+
overflow: auto; box-sizing: border-box;
271+
}
272+
.lsp-mdtable-renderer.lsp-mdt-max .lsp-mdtable-scroll { overflow: auto; }
273+
.lsp-mdtable-renderer.lsp-mdt-max table.lsp-mdt {
274+
width: 100% !important; min-width: max-content;
275+
}
276+
.lsp-mdtable-renderer.lsp-mdt-max table.lsp-mdt th,
277+
.lsp-mdtable-renderer.lsp-mdt-max table.lsp-mdt td {
278+
padding: 8px 12px !important; font-size: 14px;
279+
}
280+
.lsp-mdtable-renderer.lsp-mdt-max .lsp-mdtable-text { font-size: 14px; }
263281
.lsp-mdt-menu {
264282
position: fixed; z-index: 2147483647; min-width: 168px;
265283
padding: 4px; border-radius: 6px;
@@ -410,7 +428,9 @@ if (isInBrowser) {
410428
pinToolbar: i18n.t('Pin toolbar'),
411429
unpinToolbar: i18n.t('Unpin toolbar'),
412430
fullScreen: i18n.t('Full screen'),
413-
exitFullScreen: i18n.t('Exit full screen')
431+
exitFullScreen: i18n.t('Exit full screen'),
432+
maximise: i18n.t('Maximise'),
433+
exitMaximise: i18n.t('Exit maximise')
414434
}
415435
}
416436
attachInlineEditing(el, inlineOpts)

src/locales/en/translation.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
"readable data": "readable data (align table source)",
1212
"Full screen": "Full screen",
1313
"Exit full screen": "Exit full screen",
14+
"Maximise": "Maximise",
15+
"Exit maximise": "Exit maximise",
1416
"Move row up": "Move row up",
1517
"Move row down": "Move row down",
1618
"Move column left": "Move column left",

src/locales/zh-CN/translation.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
"readable data": "可读化(对齐表格源码)",
1212
"Full screen": "全屏",
1313
"Exit full screen": "退出全屏",
14+
"Maximise": "最大化",
15+
"Exit maximise": "退出最大化",
1416
"Move row up": "上移行",
1517
"Move row down": "下移行",
1618
"Move column left": "左移列",

src/utils/inlineEditable.js

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
119120
export 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.
509600
const 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

Comments
 (0)