diff --git a/packages/notebook-extension/src/index.ts b/packages/notebook-extension/src/index.ts index cd9d9752d1..07712ee88a 100644 --- a/packages/notebook-extension/src/index.ts +++ b/packages/notebook-extension/src/index.ts @@ -38,6 +38,7 @@ import { Poll } from '@lumino/polling'; import { Widget } from '@lumino/widgets'; import { TrustedComponent } from './trusted'; +import { scrollButtons } from './scroll-buttons'; /** * The class for kernel status errors. @@ -432,30 +433,15 @@ const scrollOutput: JupyterFrontEndPlugin = { tracker: INotebookTracker, settingRegistry: ISettingRegistry | null ) => { - const autoScrollThreshold = 100; - let autoScrollOutputs = true; + // Auto-scroll functionality temporarily disabled + // const autoScrollThreshold = 50; + // let autoScrollOutputs = true; // decide whether to scroll the output of the cell based on some heuristics const autoScroll = (cell: CodeCell) => { - if (!autoScrollOutputs) { - // bail if disabled via the settings - cell.removeClass(SCROLLED_OUTPUTS_CLASS); - return; - } - const { outputArea } = cell; - // respect cells with an explicit scrolled state - const scrolled = cell.model.getMetadata('scrolled'); - if (scrolled !== undefined) { - return; - } - const { node } = outputArea; - const height = node.scrollHeight; - const fontSize = parseFloat(node.style.fontSize.replace('px', '')); - const lineHeight = (fontSize || 14) * 1.3; - // do not set via cell.outputScrolled = true, as this would - // otherwise synchronize the scrolled state to the notebook metadata - const scroll = height > lineHeight * autoScrollThreshold; - cell.toggleClass(SCROLLED_OUTPUTS_CLASS, scroll); + // Auto-scroll disabled to fix issues + cell.removeClass(SCROLLED_OUTPUTS_CLASS); + return; }; const handlers: { [id: string]: () => void } = {}; @@ -690,6 +676,7 @@ const plugins: JupyterFrontEndPlugin[] = [ kernelStatus, notebookToolsWidget, scrollOutput, + scrollButtons, tabIcon, trusted, ]; diff --git a/packages/notebook-extension/src/scroll-buttons.ts b/packages/notebook-extension/src/scroll-buttons.ts new file mode 100644 index 0000000000..a487f435d2 --- /dev/null +++ b/packages/notebook-extension/src/scroll-buttons.ts @@ -0,0 +1,90 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import { JupyterFrontEnd, JupyterFrontEndPlugin } from '@jupyterlab/application'; +import { INotebookTracker } from '@jupyterlab/notebook'; +import { ITranslator } from '@jupyterlab/translation'; + +/** + * The class name for the scroll buttons container + */ +const SCROLL_BUTTONS_CLASS = 'jp-Notebook-scrollButtons'; + +/** + * The class name for individual scroll buttons + */ +const SCROLL_BUTTON_CLASS = 'jp-Notebook-scrollButton'; + +/** + * The class name for hiding scroll buttons + */ +const SCROLL_BUTTONS_HIDDEN_CLASS = 'jp-mod-hidden'; + +/** + * A plugin that adds scroll buttons to the notebook + */ +export const scrollButtons: JupyterFrontEndPlugin = { + id: '@jupyter-notebook/notebook-extension:scroll-buttons', + description: 'A plugin that adds scroll buttons to the notebook.', + autoStart: true, + requires: [INotebookTracker], + optional: [ITranslator], + activate: (app: JupyterFrontEnd, tracker: INotebookTracker) => { + // Create scroll buttons container + const buttonContainer = document.createElement('div'); + buttonContainer.className = SCROLL_BUTTONS_CLASS; + + // Create up button + const upButton = document.createElement('button'); + upButton.className = SCROLL_BUTTON_CLASS; + upButton.innerHTML = '↑'; + upButton.title = 'Scroll to top'; + upButton.onclick = () => { + const notebook = tracker.currentWidget?.content; + if (notebook) { + notebook.node.scrollTo({ + top: 0, + behavior: 'smooth' + }); + } + }; + + // Create down button + const downButton = document.createElement('button'); + downButton.className = SCROLL_BUTTON_CLASS; + downButton.innerHTML = '↓'; + downButton.title = 'Scroll to bottom'; + downButton.onclick = () => { + const notebook = tracker.currentWidget?.content; + if (notebook) { + notebook.node.scrollTo({ + top: notebook.node.scrollHeight, + behavior: 'smooth' + }); + } + }; + + // Add buttons to container + buttonContainer.appendChild(upButton); + buttonContainer.appendChild(downButton); + + // Add container to document body + document.body.appendChild(buttonContainer); + + // Show/hide buttons based on scroll position + tracker.currentChanged.connect(() => { + const notebook = tracker.currentWidget?.content; + if (notebook) { + const handleScroll = () => { + const { scrollTop, scrollHeight, clientHeight } = notebook.node; + buttonContainer.classList.toggle( + SCROLL_BUTTONS_HIDDEN_CLASS, + scrollHeight <= clientHeight + ); + }; + notebook.node.addEventListener('scroll', handleScroll); + handleScroll(); // Initial check + } + }); + } +}; diff --git a/packages/notebook-extension/style/base.css b/packages/notebook-extension/style/base.css index ac793f9cfd..0d590c0ff3 100644 --- a/packages/notebook-extension/style/base.css +++ b/packages/notebook-extension/style/base.css @@ -5,6 +5,7 @@ |----------------------------------------------------------------------------*/ @import './variables.css'; +@import './scroll-buttons.css'; /** Document oriented look for the notebook. diff --git a/packages/notebook-extension/style/scroll-buttons.css b/packages/notebook-extension/style/scroll-buttons.css new file mode 100644 index 0000000000..71f153df23 --- /dev/null +++ b/packages/notebook-extension/style/scroll-buttons.css @@ -0,0 +1,42 @@ +/* Scroll buttons container */ +.jp-Notebook-scrollButtons { + position: fixed; + bottom: 20px; + right: 20px; + display: flex; + flex-direction: column; + gap: 8px; + z-index: 1000; +} + +/* Common button styles */ +.jp-Notebook-scrollButton { + width: 40px; + height: 40px; + border-radius: 50%; + background-color: var(--jp-layout-color1); + border: 1px solid var(--jp-border-color1); + color: var(--jp-ui-font-color1); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: background-color 0.2s, transform 0.2s; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +/* Hover state */ +.jp-Notebook-scrollButton:hover { + background-color: var(--jp-layout-color2); + transform: translateY(-2px); +} + +/* Active state */ +.jp-Notebook-scrollButton:active { + transform: translateY(0); +} + +/* Hide buttons when not needed */ +.jp-Notebook-scrollButtons.jp-mod-hidden { + display: none; +}