From 8a501521e8a13906038288898d49ad4f07c954fa Mon Sep 17 00:00:00 2001 From: zadam Date: Sat, 11 May 2019 19:27:33 +0200 Subject: [PATCH] some refactoring etc. --- libraries/chrome-tabs/chrome-tabs.js | 314 +++++++++--------- .../javascripts/services/hoisted_note.js | 5 + .../javascripts/services/note_detail.js | 30 +- src/public/javascripts/services/tree.js | 4 +- src/public/stylesheets/style.css | 2 +- 5 files changed, 191 insertions(+), 164 deletions(-) diff --git a/libraries/chrome-tabs/chrome-tabs.js b/libraries/chrome-tabs/chrome-tabs.js index 34a37d772..c38beb8bf 100644 --- a/libraries/chrome-tabs/chrome-tabs.js +++ b/libraries/chrome-tabs/chrome-tabs.js @@ -8,33 +8,33 @@ !function(i,e){"function"==typeof define&&define.amd?define("jquery-bridget/jquery-bridget",["jquery"],function(t){return e(i,t)}):"object"==typeof module&&module.exports?module.exports=e(i,require("jquery")):i.jQueryBridget=e(i,i.jQuery)}(window,function(t,i){"use strict";var c=Array.prototype.slice,e=t.console,p=void 0===e?function(){}:function(t){e.error(t)};function n(d,o,u){(u=u||i||t.jQuery)&&(o.prototype.option||(o.prototype.option=function(t){u.isPlainObject(t)&&(this.options=u.extend(!0,this.options,t))}),u.fn[d]=function(t){if("string"==typeof t){var i=c.call(arguments,1);return s=i,a="$()."+d+'("'+(r=t)+'")',(e=this).each(function(t,i){var e=u.data(i,d);if(e){var n=e[r];if(n&&"_"!=r.charAt(0)){var o=n.apply(e,s);h=void 0===h?o:h}else p(a+" is not a valid method")}else p(d+" not initialized. Cannot call methods, i.e. "+a)}),void 0!==h?h:e}var e,r,s,h,a,n;return n=t,this.each(function(t,i){var e=u.data(i,d);e?(e.option(n),e._init()):(e=new o(i,n),u.data(i,d,e))}),this},r(u))}function r(t){!t||t&&t.bridget||(t.bridget=n)}return r(i||t.jQuery),n}),function(t,i){"use strict";"function"==typeof define&&define.amd?define("get-size/get-size",[],function(){return i()}):"object"==typeof module&&module.exports?module.exports=i():t.getSize=i()}(window,function(){"use strict";function m(t){var i=parseFloat(t);return-1==t.indexOf("%")&&!isNaN(i)&&i}var e="undefined"==typeof console?function(){}:function(t){console.error(t)},y=["paddingLeft","paddingRight","paddingTop","paddingBottom","marginLeft","marginRight","marginTop","marginBottom","borderLeftWidth","borderRightWidth","borderTopWidth","borderBottomWidth"],b=y.length;function E(t){var i=getComputedStyle(t);return i||e("Style returned "+i+". Are you running this code in a hidden iframe on Firefox? See http://bit.ly/getsizebug1"),i}var _,x=!1;function P(t){if(function(){if(!x){x=!0;var t=document.createElement("div");t.style.width="200px",t.style.padding="1px 2px 3px 4px",t.style.borderStyle="solid",t.style.borderWidth="1px 2px 3px 4px",t.style.boxSizing="border-box";var i=document.body||document.documentElement;i.appendChild(t);var e=E(t);P.isBoxSizeOuter=_=200==m(e.width),i.removeChild(t)}}(),"string"==typeof t&&(t=document.querySelector(t)),t&&"object"==typeof t&&t.nodeType){var i=E(t);if("none"==i.display)return function(){for(var t={width:0,height:0,innerWidth:0,innerHeight:0,outerWidth:0,outerHeight:0},i=0;i {} + const noop = _ => {}; const closest = (value, array) => { - let closest = Infinity - let closestIndex = -1 + let closest = Infinity; + let closestIndex = -1; array.forEach((v, i) => { if (Math.abs(value - v) < closest) { - closest = Math.abs(value - v) + closest = Math.abs(value - v); closestIndex = i } - }) + }); - return closestIndex - } + return closestIndex; + }; const tabTemplate = `
@@ -44,13 +44,13 @@
×
- ` + `; const defaultTapProperties = { title: 'New tab' - } + }; - let instanceId = 0 + let instanceId = 0; class ChromeTabs { constructor() { @@ -58,40 +58,40 @@ } init(el) { - this.el = el + this.el = el; - this.instanceId = instanceId - this.el.setAttribute('data-chrome-tabs-instance-id', this.instanceId) - instanceId += 1 + this.instanceId = instanceId; + this.el.setAttribute('data-chrome-tabs-instance-id', this.instanceId); + instanceId += 1; - this.setupCustomProperties() - this.setupStyleEl() - this.setupEvents() - this.layoutTabs() - this.setupDraggabilly() - this.setVisibility() + this.setupCustomProperties(); + this.setupStyleEl(); + this.setupEvents(); + this.layoutTabs(); + this.setupDraggabilly(); + this.setVisibility(); } emit(eventName, data) { - this.el.dispatchEvent(new CustomEvent(eventName, { detail: data })) + this.el.dispatchEvent(new CustomEvent(eventName, { detail: data })); } setupCustomProperties() { - this.el.style.setProperty('--tab-content-margin', `${ TAB_CONTENT_MARGIN }px`) + this.el.style.setProperty('--tab-content-margin', `${ TAB_CONTENT_MARGIN }px`); } setupStyleEl() { - this.styleEl = document.createElement('style') - this.el.appendChild(this.styleEl) + this.styleEl = document.createElement('style'); + this.el.appendChild(this.styleEl); } setupEvents() { window.addEventListener('resize', _ => { - this.cleanUpPreviouslyDraggedTabs() - this.layoutTabs() - }) + this.cleanUpPreviouslyDraggedTabs(); + this.layoutTabs(); + }); - this.tabEls.forEach((tabEl) => this.setTabCloseEventListener(tabEl)) + this.tabEls.forEach((tabEl) => this.setTabCloseEventListener(tabEl)); } setVisibility() { @@ -99,121 +99,117 @@ } get tabEls() { - return Array.prototype.slice.call(this.el.querySelectorAll('.chrome-tab')) + return Array.prototype.slice.call(this.el.querySelectorAll('.chrome-tab')); } get tabContentEl() { - return this.el.querySelector('.chrome-tabs-content') + return this.el.querySelector('.chrome-tabs-content'); } get tabContentWidths() { - const numberOfTabs = this.tabEls.length - const tabsContentWidth = this.tabContentEl.clientWidth - const tabsCumulativeOverlappedWidth = (numberOfTabs - 1) * TAB_CONTENT_OVERLAP_DISTANCE - const targetWidth = (tabsContentWidth - (2 * TAB_CONTENT_MARGIN) + tabsCumulativeOverlappedWidth) / numberOfTabs - const clampedTargetWidth = Math.max(TAB_CONTENT_MIN_WIDTH, Math.min(TAB_CONTENT_MAX_WIDTH, targetWidth)) - const flooredClampedTargetWidth = Math.floor(clampedTargetWidth) - const totalTabsWidthUsingTarget = (flooredClampedTargetWidth * numberOfTabs) + (2 * TAB_CONTENT_MARGIN) - tabsCumulativeOverlappedWidth - const totalExtraWidthDueToFlooring = tabsContentWidth - totalTabsWidthUsingTarget + const numberOfTabs = this.tabEls.length; + const tabsContentWidth = this.tabContentEl.clientWidth; + const tabsCumulativeOverlappedWidth = (numberOfTabs - 1) * TAB_CONTENT_OVERLAP_DISTANCE; + const targetWidth = (tabsContentWidth - (2 * TAB_CONTENT_MARGIN) + tabsCumulativeOverlappedWidth) / numberOfTabs; + const clampedTargetWidth = Math.max(TAB_CONTENT_MIN_WIDTH, Math.min(TAB_CONTENT_MAX_WIDTH, targetWidth)); + const flooredClampedTargetWidth = Math.floor(clampedTargetWidth); + const totalTabsWidthUsingTarget = (flooredClampedTargetWidth * numberOfTabs) + (2 * TAB_CONTENT_MARGIN) - tabsCumulativeOverlappedWidth; + const totalExtraWidthDueToFlooring = tabsContentWidth - totalTabsWidthUsingTarget; - // TODO - Support tabs with different widths / e.g. "pinned" tabs - const widths = [] - let extraWidthRemaining = totalExtraWidthDueToFlooring + const widths = []; + let extraWidthRemaining = totalExtraWidthDueToFlooring; for (let i = 0; i < numberOfTabs; i += 1) { - const extraWidth = flooredClampedTargetWidth < TAB_CONTENT_MAX_WIDTH && extraWidthRemaining > 0 ? 1 : 0 - widths.push(flooredClampedTargetWidth + extraWidth) - if (extraWidthRemaining > 0) extraWidthRemaining -= 1 + const extraWidth = flooredClampedTargetWidth < TAB_CONTENT_MAX_WIDTH && extraWidthRemaining > 0 ? 1 : 0; + widths.push(flooredClampedTargetWidth + extraWidth); + if (extraWidthRemaining > 0) extraWidthRemaining -= 1; } return widths } get tabContentPositions() { - const positions = [] - const tabContentWidths = this.tabContentWidths + const positions = []; + const tabContentWidths = this.tabContentWidths; - let position = TAB_CONTENT_MARGIN + let position = TAB_CONTENT_MARGIN; tabContentWidths.forEach((width, i) => { - const offset = i * TAB_CONTENT_OVERLAP_DISTANCE - positions.push(position - offset) - position += width - }) + const offset = i * TAB_CONTENT_OVERLAP_DISTANCE; + positions.push(position - offset); + position += width; + }); - return positions + return positions; } get tabPositions() { - const positions = [] + const positions = []; this.tabContentPositions.forEach((contentPosition) => { - positions.push(contentPosition - TAB_CONTENT_MARGIN) - }) + positions.push(contentPosition - TAB_CONTENT_MARGIN); + }); - return positions + return positions; } layoutTabs() { - const tabContentWidths = this.tabContentWidths + const tabContentWidths = this.tabContentWidths; this.tabEls.forEach((tabEl, i) => { - const contentWidth = tabContentWidths[i] - const width = contentWidth + (2 * TAB_CONTENT_MARGIN) + const contentWidth = tabContentWidths[i]; + const width = contentWidth + (2 * TAB_CONTENT_MARGIN); - tabEl.style.width = width + 'px' - tabEl.removeAttribute('is-small') - tabEl.removeAttribute('is-smaller') - tabEl.removeAttribute('is-mini') + tabEl.style.width = width + 'px'; + tabEl.removeAttribute('is-small'); + tabEl.removeAttribute('is-smaller'); + tabEl.removeAttribute('is-mini'); - if (contentWidth < TAB_SIZE_SMALL) tabEl.setAttribute('is-small', '') - if (contentWidth < TAB_SIZE_SMALLER) tabEl.setAttribute('is-smaller', '') - if (contentWidth < TAB_SIZE_MINI) tabEl.setAttribute('is-mini', '') - }) + if (contentWidth < TAB_SIZE_SMALL) tabEl.setAttribute('is-small', ''); + if (contentWidth < TAB_SIZE_SMALLER) tabEl.setAttribute('is-smaller', ''); + if (contentWidth < TAB_SIZE_MINI) tabEl.setAttribute('is-mini', ''); + }); - let styleHTML = '' + let styleHTML = ''; this.tabPositions.forEach((position, i) => { styleHTML += ` .chrome-tabs[data-chrome-tabs-instance-id="${ this.instanceId }"] .chrome-tab:nth-child(${ i + 1 }) { transform: translate3d(${ position }px, 0, 0) } ` - }) - this.styleEl.innerHTML = styleHTML - } + }); - createNewTabEl() { - const div = document.createElement('div') - div.innerHTML = tabTemplate - return div.firstElementChild + this.styleEl.innerHTML = styleHTML; } addTab(tabProperties, { animate = true, background = false } = {}) { - const tabEl = this.createNewTabEl() + const div = document.createElement('div'); + div.innerHTML = tabTemplate; + const tabEl = div.firstElementChild; if (animate) { - tabEl.classList.add('chrome-tab-was-just-added') - setTimeout(() => tabEl.classList.remove('chrome-tab-was-just-added'), 500) + tabEl.classList.add('chrome-tab-was-just-added'); + setTimeout(() => tabEl.classList.remove('chrome-tab-was-just-added'), 500); } - tabProperties = Object.assign({}, defaultTapProperties, tabProperties) - this.tabContentEl.appendChild(tabEl) - this.setVisibility() - this.setTabCloseEventListener(tabEl) - this.updateTab(tabEl, tabProperties) - this.emit('tabAdd', { tabEl }) - if (!background) this.setCurrentTab(tabEl) - this.cleanUpPreviouslyDraggedTabs() - this.layoutTabs() - this.setupDraggabilly() + tabProperties = Object.assign({}, defaultTapProperties, tabProperties); + this.tabContentEl.appendChild(tabEl); + this.setVisibility(); + this.setTabCloseEventListener(tabEl); + this.updateTab(tabEl, tabProperties); + this.emit('tabAdd', { tabEl }); + if (!background) this.setCurrentTab(tabEl); + this.cleanUpPreviouslyDraggedTabs(); + this.layoutTabs(); + this.setupDraggabilly(); - return tabEl + return tabEl; } setTabCloseEventListener(tabEl) { - tabEl.querySelector('.chrome-tab-close').addEventListener('click', _ => this.removeTab(tabEl)) + tabEl.querySelector('.chrome-tab-close').addEventListener('click', _ => this.removeTab(tabEl)); } get activeTabEl() { - return this.el.querySelector('.chrome-tab[active]') + return this.el.querySelector('.chrome-tab[active]'); } get previousTabEl() { @@ -257,15 +253,15 @@ } hasActiveTab() { - return !!this.activeTabEl + return !!this.activeTabEl; } setCurrentTab(tabEl) { - const activeTabEl = this.activeTabEl - if (activeTabEl === tabEl) return - if (activeTabEl) activeTabEl.removeAttribute('active') - tabEl.setAttribute('active', '') - this.emit('activeTabChange', { tabEl }) + const activeTabEl = this.activeTabEl; + if (activeTabEl === tabEl) return; + if (activeTabEl) activeTabEl.removeAttribute('active'); + tabEl.setAttribute('active', ''); + this.emit('activeTabChange', { tabEl }); } removeTab(tabEl) { @@ -276,12 +272,12 @@ this.setCurrentTab(tabEl.previousElementSibling) } } - tabEl.parentNode.removeChild(tabEl) - this.emit('tabRemove', { tabEl }) - this.cleanUpPreviouslyDraggedTabs() - this.layoutTabs() - this.setupDraggabilly() - this.setVisibility() + tabEl.parentNode.removeChild(tabEl); + this.emit('tabRemove', { tabEl }); + this.cleanUpPreviouslyDraggedTabs(); + this.layoutTabs(); + this.setupDraggabilly(); + this.setVisibility(); } removeAllTabsExceptForThis(remainingTabEl) { @@ -293,93 +289,93 @@ } updateTab(tabEl, tabProperties) { - tabEl.querySelector('.chrome-tab-title').textContent = tabProperties.title + tabEl.querySelector('.chrome-tab-title').textContent = tabProperties.title; if (tabProperties.id) { - tabEl.setAttribute('data-tab-id', tabProperties.id) + tabEl.setAttribute('data-tab-id', tabProperties.id); } } cleanUpPreviouslyDraggedTabs() { - this.tabEls.forEach((tabEl) => tabEl.classList.remove('chrome-tab-was-just-dragged')) + this.tabEls.forEach((tabEl) => tabEl.classList.remove('chrome-tab-was-just-dragged')); } setupDraggabilly() { - const tabEls = this.tabEls - const tabPositions = this.tabPositions + const tabEls = this.tabEls; + const tabPositions = this.tabPositions; if (this.isDragging) { - this.isDragging = false - this.el.classList.remove('chrome-tabs-is-sorting') - this.draggabillyDragging.element.classList.remove('chrome-tab-is-dragging') - this.draggabillyDragging.element.style.transform = '' - this.draggabillyDragging.dragEnd() - this.draggabillyDragging.isDragging = false - this.draggabillyDragging.positionDrag = noop // Prevent Draggabilly from updating tabEl.style.transform in later frames - this.draggabillyDragging.destroy() - this.draggabillyDragging = null + this.isDragging = false; + this.el.classList.remove('chrome-tabs-is-sorting'); + this.draggabillyDragging.element.classList.remove('chrome-tab-is-dragging'); + this.draggabillyDragging.element.style.transform = ''; + this.draggabillyDragging.dragEnd(); + this.draggabillyDragging.isDragging = false; + this.draggabillyDragging.positionDrag = noop; // Prevent Draggabilly from updating tabEl.style.transform in later frames + this.draggabillyDragging.destroy(); + this.draggabillyDragging = null; } - this.draggabillies.forEach(d => d.destroy()) + this.draggabillies.forEach(d => d.destroy()); tabEls.forEach((tabEl, originalIndex) => { - const originalTabPositionX = tabPositions[originalIndex] + const originalTabPositionX = tabPositions[originalIndex]; const draggabilly = new Draggabilly(tabEl, { axis: 'x', handle: '.chrome-tab-drag-handle', containment: this.tabContentEl - }) + }); - this.draggabillies.push(draggabilly) + this.draggabillies.push(draggabilly); draggabilly.on('pointerDown', _ => { this.setCurrentTab(tabEl) - }) + }); draggabilly.on('dragStart', _ => { - this.isDragging = true - this.draggabillyDragging = draggabilly - tabEl.classList.add('chrome-tab-is-dragging') - this.el.classList.add('chrome-tabs-is-sorting') - }) + this.isDragging = true; + this.draggabillyDragging = draggabilly; + tabEl.classList.add('chrome-tab-is-dragging'); + this.el.classList.add('chrome-tabs-is-sorting'); + }); draggabilly.on('dragEnd', _ => { - this.isDragging = false - const finalTranslateX = parseFloat(tabEl.style.left, 10) - tabEl.style.transform = `translate3d(0, 0, 0)` + this.isDragging = false; + const finalTranslateX = parseFloat(tabEl.style.left, 10); + tabEl.style.transform = `translate3d(0, 0, 0)`; // Animate dragged tab back into its place requestAnimationFrame(_ => { - tabEl.style.left = '0' - tabEl.style.transform = `translate3d(${ finalTranslateX }px, 0, 0)` + tabEl.style.left = '0'; + tabEl.style.transform = `translate3d(${ finalTranslateX }px, 0, 0)`; requestAnimationFrame(_ => { - tabEl.classList.remove('chrome-tab-is-dragging') - this.el.classList.remove('chrome-tabs-is-sorting') + tabEl.classList.remove('chrome-tab-is-dragging'); + this.el.classList.remove('chrome-tabs-is-sorting'); - tabEl.classList.add('chrome-tab-was-just-dragged') + tabEl.classList.add('chrome-tab-was-just-dragged'); requestAnimationFrame(_ => { - tabEl.style.transform = '' + tabEl.style.transform = ''; - this.layoutTabs() - this.setupDraggabilly() + this.layoutTabs(); + this.setupDraggabilly(); }) }) }) - }) + }); draggabilly.on('dragMove', (event, pointer, moveVector) => { // Current index be computed within the event since it can change during the dragMove - const tabEls = this.tabEls - const currentIndex = tabEls.indexOf(tabEl) + const tabEls = this.tabEls; + const currentIndex = tabEls.indexOf(tabEl); - const currentTabPositionX = originalTabPositionX + moveVector.x - const destinationIndexTarget = closest(currentTabPositionX, tabPositions) - const destinationIndex = Math.max(0, Math.min(tabEls.length, destinationIndexTarget)) + const currentTabPositionX = originalTabPositionX + moveVector.x; + const destinationIndexTarget = closest(currentTabPositionX, tabPositions); + const destinationIndex = Math.max(0, Math.min(tabEls.length, destinationIndexTarget)); if (currentIndex !== destinationIndex) { - this.animateTabMove(tabEl, currentIndex, destinationIndex) + this.animateTabMove(tabEl, currentIndex, destinationIndex); } }) }) @@ -387,14 +383,14 @@ animateTabMove(tabEl, originIndex, destinationIndex) { if (destinationIndex < originIndex) { - tabEl.parentNode.insertBefore(tabEl, this.tabEls[destinationIndex]) + tabEl.parentNode.insertBefore(tabEl, this.tabEls[destinationIndex]); } else { - tabEl.parentNode.insertBefore(tabEl, this.tabEls[destinationIndex + 1]) + tabEl.parentNode.insertBefore(tabEl, this.tabEls[destinationIndex + 1]); } - this.emit('tabReorder', { tabEl, originIndex, destinationIndex }) - this.layoutTabs() + this.emit('tabReorder', { tabEl, originIndex, destinationIndex }); + this.layoutTabs(); } } - window.ChromeTabs = ChromeTabs -})() + window.ChromeTabs = ChromeTabs; +})(); \ No newline at end of file diff --git a/src/public/javascripts/services/hoisted_note.js b/src/public/javascripts/services/hoisted_note.js index b01f9ea67..ebeb26351 100644 --- a/src/public/javascripts/services/hoisted_note.js +++ b/src/public/javascripts/services/hoisted_note.js @@ -1,6 +1,7 @@ import optionsInit from './options_init.js'; import server from "./server.js"; import tree from "./tree.js"; +import noteDetailService from "./note_detail.js"; let hoistedNoteId; @@ -17,6 +18,10 @@ async function getHoistedNoteId() { async function setHoistedNoteId(noteId) { hoistedNoteId = noteId; + if (noteId !== 'root') { + await noteDetailService.filterTabs(noteId); + } + await server.put('options/hoistedNoteId/' + noteId); await tree.reload(); diff --git a/src/public/javascripts/services/note_detail.js b/src/public/javascripts/services/note_detail.js index ad59b6d84..72eb3194d 100644 --- a/src/public/javascripts/services/note_detail.js +++ b/src/public/javascripts/services/note_detail.js @@ -61,6 +61,8 @@ async function switchToNote(notePath) { await saveNotesIfChanged(); await loadNoteDetail(notePath); + + openTabsChanged(); } function getActiveNoteContent() { @@ -178,7 +180,7 @@ async function loadNoteDetailToContext(ctx, note, notePath) { } } -async function loadNoteDetail(notePath, options) { +async function loadNoteDetail(notePath, options = {}) { const newTab = !!options.newTab; const activate = !!options.activate; @@ -218,6 +220,19 @@ async function loadNote(noteId) { return new NoteFull(treeCache, row); } +async function filterTabs(noteId) { + for (const tc of tabContexts) { + chromeTabs.removeTab(tc.tab); + } + + await loadNoteDetail(noteId, { + newTab: true, + activate: true + }); + + await saveOpenTabs(); +} + function focusOnTitle() { getActiveContext().$noteTitle.focus(); } @@ -280,9 +295,17 @@ chromeTabsEl.addEventListener('activeTabChange', ({ detail }) => { console.log(`Activated tab ${tabId}`); }); -chromeTabsEl.addEventListener('tabRemove', ({ detail }) => { +chromeTabsEl.addEventListener('tabRemove', async ({ detail }) => { const tabId = parseInt(detail.tabEl.getAttribute('data-tab-id')); + await saveNotesIfChanged(); + + const tabContentToDelete = tabContexts.find(nc => nc.tabId === tabId); + + if (tabContentToDelete) { + tabContentToDelete.$tabContent.remove(); + } + tabContexts = tabContexts.filter(nc => nc.tabId !== tabId); console.log(`Removed tab ${tabId}`); @@ -399,5 +422,6 @@ export default { addDetailLoadedListener, getActiveContext, getActiveComponent, - clearOpenTabsTask + clearOpenTabsTask, + filterTabs }; \ No newline at end of file diff --git a/src/public/javascripts/services/tree.js b/src/public/javascripts/services/tree.js index 376d1a8c3..a6d6b3813 100644 --- a/src/public/javascripts/services/tree.js +++ b/src/public/javascripts/services/tree.js @@ -531,7 +531,9 @@ async function reload() { // make sure the reload won't trigger reactivation. This is important especially in cases where we wait for the reload // to finish to then activate some other note. But since the activate() event is called asynchronously, it may be called // (or finished calling) after we switched to a different note. - ignoreNextActivationNoteId = getActiveNode().data.noteId; + if (getActiveNode()) { + ignoreNextActivationNoteId = getActiveNode().data.noteId; + } await getTree().reload(notes); } diff --git a/src/public/stylesheets/style.css b/src/public/stylesheets/style.css index c3e128014..7ba6bc1d4 100644 --- a/src/public/stylesheets/style.css +++ b/src/public/stylesheets/style.css @@ -743,7 +743,7 @@ div[data-notify="container"] { .unhoist-button { text-decoration: underline !important; - color: blue !important; + color: var(--link-color) !important; cursor: pointer !important; }