2025-06-04 21:07:47 +02:00
/ *
THIS IS A GENERATED / BUNDLED FILE BY ROLLUP
if you want to view the source visit the plugins github repository
* /
'use strict' ;
var obsidian = require ( 'obsidian' ) ;
/ * ! * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Copyright ( c ) Microsoft Corporation .
Permission to use , copy , modify , and / or distribute this software for any
purpose with or without fee is hereby granted .
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS . IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL , DIRECT ,
INDIRECT , OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE , DATA OR PROFITS , WHETHER IN AN ACTION OF CONTRACT , NEGLIGENCE OR
OTHER TORTIOUS ACTION , ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
function _ _awaiter ( thisArg , _arguments , P , generator ) {
function adopt ( value ) { return value instanceof P ? value : new P ( function ( resolve ) { resolve ( value ) ; } ) ; }
return new ( P || ( P = Promise ) ) ( function ( resolve , reject ) {
function fulfilled ( value ) { try { step ( generator . next ( value ) ) ; } catch ( e ) { reject ( e ) ; } }
function rejected ( value ) { try { step ( generator [ "throw" ] ( value ) ) ; } catch ( e ) { reject ( e ) ; } }
function step ( result ) { result . done ? resolve ( result . value ) : adopt ( result . value ) . then ( fulfilled , rejected ) ; }
step ( ( generator = generator . apply ( thisArg , _arguments || [ ] ) ) . next ( ) ) ;
} ) ;
}
/ * *
* ReplaceTerm enables us to store the parameters for a replacement to add a new size parameter .
* /
class ReplaceTerm {
constructor ( replaceFrom , replaceWith ) {
this . replaceFrom = replaceFrom ;
this . replaceWith = replaceWith ;
}
// Generate a string that can be used in a string.replace() call as the string to replace
getReplaceFromString ( oldSize ) {
return this . replaceFrom ( oldSize ) ;
}
// Generate a string that can be used in a string.replace() call as the replacement string
getReplaceWithString ( newSize ) {
return this . replaceWith ( newSize ) ;
}
}
class Util {
/ * *
* For a given file content decide if a string is inside a table
* @ param searchString string
* @ param fileValue file content
* @ private
* /
static isInTable ( searchString , fileValue ) {
2026-03-06 16:34:59 +01:00
return fileValue . search ( new RegExp ( ` ^ \\ |.+ ${ escapeRegex ( searchString ) } .+ \\ | $ ` , "m" ) ) !== - 1 ;
2025-06-04 21:07:47 +02:00
}
/ * *
* Get the image name from a given src uri of a local image
* ( URI like app : //local/C:/.../image.png?1677337704730)
* @ param imageUri uri of the image
* @ private
* /
static getLocalImageNameFromUri ( imageUri ) {
imageUri = decodeURI ( imageUri ) ;
const imageNameMatch = imageUri . match ( /([^\/?\\]+)(\?.*?|)$/ ) ;
const imageName = imageNameMatch ? imageNameMatch [ 1 ] : "" ;
// Handle linux not correctly decoding the %2F before the Filename to a \
const hasLinuxDecodingIssue = imageName . startsWith ( "2F" ) ;
return hasLinuxDecodingIssue ? imageName . slice ( 2 ) : imageName ;
}
/ * *
* Get the parameters needed to handle the zoom for a local image .
* Source can be either a obsidian link like [ [ image . png ] ] or a markdown link like [ image . png ] ( image . png )
* @ param imageName Name of the image
* @ param fileText content of the current file
* @ returns parameters to handle the zoom
* /
static getLocalImageZoomParams ( imageName , fileText ) {
imageName = this . determineImageName ( imageName , fileText ) ;
// Get the folder name if the image is located in a folder
const folderName = this . getFolderNameIfExist ( imageName , fileText ) ;
imageName = ` ${ folderName } ${ imageName } ` ;
const isInTable = Util . isInTable ( imageName , fileText ) ;
// Separator to use for the replacement
const sizeSeparator = isInTable ? "\\|" : "|" ;
// Separator to use for the regex: isInTable ? \\\| : \|
const regexSeparator = isInTable ? "\\\\\\|" : "\\|" ;
// check character before the imageName to check if markdown link or obsidian link
const imageNamePosition = fileText . indexOf ( imageName ) ;
const isObsidianLink = fileText . charAt ( imageNamePosition - 1 ) === "[" ;
if ( isObsidianLink ) {
2026-03-06 16:34:59 +01:00
const imageAttributes = this . getImageAttributes ( imageName , fileText ) ;
imageName = ` ${ imageName } ${ imageAttributes } ` ;
2025-06-04 21:07:47 +02:00
return Util . generateReplaceTermForObsidianSyntax ( imageName , regexSeparator , sizeSeparator ) ;
}
else {
return Util . generateReplaceTermForMarkdownSyntax ( imageName , regexSeparator , sizeSeparator , fileText ) ;
}
}
/ * *
* When using markdown link syntax the image name can be encoded . This function checks if the image name is encoded and if not encodes it .
*
* @ param origImageName Image name
* @ param fileText File content
* @ returns image name with the correct encoding
* /
static determineImageName ( origImageName , fileText ) {
const encodedImageName = encodeURI ( origImageName ) ;
const spaceEncodedImageName = origImageName . replace ( / /g , "%20" ) ;
// Try matching original, full URI encoded, and space encoded
const imageNameVariants = [ origImageName , encodedImageName , spaceEncodedImageName ] ;
for ( const variant of imageNameVariants ) {
if ( fileText . includes ( variant ) ) {
return variant ;
}
}
throw new Error ( "Image not found in file" ) ;
}
/ * *
* Extracts the folder name from the given image name by looking for the first "[" or "(" character
* that appears before the image name in the file text .
* @ param imageName The name of the image .
* @ param fileText The text of the file that contains the image .
* @ returns The name of the folder that contains the image , or an empty string if no folder is found .
* /
static getFolderNameIfExist ( imageName , fileText ) {
const index = fileText . indexOf ( imageName ) ;
if ( index === - 1 ) {
throw new Error ( "Image not found in file" ) ;
}
const stringBeforeFileName = fileText . substring ( 0 , index ) ;
const lastOpeningBracket = stringBeforeFileName . lastIndexOf ( "[" ) ; // Obsidian link
const lastOpeningParenthesis = stringBeforeFileName . lastIndexOf ( "(" ) ; // Markdown link
const lastOpeningBracketOrParenthesis = Math . max ( lastOpeningBracket , lastOpeningParenthesis ) ;
const folderName = stringBeforeFileName . substring ( lastOpeningBracketOrParenthesis + 1 ) ;
return folderName ;
}
/ * *
* Extracts any image attributes like | ctr for ITS Theme that appear after the given image name in the file .
* @ param imageName - The name of the image to search for .
* @ param fileText - The content of the file to search in .
* @ returns A string containing any image attributes that appear after the image name .
* /
static getImageAttributes ( imageName , fileText ) {
const index = fileText . indexOf ( imageName ) ;
const stringAfterFileName = fileText . substring ( index + imageName . length ) ;
const regExpMatchArray = stringAfterFileName . match ( /([^\]]*?)\\?\|\d+]]|([^\]]*?)]]|/ ) ;
if ( regExpMatchArray ) {
if ( ! ! regExpMatchArray [ 1 ] ) {
return regExpMatchArray [ 1 ] ;
}
else if ( ! ! regExpMatchArray [ 2 ] ) {
return regExpMatchArray [ 2 ] ;
}
}
return "" ;
}
/ * *
* Get the parameters needed to handle the zoom for images in markdown format .
* Example : ! [ image . png ] ( image . png )
* @ param imageName Name of the image
* @ param fileText content of the current file
* @ returns parameters to handle the zoom
* @ private
*
* /
static generateReplaceTermForMarkdownSyntax ( imageName , regexSeparator , sizeSeparator , fileText ) {
const sizeMatchRegExp = new RegExp ( ` ${ regexSeparator } ( \\ d+)] ${ escapeRegex ( "(" + imageName + ")" ) } ` ) ;
const replaceSizeExistFrom = ( oldSize ) => ` ${ sizeSeparator } ${ oldSize } ]( ${ imageName } ) ` ;
const replaceSizeExistWith = ( newSize ) => ` ${ sizeSeparator } ${ newSize } ]( ${ imageName } ) ` ;
const replaceSizeNotExistsFrom = ( oldSize ) => ` ]( ${ imageName } ) ` ;
const replaceSizeNotExistsWith = ( newSize ) => ` ${ sizeSeparator } ${ newSize } ]( ${ imageName } ) ` ;
const replaceSizeExist = new ReplaceTerm ( replaceSizeExistFrom , replaceSizeExistWith ) ;
const replaceSizeNotExist = new ReplaceTerm ( replaceSizeNotExistsFrom , replaceSizeNotExistsWith ) ;
return {
sizeMatchRegExp : sizeMatchRegExp ,
replaceSizeExist : replaceSizeExist ,
replaceSizeNotExist : replaceSizeNotExist ,
} ;
}
/ * *
* Get the parameters needed to handle the zoom for images in markdown format .
* Example : ! [ [ image . png ] ]
* @ param imageName Name of the image
* @ param fileText content of the current file
* @ returns parameters to handle the zoom
* @ private
*
* /
static generateReplaceTermForObsidianSyntax ( imageName , regexSeparator , sizeSeparator ) {
const sizeMatchRegExp = new RegExp ( ` ${ escapeRegex ( imageName ) } ${ regexSeparator } ( \\ d+) ` ) ;
const replaceSizeExistFrom = ( oldSize ) => ` ${ imageName } ${ sizeSeparator } ${ oldSize } ` ;
const replaceSizeExistWith = ( newSize ) => ` ${ imageName } ${ sizeSeparator } ${ newSize } ` ;
const replaceSizeNotExistsFrom = ( oldSize ) => ` ${ imageName } ` ;
const replaceSizeNotExistsWith = ( newSize ) => ` ${ imageName } ${ sizeSeparator } ${ newSize } ` ;
const replaceSizeExist = new ReplaceTerm ( replaceSizeExistFrom , replaceSizeExistWith ) ;
const replaceSizeNotExist = new ReplaceTerm ( replaceSizeNotExistsFrom , replaceSizeNotExistsWith ) ;
return {
sizeMatchRegExp : sizeMatchRegExp ,
replaceSizeExist : replaceSizeExist ,
replaceSizeNotExist : replaceSizeNotExist ,
} ;
}
/ * *
* Get the parameters needed to handle the zoom for a remote image .
* Format : https : //www.example.com/image.png
* @ param imageUri URI of the image
* @ param fileText content of the current file
* @ returns parameters to handle the zoom
* /
static getRemoteImageZoomParams ( imageUri , fileText ) {
const isInTable = Util . isInTable ( imageUri , fileText ) ;
// Separator to use for the replacement
const sizeSeparator = isInTable ? "\\|" : "|" ;
// Separator to use for the regex: isInTable ? \\\| : \|
const regexSeparator = isInTable ? "\\\\\\|" : "\\|" ;
return Util . generateReplaceTermForMarkdownSyntax ( imageUri , regexSeparator , sizeSeparator , fileText ) ;
}
}
/ * *
* Function to escape a string into a valid searchable string for a regex
* @ param string string to escape
* @ returns escaped string
* /
function escapeRegex ( string ) {
return string . replace ( /[-\/\\^$*+?.()|[\]{}]/g , '\\$&' ) ;
}
var ModifierKey ;
( function ( ModifierKey ) {
ModifierKey [ "ALT" ] = "AltLeft" ;
ModifierKey [ "CTRL" ] = "ControlLeft" ;
ModifierKey [ "SHIFT" ] = "ShiftLeft" ;
ModifierKey [ "ALT_RIGHT" ] = "AltRight" ;
ModifierKey [ "CTRL_RIGHT" ] = "ControlRight" ;
ModifierKey [ "SHIFT_RIGHT" ] = "ShiftRight" ;
} ) ( ModifierKey || ( ModifierKey = { } ) ) ;
const DEFAULT _SETTINGS = {
modifierKey : ModifierKey . ALT ,
stepSize : 25 ,
2026-03-06 16:34:59 +01:00
initialSize : 500 ,
resizeInCanvas : true ,
2025-06-04 21:07:47 +02:00
} ;
2026-03-06 16:34:59 +01:00
const CtrlCanvasConflictWarning = "Warning: Using Ctrl as the modifier key conflicts with default canvas zooming behavior when 'Resize in canvas' is enabled. Consider using another modifier key or disabling 'Resize in canvas'." ;
2025-06-04 21:07:47 +02:00
class MouseWheelZoomPlugin extends obsidian . Plugin {
constructor ( ) {
super ( ... arguments ) ;
this . isKeyHeldDown = false ;
this . wheelOpt = { passive : false , capture : true } ;
this . wheelEvent = 'wheel' ;
}
onload ( ) {
return _ _awaiter ( this , void 0 , void 0 , function * ( ) {
yield this . loadSettings ( ) ;
this . registerEvent ( this . app . workspace . on ( "window-open" , ( newWindow ) => this . registerEvents ( newWindow . win ) ) ) ;
this . registerEvents ( window ) ;
this . addSettingTab ( new MouseWheelZoomSettingsTab ( this . app , this ) ) ;
console . log ( "Loaded: Mousewheel image zoom" ) ;
2026-03-06 16:34:59 +01:00
this . checkExistingUserConflict ( ) ;
2025-06-04 21:07:47 +02:00
} ) ;
}
2026-03-06 16:34:59 +01:00
checkExistingUserConflict ( ) {
const noticeShownKey = 'mousewheel-zoom-ctrl-warning-shown' ; // Key for localStorage flag
const isCtrl = this . settings . modifierKey === ModifierKey . CTRL || this . settings . modifierKey === ModifierKey . CTRL _RIGHT ;
// Only show the notice if the conflict exists AND the user hasn't dismissed it before (using localStorage flag)
if ( isCtrl && this . settings . resizeInCanvas && ! localStorage . getItem ( noticeShownKey ) ) {
const fragment = document . createDocumentFragment ( ) ;
const titleEl = document . createElement ( 'strong' ) ;
titleEl . textContent = "Mousewheel Image Zoom" ;
fragment . appendChild ( titleEl ) ;
fragment . appendChild ( document . createElement ( 'br' ) ) ;
const messageEl = document . createElement ( 'span' ) ;
messageEl . textContent = CtrlCanvasConflictWarning ;
fragment . appendChild ( messageEl ) ;
fragment . appendChild ( document . createElement ( 'br' ) ) ;
const settingsButton = document . createElement ( 'button' ) ;
settingsButton . textContent = "Open Settings" ;
settingsButton . style . marginTop = "5px" ;
settingsButton . onclick = ( ) => {
// settings is a private property of the app object, so we need to cast it to any to access it
// See https://forum.obsidian.md/t/open-settings-for-my-plugin-community-plugin-settings-deeplink/61563/4
const setting = this . app . setting ;
setting . open ( ) ;
setting . openTabById ( this . manifest . id ) ;
} ;
fragment . appendChild ( settingsButton ) ;
new obsidian . Notice ( fragment , 0 ) ;
// Set the flag in localStorage so the notice doesn't appear again
// unless the user clears their localStorage or the key changes.
localStorage . setItem ( noticeShownKey , 'true' ) ;
}
}
2025-06-04 21:07:47 +02:00
/ * *
* When the config key is released , we enable the scroll again and reset the key held down flag .
* /
onConfigKeyUp ( currentWindow ) {
this . isKeyHeldDown = false ;
this . enableScroll ( currentWindow ) ;
}
onunload ( currentWindow = window ) {
// Re-enable the normal scrolling behavior when the plugin unloads
this . enableScroll ( currentWindow ) ;
}
/ * *
* Registers image resizing events for the specified window
* @ param currentWindow window in which to register events
* @ private
* /
registerEvents ( currentWindow ) {
const doc = currentWindow . document ;
this . registerDomEvent ( doc , "keydown" , ( evt ) => {
2026-03-06 16:34:59 +01:00
var _a ;
2025-06-04 21:07:47 +02:00
if ( evt . code === this . settings . modifierKey . toString ( ) ) {
2026-03-06 16:34:59 +01:00
// When canvas mode is enabled we just ignore the keydown event if the canvas is active
const isActiveViewCanvas = ( ( _a = this . app . workspace . getActiveViewOfType ( obsidian . View ) ) === null || _a === void 0 ? void 0 : _a . getViewType ( ) ) === "canvas" ;
if ( isActiveViewCanvas && ! this . settings . resizeInCanvas ) {
return ;
}
2025-06-04 21:07:47 +02:00
this . isKeyHeldDown = true ;
if ( this . settings . modifierKey !== ModifierKey . SHIFT && this . settings . modifierKey !== ModifierKey . SHIFT _RIGHT ) { // Ignore shift to allow horizontal scrolling
// Disable the normal scrolling behavior when the key is held down
this . disableScroll ( currentWindow ) ;
}
}
} ) ;
this . registerDomEvent ( doc , "keyup" , ( evt ) => {
if ( evt . code === this . settings . modifierKey . toString ( ) ) {
this . onConfigKeyUp ( currentWindow ) ;
}
} ) ;
this . registerDomEvent ( doc , "wheel" , ( evt ) => {
if ( this . isKeyHeldDown ) {
// When for example using Alt + Tab to switch between windows, the key is still recognized as held down.
// We check if the key is really held down by checking if the key is still pressed in the event when the
// wheel event is triggered.
if ( ! this . isConfiguredKeyDown ( evt ) ) {
this . onConfigKeyUp ( currentWindow ) ;
return ;
}
const eventTarget = evt . target ;
const targetIsCanvas = eventTarget . hasClass ( "canvas-node-content-blocker" ) ;
const targetIsCanvasNode = eventTarget . closest ( ".canvas-node-content" ) !== null ;
const targetIsImage = eventTarget . nodeName === "IMG" ;
if ( targetIsCanvas || targetIsCanvasNode || targetIsImage ) {
this . disableScroll ( currentWindow ) ;
}
2026-03-06 16:34:59 +01:00
if ( targetIsCanvas && this . settings . resizeInCanvas ) {
2025-06-04 21:07:47 +02:00
// seems we're trying to zoom on some canvas node.
this . handleZoomForCanvas ( evt , eventTarget ) ;
}
else if ( targetIsCanvasNode ) ;
else if ( targetIsImage ) {
// Handle the zooming of the image
this . handleZoom ( evt , eventTarget ) ;
}
}
} ) ;
2026-03-06 16:34:59 +01:00
this . registerDomEvent ( currentWindow , "blur" , ( ) => {
// When the window loses focus, ensure scrolling is re-enabled for this window
// and reset the key held state defensively, although the keyup should ideally handle it.
this . isKeyHeldDown = false ;
this . enableScroll ( currentWindow ) ;
} ) ;
2025-06-04 21:07:47 +02:00
}
/ * *
* Handles zooming with the mousewheel on canvas node
* @ param evt wheel event
* @ param eventTarget targeted canvas node element
* @ private
* /
handleZoomForCanvas ( evt , eventTarget ) {
// get active canvas
const isCanvas = this . app . workspace . getActiveViewOfType ( obsidian . View ) . getViewType ( ) === "canvas" ;
if ( ! isCanvas ) {
throw new Error ( "Can't find canvas" ) ;
}
// Unfortunately the current type definitions don't include any canvas functionality...
const canvas = this . app . workspace . getActiveViewOfType ( obsidian . View ) . canvas ;
// get triggered canvasNode
const canvasNode = Array . from ( canvas . nodes . values ( ) )
. find ( node => node . contentBlockerEl == eventTarget ) ;
// Adjust delta based on the direction of the resize
let delta = evt . deltaY > 0 ? this . settings . stepSize : this . settings . stepSize * - 1 ;
// Calculate new dimensions directly using the delta and aspectRatio
const aspectRatio = canvasNode . width / canvasNode . height ;
const newWidth = canvasNode . width + delta ;
const newHeight = newWidth / aspectRatio ;
// Resize the canvas node using the new dimensions
canvasNode . resize ( { width : newWidth , height : newHeight } ) ;
}
/ * *
* Handles zooming with the mousewheel on an image
* @ param evt wheel event
* @ param eventTarget targeted image element
* @ private
* /
handleZoom ( evt , eventTarget ) {
return _ _awaiter ( this , void 0 , void 0 , function * ( ) {
const imageUri = eventTarget . attributes . getNamedItem ( "src" ) . textContent ;
const activeFile = yield this . getActivePaneWithImage ( eventTarget ) ;
2026-03-06 16:34:59 +01:00
yield this . app . vault . process ( activeFile , ( fileText ) => {
let frontmatter = "" ;
let body = fileText ;
const frontmatterRegex = /^---\s*([\s\S]*?)\s*---\n*/ ;
const match = fileText . match ( frontmatterRegex ) ;
if ( match ) {
frontmatter = match [ 0 ] ; // Keep the full matched frontmatter block including delimiters and trailing newline
body = fileText . slice ( frontmatter . length ) ; // The rest is the body
2025-06-04 21:07:47 +02:00
}
2026-03-06 16:34:59 +01:00
const zoomParams = this . getZoomParams ( imageUri , body , eventTarget ) ;
// Perform replacements ONLY on the body
let modifiedBody = body ;
const sizeMatches = body . match ( zoomParams . sizeMatchRegExp ) ;
// Element already has a size entry in the body
if ( sizeMatches !== null ) {
const oldSize = parseInt ( sizeMatches [ 1 ] ) ;
let newSize = oldSize ;
if ( evt . deltaY < 0 ) {
newSize += this . settings . stepSize ;
}
else if ( evt . deltaY > 0 && newSize > this . settings . stepSize ) {
newSize -= this . settings . stepSize ;
}
// Replace within the body
modifiedBody = body . replace ( zoomParams . replaceSizeExist . getReplaceFromString ( oldSize ) , zoomParams . replaceSizeExist . getReplaceWithString ( newSize ) ) ;
2025-06-04 21:07:47 +02:00
}
2026-03-06 16:34:59 +01:00
else { // Element has no size entry in the body -> give it an initial size
const initialSize = this . settings . initialSize ;
const image = new Image ( ) ;
image . src = imageUri ;
const width = image . naturalWidth || initialSize ;
const minWidth = Math . min ( width , initialSize ) ;
// Replace within the body
modifiedBody = body . replace ( zoomParams . replaceSizeNotExist . getReplaceFromString ( 0 ) , zoomParams . replaceSizeNotExist . getReplaceWithString ( minWidth ) ) ;
}
// Combine original frontmatter with the modified body
return frontmatter + modifiedBody ;
} ) ;
2025-06-04 21:07:47 +02:00
} ) ;
}
/ * *
* Loop through all panes and get the pane that hosts a markdown file with the image to zoom
* @ param imageElement The HTML Element of the image
* @ private
* /
getActivePaneWithImage ( imageElement ) {
return _ _awaiter ( this , void 0 , void 0 , function * ( ) {
return new Promise ( ( ( resolve , reject ) => {
this . app . workspace . iterateAllLeaves ( leaf => {
if ( leaf . view . containerEl . contains ( imageElement ) && leaf . view instanceof obsidian . MarkdownView ) {
resolve ( leaf . view . file ) ;
}
} ) ;
reject ( new Error ( "No file belonging to the image found" ) ) ;
} ) ) ;
} ) ;
}
getZoomParams ( imageUri , fileText , target ) {
if ( imageUri . contains ( "http" ) ) {
return Util . getRemoteImageZoomParams ( imageUri , fileText ) ;
}
else if ( target . classList . value . match ( "excalidraw-svg.*" ) ) {
const src = target . attributes . getNamedItem ( "filesource" ) . textContent ;
// remove ".md" from the end of the src
const imageName = src . substring ( 0 , src . length - 3 ) ;
// Only get text after "/"
const imageNameAfterSlash = imageName . substring ( imageName . lastIndexOf ( "/" ) + 1 ) ;
return Util . getLocalImageZoomParams ( imageNameAfterSlash , fileText ) ;
}
else if ( imageUri . contains ( "app://" ) ) {
const imageName = Util . getLocalImageNameFromUri ( imageUri ) ;
return Util . getLocalImageZoomParams ( imageName , fileText ) ;
}
2026-03-06 16:34:59 +01:00
else if ( imageUri . contains ( "data:image/" ) ) { // for image generated by PDF++ extension
// example: data:image/png;base64,iVB...
const imageName = Util . getLocalImageNameFromUri ( target . parentElement . getAttribute ( "src" ) ) ;
return Util . getLocalImageZoomParams ( imageName , fileText ) ;
}
2025-06-04 21:07:47 +02:00
throw new Error ( "Image is not zoomable" ) ;
}
loadSettings ( ) {
return _ _awaiter ( this , void 0 , void 0 , function * ( ) {
this . settings = Object . assign ( { } , DEFAULT _SETTINGS , yield this . loadData ( ) ) ;
} ) ;
}
saveSettings ( ) {
return _ _awaiter ( this , void 0 , void 0 , function * ( ) {
yield this . saveData ( this . settings ) ;
} ) ;
}
// Utilities to disable and enable scrolling //
preventDefault ( ev ) {
ev . preventDefault ( ) ;
}
/ * *
* Disables the normal scroll event
* /
disableScroll ( currentWindow ) {
currentWindow . addEventListener ( this . wheelEvent , this . preventDefault , this . wheelOpt ) ;
}
/ * *
* Enables the normal scroll event
* /
enableScroll ( currentWindow ) {
currentWindow . removeEventListener ( this . wheelEvent , this . preventDefault , this . wheelOpt ) ;
}
isConfiguredKeyDown ( evt ) {
switch ( this . settings . modifierKey ) {
case ModifierKey . ALT :
case ModifierKey . ALT _RIGHT :
return evt . altKey ;
case ModifierKey . CTRL :
case ModifierKey . CTRL _RIGHT :
return evt . ctrlKey ;
case ModifierKey . SHIFT :
case ModifierKey . SHIFT _RIGHT :
return evt . shiftKey ;
}
}
}
class MouseWheelZoomSettingsTab extends obsidian . PluginSettingTab {
constructor ( app , plugin ) {
super ( app , plugin ) ;
this . plugin = plugin ;
}
2026-03-06 16:34:59 +01:00
// Helper function to update the warning message
updateWarningMessage ( modifierKey , resizeInCanvas ) {
if ( ! this . warningEl )
return ;
const isCtrl = modifierKey === ModifierKey . CTRL || modifierKey === ModifierKey . CTRL _RIGHT ;
const conflict = isCtrl && resizeInCanvas ;
if ( conflict ) {
this . warningEl . setText ( CtrlCanvasConflictWarning ) ;
this . warningEl . style . display = 'block' ;
this . warningEl . style . color = 'var(--text-warning)' ;
this . warningEl . style . marginTop = '10px' ;
}
else {
this . warningEl . setText ( "" ) ;
this . warningEl . style . display = 'none' ;
}
}
2025-06-04 21:07:47 +02:00
display ( ) {
let { containerEl } = this ;
containerEl . empty ( ) ;
containerEl . createEl ( 'h2' , { text : 'Settings for mousewheel zoom' } ) ;
new obsidian . Setting ( containerEl )
. setName ( 'Trigger Key' )
. setDesc ( 'Key that needs to be pressed down for mousewheel zoom to work.' )
. addDropdown ( dropdown => dropdown
. addOption ( ModifierKey . CTRL , "Ctrl" )
. addOption ( ModifierKey . ALT , "Alt" )
. addOption ( ModifierKey . SHIFT , "Shift" )
. addOption ( ModifierKey . CTRL _RIGHT , "Right Ctrl" )
. addOption ( ModifierKey . ALT _RIGHT , "Right Alt" )
. addOption ( ModifierKey . SHIFT _RIGHT , "Right Shift" )
. setValue ( this . plugin . settings . modifierKey )
. onChange ( ( value ) => _ _awaiter ( this , void 0 , void 0 , function * ( ) {
this . plugin . settings . modifierKey = value ;
2026-03-06 16:34:59 +01:00
this . updateWarningMessage ( this . plugin . settings . modifierKey , this . plugin . settings . resizeInCanvas ) ;
2025-06-04 21:07:47 +02:00
yield this . plugin . saveSettings ( ) ;
} ) ) ) ;
new obsidian . Setting ( containerEl )
. setName ( 'Step size' )
. setDesc ( 'Step value by which the size of the image should be increased/decreased' )
. addSlider ( slider => {
slider
. setValue ( 25 )
. setLimits ( 0 , 100 , 1 )
. setDynamicTooltip ( )
. setValue ( this . plugin . settings . stepSize )
. onChange ( ( value ) => _ _awaiter ( this , void 0 , void 0 , function * ( ) {
this . plugin . settings . stepSize = value ;
yield this . plugin . saveSettings ( ) ;
} ) ) ;
} ) ;
new obsidian . Setting ( containerEl )
. setName ( 'Initial Size' )
. setDesc ( 'Initial image size if no size was defined beforehand' )
. addSlider ( slider => {
slider
. setValue ( 500 )
. setLimits ( 0 , 1000 , 25 )
. setDynamicTooltip ( )
. setValue ( this . plugin . settings . initialSize )
. onChange ( ( value ) => _ _awaiter ( this , void 0 , void 0 , function * ( ) {
this . plugin . settings . initialSize = value ;
yield this . plugin . saveSettings ( ) ;
} ) ) ;
} ) ;
2026-03-06 16:34:59 +01:00
new obsidian . Setting ( containerEl )
. setName ( 'Resize in canvas' )
. setDesc ( 'When enabled, all nodes on the Obsidian canvas can also be resized using the Modifier key' )
. addToggle ( ( toggle ) => {
toggle . setValue ( this . plugin . settings . resizeInCanvas )
. onChange ( ( value ) => _ _awaiter ( this , void 0 , void 0 , function * ( ) {
this . plugin . settings . resizeInCanvas = value ;
this . updateWarningMessage ( this . plugin . settings . modifierKey , value ) ;
yield this . plugin . saveSettings ( ) ;
} ) ) ;
} ) ;
this . warningEl = containerEl . createDiv ( { cls : 'mousewheel-zoom-warning' } ) ;
this . warningEl . style . display = 'none' ;
this . updateWarningMessage ( this . plugin . settings . modifierKey , this . plugin . settings . resizeInCanvas ) ;
2025-06-04 21:07:47 +02:00
}
}
module . exports = MouseWheelZoomPlugin ;
/* nosourcemap */