diff --git a/chrome/content/zotero-platform/mac/overlay.css b/chrome/content/zotero-platform/mac/overlay.css index 7a8aa294db..1dc1f9fc1a 100644 --- a/chrome/content/zotero-platform/mac/overlay.css +++ b/chrome/content/zotero-platform/mac/overlay.css @@ -255,13 +255,6 @@ treechildren::-moz-tree-image { list-style-image: url('chrome://zotero/skin/mac/toolbar-note-add.png'); } -/* Hide icons on macOS. We use :is() to work around weird behavior in Fx101 where a regular child - selector doesn't match the first time the menu is opened. */ -:is(#zotero-collectionmenu, #zotero-itemmenu) > :is(.menuitem-iconic, .menu-iconic) { - list-style-image: none !important; -} - - /* BEGIN 2X BLOCK -- DO NOT EDIT MANUALLY -- USE 2XIZE */ @media (min-resolution: 1.25dppx) { .zotero-tb-button,.zotero-tb-button:first-child,.zotero-tb-button:last-child { background: url("chrome://zotero/skin/mac/menubutton-end@2x.png") right center/auto 24px no-repeat; } diff --git a/chrome/content/zotero/elements/collapsibleSection.js b/chrome/content/zotero/elements/collapsibleSection.js index 2a1da837cb..6b29801c01 100644 --- a/chrome/content/zotero/elements/collapsibleSection.js +++ b/chrome/content/zotero/elements/collapsibleSection.js @@ -47,9 +47,10 @@ let open = this.open; if (open === val || this.empty) return; this.render(); - let openHeight = this._head?.nextSibling?.scrollHeight; - if (openHeight) { - this.style.setProperty('--open-height', `${openHeight}px`); + if (this._head?.nextSibling + && this._head.nextSibling.getBoundingClientRect().height + && this._head.nextSibling.scrollHeight) { + this.style.setProperty('--open-height', `${this._head.nextSibling.scrollHeight}px`); } else { this.style.setProperty('--open-height', 'auto'); @@ -80,7 +81,9 @@ setCount(count) { this.setAttribute('data-l10n-args', JSON.stringify({ count })); - this.empty = !count; + this._runWithTransitionsDisabled(() => { + this.empty = !count; + }); } get label() { @@ -119,6 +122,7 @@ this._head.className = 'head'; this._head.addEventListener('click', this._handleClick); this._head.addEventListener('keydown', this._handleKeyDown); + this._head.addEventListener('contextmenu', this._handleContextMenu); this._title = document.createElement('span'); this._title.className = 'title'; @@ -131,13 +135,21 @@ }); this._head.append(this._addButton); + this._contextMenu = this._buildContextMenu(); + let popupset = document.createXULElement('popupset'); + popupset.append(this._contextMenu); + this._head.append(popupset); + let twisty = document.createXULElement('toolbarbutton'); twisty.className = 'twisty'; this._head.append(twisty); this.prepend(this._head); - this._restoreOpenState(); - this.render(); + + this._runWithTransitionsDisabled(() => { + this._restoreOpenState(); + this.render(); + }); this._notifierID = Zotero.Prefs.registerObserver(`panes.${this.dataset.pane}.open`, this._restoreOpenState.bind(this)); @@ -146,9 +158,79 @@ } } + _buildContextMenu() { + let containerRoot = this.closest('.zotero-view-item-container'); + + let contextMenu = document.createXULElement('menupopup'); + let collapseOtherSections = document.createXULElement('menuitem'); + collapseOtherSections.classList.add('menuitem-iconic', 'zotero-menuitem-collapse-others'); + collapseOtherSections.setAttribute('data-l10n-id', 'collapse-other-sections'); + collapseOtherSections.addEventListener('command', () => { + for (let section of containerRoot.querySelectorAll('collapsible-section')) { + if (section !== this) { + section.open = false; + } + } + }); + contextMenu.append(collapseOtherSections); + + let expandAllSections = document.createXULElement('menuitem'); + expandAllSections.classList.add('menuitem-iconic', 'zotero-menuitem-expand-all'); + expandAllSections.setAttribute('data-l10n-id', 'expand-all-sections'); + expandAllSections.addEventListener('command', () => { + for (let section of containerRoot.querySelectorAll('collapsible-section')) { + section.open = true; + } + }); + contextMenu.append(expandAllSections); + + let pinSection, unpinSection; + let pinUnpinSeparator = document.createXULElement('menuseparator'); + contextMenu.append(pinUnpinSeparator); + + pinSection = document.createXULElement('menuitem'); + pinSection.classList.add('menuitem-iconic', 'zotero-menuitem-pin'); + pinSection.setAttribute('data-l10n-id', 'pin-section'); + pinSection.addEventListener('command', () => { + let sidenav = this._getSidenav(); + sidenav.scrollToPane(this.dataset.pane, 'smooth'); + sidenav.pinnedPane = this.dataset.pane; + }); + contextMenu.append(pinSection); + + unpinSection = document.createXULElement('menuitem'); + unpinSection.classList.add('menuitem-iconic', 'zotero-menuitem-unpin'); + unpinSection.setAttribute('data-l10n-id', 'unpin-section'); + unpinSection.addEventListener('command', () => { + this._getSidenav().pinnedPane = null; + }); + contextMenu.append(unpinSection); + + contextMenu.addEventListener('popupshowing', () => { + let sections = Array.from(containerRoot.querySelectorAll('collapsible-section')); + collapseOtherSections.disabled = sections.every(section => section === this || !section.open); + expandAllSections.disabled = sections.every(section => section.open); + + let sidenav = this._getSidenav(); + if (sidenav?.isPanePinnable(this.dataset.pane)) { + pinUnpinSeparator.hidden = false; + pinSection.hidden = sidenav.pinnedPane == this.dataset.pane; + unpinSection.hidden = sidenav.pinnedPane != this.dataset.pane; + } + else { + pinUnpinSeparator.hidden = true; + pinSection.hidden = true; + unpinSection.hidden = true; + } + }); + + return contextMenu; + } + destroy() { this._head.removeEventListener('click', this._handleClick); this._head.removeEventListener('keydown', this._handleKeyDown); + this._head.removeEventListener('contextmenu', this._handleContextMenu); Zotero.Prefs.unregisterObserver(this._notifierID); } @@ -160,6 +242,15 @@ _restoreOpenState() { this.open = Zotero.Prefs.get(`panes.${this.dataset.pane}.open`) ?? true; } + + _runWithTransitionsDisabled(fn) { + this.classList.add('disable-transitions'); + fn(); + // Need to wait a tick before re-enabling - forcing style recalculation isn't enough here + requestAnimationFrame(() => { + this.classList.remove('disable-transitions'); + }); + } _handleClick = (event) => { if (event.target.closest('.add')) return; @@ -174,6 +265,16 @@ } }; + _handleContextMenu = (event) => { + if (event.target.closest('.add')) return; + event.preventDefault(); + this._contextMenu.openPopupAtScreen(event.screenX, event.screenY, true); + }; + + _getSidenav() { + return this.closest('.zotero-view-item-container')?.querySelector('item-pane-sidenav'); + } + render() { if (!this.initialized) return; diff --git a/chrome/content/zotero/elements/itemPaneSidenav.js b/chrome/content/zotero/elements/itemPaneSidenav.js index 10b3950c1e..3b76e3193e 100644 --- a/chrome/content/zotero/elements/itemPaneSidenav.js +++ b/chrome/content/zotero/elements/itemPaneSidenav.js @@ -28,31 +28,50 @@ { class ItemPaneSidenav extends XULElementBase { content = MozXULElement.parseXULToFragment(` - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + `, ['chrome://zotero/locale/zotero.dtd']); _container = null; - _currentSmoothScrollTarget = null; - - _smoothScrollTargetTimeout = null; + _contextMenuTarget = null; + + _preserveMinScrollHeightTimeout = null; get container() { return this._container; @@ -60,26 +79,33 @@ set container(val) { if (this._container == val) return; - - this._container?.removeEventListener('scroll', this._updateSelectedPane.bind(this), { passive: true }); + this._container?.removeEventListener('scroll', this._handleContainerScroll); this._container = val; - this._container.addEventListener('scroll', this._updateSelectedPane.bind(this), { passive: true }); - - this._updateSelectedPane(); + this._container.addEventListener('scroll', this._handleContainerScroll); this.render(); } - get selectedPane() { - return this.getAttribute('selectedPane'); + get pinnedPane() { + return this.getAttribute('pinnedPane'); } - set selectedPane(val) { - this.setAttribute('selectedPane', val); - this.render(); + set pinnedPane(val) { + this.setAttribute('pinnedPane', val || ''); + if (val) { + this._pinnedPaneMinScrollHeight = this._getMinScrollHeightForPane(this.getPane(val)); + } + } + + get _minScrollHeight() { + return parseFloat(this._container.style.getPropertyValue('--min-scroll-height') || 0); + } + + set _minScrollHeight(val) { + this._container.style.setProperty('--min-scroll-height', val + 'px'); } static get observedAttributes() { - return ['selectedPane']; + return ['pinnedPane']; } attributeChangedCallback() { @@ -87,75 +113,134 @@ } scrollToPane(id, behavior = 'smooth') { - let pane = this._getPane(id); - if (pane) { - this._currentSmoothScrollTarget = id; - pane.scrollIntoView({ block: 'start', behavior }); - - if (this._smoothScrollTargetTimeout) { - clearTimeout(this._smoothScrollTargetTimeout); + let pane = this.getPane(id); + if (!pane) return; + + // The pane should always be at the very top + // If there isn't enough stuff below it for it to be at the top, we add padding + // We use a ::before pseudo-element for this so that we don't need to add another level to the DOM + this._makeSpaceForPane(pane); + if (behavior == 'smooth' && pane.getBoundingClientRect().top > this._container.getBoundingClientRect().top) { + if (this._preserveMinScrollHeightTimeout) { + clearTimeout(this._preserveMinScrollHeightTimeout); } - this._smoothScrollTargetTimeout = setTimeout(() => { - this._currentSmoothScrollTarget = null; + this._preserveMinScrollHeightTimeout = setTimeout(() => { + this._preserveMinScrollHeightTimeout = null; + this._handleContainerScroll(); + }, 1000); + } + pane.scrollIntoView({ block: 'start', behavior }); + pane.focus(); + } + + _flashPane(id) { + let paneSection = this.getPane(id)?.querySelector('collapsible-section'); + if (paneSection) { + paneSection.classList.add('highlight'); + setTimeout(() => { + paneSection.classList.remove('highlight'); }, 1000); } } - _updateSelectedPane() { - let topPane = null; - let containerBoundingRect = this.container.getBoundingClientRect(); - // If not initialized with content, just select the first pane - if (containerBoundingRect.height == 0) { - this.selectedPane = 'info'; - return; + _makeSpaceForPane(pane) { + let oldMinScrollHeight = this._minScrollHeight; + let newMinScrollHeight = this._getMinScrollHeightForPane(pane); + if (newMinScrollHeight > oldMinScrollHeight) { + this._minScrollHeight = newMinScrollHeight; } - for (let box of this._getPanes()) { - // Allow a little padding to deal with floating-point imprecision - if (box.getBoundingClientRect().top > containerBoundingRect.top + 5) { - break; - } - topPane = box.dataset.pane; - } - if (this._currentSmoothScrollTarget) { - if (this._currentSmoothScrollTarget == topPane) { - this._currentSmoothScrollTarget = null; - } - else { - return; - } - } - this.selectedPane = topPane || 'info'; - } - - _getPanes() { - return Array.from(this.container.querySelectorAll('[data-pane]')); } - _getPane(id) { - return this.container.querySelector(':scope > [data-pane="' + id + '"]'); + _getMinScrollHeightForPane(pane) { + let paneRect = pane.getBoundingClientRect(); + let containerRect = this._container.getBoundingClientRect(); + // No offsetTop property for XUL elements + let offsetTop = paneRect.top - containerRect.top + this._container.scrollTop; + return offsetTop + containerRect.height; + } + + _handleContainerScroll = () => { + if (this._preserveMinScrollHeightTimeout) return; + let minHeight = this._minScrollHeight; + if (minHeight) { + let newMinScrollHeight = this._container.scrollTop + this._container.clientHeight; + if (this.pinnedPane && newMinScrollHeight < this._pinnedPaneMinScrollHeight) { + return; + } + this._minScrollHeight = newMinScrollHeight; + } + }; + + getPanes() { + return Array.from(this.container.querySelectorAll(':scope > [data-pane]')); + } + + getPane(id) { + return this.container.querySelector(`:scope > [data-pane="${CSS.escape(id)}"]`); + } + + isPanePinnable(id) { + return id !== 'info'; } init() { if (!this.container) { this.container = document.getElementById('zotero-view-item'); } - - for (let toolbarbutton of this.children) { - toolbarbutton.addEventListener('command', () => { - this.selectedPane = toolbarbutton.dataset.pane; - this.scrollToPane(toolbarbutton.dataset.pane); + + for (let toolbarbutton of this.querySelectorAll('toolbarbutton')) { + let pane = toolbarbutton.dataset.pane; + let pinnable = this.isPanePinnable(pane); + toolbarbutton.parentElement.classList.toggle('pinnable', pinnable); + + toolbarbutton.addEventListener('click', (event) => { + switch (event.detail) { + case 1: + this.scrollToPane(pane, 'smooth'); + this._flashPane(pane); + break; + case 2: + if (this.pinnedPane == pane || !pinnable) { + this.pinnedPane = null; + } + else { + this.pinnedPane = pane; + } + break; + } }); + + if (pinnable) { + toolbarbutton.addEventListener('contextmenu', (event) => { + this._contextMenuTarget = pane; + this.querySelector('.zotero-menuitem-pin').hidden = this.pinnedPane == pane; + this.querySelector('.zotero-menuitem-unpin').hidden = this.pinnedPane != pane; + this.querySelector('.context-menu') + .openPopupAtScreen(event.screenX, event.screenY, true); + }); + } } + + this.querySelector('.zotero-menuitem-pin').addEventListener('command', () => { + this.scrollToPane(this._contextMenuTarget, 'smooth'); + this.pinnedPane = this._contextMenuTarget; + }); + this.querySelector('.zotero-menuitem-unpin').addEventListener('command', () => { + this.pinnedPane = null; + }); + this.render(); } render() { - let currentPane = this.selectedPane; - for (let toolbarbutton of this.children) { + let pinnedPane = this.pinnedPane; + for (let toolbarbutton of this.querySelectorAll('toolbarbutton')) { let pane = toolbarbutton.dataset.pane; - toolbarbutton.hidden = !this._getPane(pane); - toolbarbutton.toggleAttribute('selected', pane == currentPane); - toolbarbutton.setAttribute('aria-selected', pane == currentPane); + toolbarbutton.setAttribute('aria-selected', pane == pinnedPane); + toolbarbutton.parentElement.hidden = !this.getPane(pane); + + // Set .pinned on the container, for pin styling + toolbarbutton.parentElement.classList.toggle('pinned', pane == pinnedPane); } } } diff --git a/chrome/content/zotero/elements/tagsBox.js b/chrome/content/zotero/elements/tagsBox.js index 4d2c5a223d..0b3ff4dfc9 100644 --- a/chrome/content/zotero/elements/tagsBox.js +++ b/chrome/content/zotero/elements/tagsBox.js @@ -84,7 +84,7 @@ let removeAllItemTags = this._id('remove-all-item-tags'); this._id('remove-all-item-tags').addEventListener('command', this.removeAll); - this.addEventListener('contextmenu', (event) => { + this.querySelector('.body').addEventListener('contextmenu', (event) => { removeAllItemTags.disabled = !this.count; this._id('tags-context-menu').openPopupAtScreen(event.screenX, event.screenY, true); }); diff --git a/chrome/content/zotero/itemPane.js b/chrome/content/zotero/itemPane.js index 217aab0d8b..4d0b2f2e0a 100644 --- a/chrome/content/zotero/itemPane.js +++ b/chrome/content/zotero/itemPane.js @@ -62,12 +62,8 @@ var ZoteroItemPane = new function() { /* * Load a top-level item */ - this.viewItem = Zotero.Promise.coroutine(function* (item, mode, pane) { - if (!pane) { - pane = 'info'; - } - - Zotero.debug('Viewing item in pane ' + pane); + this.viewItem = Zotero.Promise.coroutine(function* (item, mode, pinnedPane) { + Zotero.debug('Viewing item'); _notesBox.parentItem = item; @@ -108,14 +104,20 @@ var ZoteroItemPane = new function() { box.inTrash = inTrash; } - _sidenav.selectedPane = pane; - _sidenav.scrollToPane(pane, 'instant'); + _scrollParent.style.paddingBottom = ''; + if (pinnedPane) { + _sidenav.scrollToPane(pinnedPane, 'instant'); + _sidenav.pinnedPane = pinnedPane; + } + else if (pinnedPane !== false) { + _sidenav.scrollToPane('info', 'instant'); + } }); this.notify = Zotero.Promise.coroutine(function* (action, type, ids, extraData) { if (action == 'refresh' && _lastItem) { - yield this.viewItem(_lastItem, null, _sidenav.selectedPane); + yield this.viewItem(_lastItem, null, false); } }); @@ -250,8 +252,8 @@ var ZoteroItemPane = new function() { }; - this.getSidenavSelectedPane = function () { - return _sidenav.selectedPane; + this.getPinnedPane = function () { + return _sidenav.pinnedPane; }; diff --git a/chrome/content/zotero/zoteroPane.js b/chrome/content/zotero/zoteroPane.js index 05ba347db1..97b5dc7c0e 100644 --- a/chrome/content/zotero/zoteroPane.js +++ b/chrome/content/zotero/zoteroPane.js @@ -1818,14 +1818,9 @@ var ZoteroPane = new function() var isCommons = collectionTreeRow.isBucket(); let deck = document.getElementById('zotero-item-pane-content'); - let pane; - if (deck.selectedIndex == 1) { - pane = ZoteroItemPane.getSidenavSelectedPane(); - } - else { - deck.selectedIndex = 1; - pane = ZoteroItemPane.getSidenavSelectedPane(); - } + deck.selectedIndex = 1; + + let pane = ZoteroItemPane.getPinnedPane(); var button = document.getElementById('zotero-item-show-original'); if (isCommons) { diff --git a/chrome/locale/en-US/zotero/zotero.ftl b/chrome/locale/en-US/zotero/zotero.ftl index 3124d1af0c..37de54deda 100644 --- a/chrome/locale/en-US/zotero/zotero.ftl +++ b/chrome/locale/en-US/zotero/zotero.ftl @@ -267,6 +267,16 @@ sidenav-tags = sidenav-related = .tooltiptext = { pane-related } +pin-section = + .label = Pin Section +unpin-section = + .label = Unpin Section + +collapse-other-sections = + .label = Collapse Other Sections +expand-all-sections = + .label = Expand All Sections + abstract-field = .label = Add abstract… diff --git a/chrome/skin/default/zotero/16/universal/collapse-others.svg b/chrome/skin/default/zotero/16/universal/collapse-others.svg new file mode 100644 index 0000000000..829091a133 --- /dev/null +++ b/chrome/skin/default/zotero/16/universal/collapse-others.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/chrome/skin/default/zotero/16/universal/expand-all.svg b/chrome/skin/default/zotero/16/universal/expand-all.svg new file mode 100644 index 0000000000..f3072b3969 --- /dev/null +++ b/chrome/skin/default/zotero/16/universal/expand-all.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/chrome/skin/default/zotero/16/universal/pin-remove.svg b/chrome/skin/default/zotero/16/universal/pin-remove.svg new file mode 100644 index 0000000000..42052788d7 --- /dev/null +++ b/chrome/skin/default/zotero/16/universal/pin-remove.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/chrome/skin/default/zotero/16/universal/pin.svg b/chrome/skin/default/zotero/16/universal/pin.svg new file mode 100644 index 0000000000..6e8bad4834 --- /dev/null +++ b/chrome/skin/default/zotero/16/universal/pin.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/chrome/skin/default/zotero/8/universal/pin-remove.svg b/chrome/skin/default/zotero/8/universal/pin-remove.svg new file mode 100644 index 0000000000..f223852f6b --- /dev/null +++ b/chrome/skin/default/zotero/8/universal/pin-remove.svg @@ -0,0 +1,3 @@ + + + diff --git a/chrome/skin/default/zotero/8/universal/pin.svg b/chrome/skin/default/zotero/8/universal/pin.svg new file mode 100644 index 0000000000..ef08d39da6 --- /dev/null +++ b/chrome/skin/default/zotero/8/universal/pin.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/chrome/skin/default/zotero/itemPane.css b/chrome/skin/default/zotero/itemPane.css index 58b1d8b107..3deb0f8285 100644 --- a/chrome/skin/default/zotero/itemPane.css +++ b/chrome/skin/default/zotero/itemPane.css @@ -15,6 +15,7 @@ .zotero-view-item-container { display: flex; flex-direction: row; + height: 0; /* Prevent overflow, somehow */ } .zotero-view-item-main { @@ -24,10 +25,22 @@ } .zotero-view-item { + position: relative; flex: 1; overflow-y: auto; padding: 0 8px; overflow-anchor: none; /* Work around tags box causing scroll to jump - figure this out */ + scrollbar-color: var(--color-scrollbar) var(--color-scrollbar-background); +} + +.zotero-view-item::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: var(--min-scroll-height); + pointer-events: none; } .zotero-view-item-container.feed-item .zotero-view-item > :is(attachments-box, notes-box, tags-box, related-box), @@ -40,10 +53,6 @@ width: 100%; } -.zotero-view-item > :last-child { - min-height: 100%; -} - .zotero-view-item > vbox { margin-left: 5px; diff --git a/scss/abstracts/_mixins.scss b/scss/abstracts/_mixins.scss index 622b8987d1..7de825cc24 100644 --- a/scss/abstracts/_mixins.scss +++ b/scss/abstracts/_mixins.scss @@ -108,3 +108,22 @@ } } } + + +/* Hide icons on macOS. We use :is() to work around weird behavior in Fx101 where a regular child + selector doesn't match the first time the menu is opened. */ +@mixin macOS-hide-menu-icons { + $selector: &; + @at-root { + @media (-moz-platform: macos) { + // Yes, every single one of these :is-es is necessary! + :is(:is(#{$selector}) .menuitem-iconic, :is(#{$selector}) .menu-iconic) { + list-style-image: none !important; + + .menu-iconic-left { + display: none !important; + } + } + } + } +} diff --git a/scss/components/_menu.scss b/scss/components/_menu.scss index 9ee14361d7..daa401cc95 100644 --- a/scss/components/_menu.scss +++ b/scss/components/_menu.scss @@ -57,7 +57,10 @@ $menu-icons: ( add-to-collection: "new-collection", new-tab: "new-tab", new-window: "new-window", - + pin: "pin", + unpin: "pin-remove", + expand-all: "expand-all", + collapse-others: "collapse-others", ); @each $cls, $icon in $menu-icons { @@ -73,3 +76,7 @@ $menu-icons: ( } } }; + +#zotero-collectionmenu, #zotero-itemmenu { + @include macOS-hide-menu-icons; +} diff --git a/scss/elements/_collapsibleSection.scss b/scss/elements/_collapsibleSection.scss index 64b907d22e..f3ddd53a6d 100644 --- a/scss/elements/_collapsibleSection.scss +++ b/scss/elements/_collapsibleSection.scss @@ -24,6 +24,7 @@ collapsible-section { gap: 4px; color: var(--fill-secondary); font-weight: 590; + transition: color 0.2s ease-out; &::before { content: ''; @@ -59,6 +60,14 @@ collapsible-section { transform-origin: center; transition: transform 0.2s ease-in-out; } + + popupset > menupopup { + @include macOS-hide-menu-icons; + } + } + + &.highlight > .head > .title { + color: var(--fill-primary); } &[open]:not([empty]) > .head { @@ -97,4 +106,9 @@ collapsible-section { transition: max-height 0.2s ease-in-out, opacity 0.2s ease-in-out, visibility 0s 0.2s, overflow-y 0s 0.2s; } } + + &.disable-transitions * { + transition: none !important; + color: red; + } } diff --git a/scss/elements/_itemPaneSidenav.scss b/scss/elements/_itemPaneSidenav.scss index fbf2414856..59506650b9 100644 --- a/scss/elements/_itemPaneSidenav.scss +++ b/scss/elements/_itemPaneSidenav.scss @@ -6,24 +6,53 @@ item-pane-sidenav { gap: 6px; border-left: var(--material-panedivider); + .toolbarbutton-container { + position: relative; + // Disable pointer events here, re-enable on the button, so that :hover styles are only applied + // when the button itself is hovered + pointer-events: none; + + &::after { + content: ''; + position: absolute; + top: 1px; + inset-inline-end: 1px; + width: 12px; + height: 12px; + border-radius: 50%; + } + + &.pinnable { + &.pinned::after { + background-color: var(--accent-blue); + background-image: icon-url("8/universal/pin.svg"); + background-position: center; + background-repeat: no-repeat; + } + } + } + toolbarbutton { // TODO: Extract button styles? width: 28px; height: 28px; + margin: 0; padding: 4px; flex-shrink: 0; border-radius: 5px; -moz-context-properties: fill, fill-opacity, stroke, stroke-opacity; + pointer-events: all; + &:hover { background-color: var(--fill-quinary); } - - &:active, &[selected] { + + &:active { background-color: var(--fill-quarternary); } - + &:disabled { background-color: transparent; fill: var(--fill-tertiary); @@ -37,4 +66,8 @@ item-pane-sidenav { } } } + + menupopup { + @include macOS-hide-menu-icons; + } }