'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 .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/* global Reflect, Promise */
var extendStatics = function ( d , b ) {
extendStatics = Object . setPrototypeOf ||
( { _ _proto _ _ : [ ] } instanceof Array && function ( d , b ) { d . _ _proto _ _ = b ; } ) ||
function ( d , b ) { for ( var p in b ) if ( Object . prototype . hasOwnProperty . call ( b , p ) ) d [ p ] = b [ p ] ; } ;
return extendStatics ( d , b ) ;
} ;
function _ _extends ( d , b ) {
if ( typeof b !== "function" && b !== null )
throw new TypeError ( "Class extends value " + String ( b ) + " is not a constructor or null" ) ;
extendStatics ( d , b ) ;
function _ _ ( ) { this . constructor = d ; }
d . prototype = b === null ? Object . create ( b ) : ( _ _ . prototype = b . prototype , new _ _ ( ) ) ;
}
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 ( ) ) ;
} ) ;
}
function _ _generator ( thisArg , body ) {
var _ = { label : 0 , sent : function ( ) { if ( t [ 0 ] & 1 ) throw t [ 1 ] ; return t [ 1 ] ; } , trys : [ ] , ops : [ ] } , f , y , t , g ;
return g = { next : verb ( 0 ) , "throw" : verb ( 1 ) , "return" : verb ( 2 ) } , typeof Symbol === "function" && ( g [ Symbol . iterator ] = function ( ) { return this ; } ) , g ;
function verb ( n ) { return function ( v ) { return step ( [ n , v ] ) ; } ; }
function step ( op ) {
if ( f ) throw new TypeError ( "Generator is already executing." ) ;
while ( _ ) try {
if ( f = 1 , y && ( t = op [ 0 ] & 2 ? y [ "return" ] : op [ 0 ] ? y [ "throw" ] || ( ( t = y [ "return" ] ) && t . call ( y ) , 0 ) : y . next ) && ! ( t = t . call ( y , op [ 1 ] ) ) . done ) return t ;
if ( y = 0 , t ) op = [ op [ 0 ] & 2 , t . value ] ;
switch ( op [ 0 ] ) {
case 0 : case 1 : t = op ; break ;
case 4 : _ . label ++ ; return { value : op [ 1 ] , done : false } ;
case 5 : _ . label ++ ; y = op [ 1 ] ; op = [ 0 ] ; continue ;
case 7 : op = _ . ops . pop ( ) ; _ . trys . pop ( ) ; continue ;
default :
if ( ! ( t = _ . trys , t = t . length > 0 && t [ t . length - 1 ] ) && ( op [ 0 ] === 6 || op [ 0 ] === 2 ) ) { _ = 0 ; continue ; }
if ( op [ 0 ] === 3 && ( ! t || ( op [ 1 ] > t [ 0 ] && op [ 1 ] < t [ 3 ] ) ) ) { _ . label = op [ 1 ] ; break ; }
if ( op [ 0 ] === 6 && _ . label < t [ 1 ] ) { _ . label = t [ 1 ] ; t = op ; break ; }
if ( t && _ . label < t [ 2 ] ) { _ . label = t [ 2 ] ; _ . ops . push ( op ) ; break ; }
if ( t [ 2 ] ) _ . ops . pop ( ) ;
_ . trys . pop ( ) ; continue ;
}
op = body . call ( thisArg , _ ) ;
} catch ( e ) { op = [ 6 , e ] ; y = 0 ; } finally { f = t = 0 ; }
if ( op [ 0 ] & 5 ) throw op [ 1 ] ; return { value : op [ 0 ] ? op [ 1 ] : void 0 , done : true } ;
}
}
/** @deprecated */
function _ _spreadArrays ( ) {
for ( var s = 0 , i = 0 , il = arguments . length ; i < il ; i ++ ) s += arguments [ i ] . length ;
for ( var r = Array ( s ) , k = 0 , i = 0 ; i < il ; i ++ )
for ( var a = arguments [ i ] , j = 0 , jl = a . length ; j < jl ; j ++ , k ++ )
r [ k ] = a [ j ] ;
return r ;
}
var DeleteFilesModal = /** @class */ ( function ( _super ) {
_ _extends ( DeleteFilesModal , _super ) ;
function DeleteFilesModal ( app , filesToDelete ) {
var _this = _super . call ( this , app ) || this ;
_this . filesToDelete = filesToDelete ;
return _this ;
}
DeleteFilesModal . prototype . onOpen = function ( ) {
var _this = this ;
var _a = this , contentEl = _a . contentEl , titleEl = _a . titleEl ;
titleEl . setText ( 'Move ' + this . filesToDelete . length + ' files to system trash?' ) ;
contentEl
. createEl ( "button" , { text : "Cancel" } )
. addEventListener ( "click" , function ( ) { return _this . close ( ) ; } ) ;
contentEl
. setAttr ( "margin" , "auto" ) ;
contentEl
. createEl ( "button" , {
cls : "mod-cta" ,
text : "Confirm"
} )
. addEventListener ( "click" , function ( ) { return _ _awaiter ( _this , void 0 , void 0 , function ( ) {
var _i , _a , file ;
return _ _generator ( this , function ( _b ) {
switch ( _b . label ) {
case 0 :
_i = 0 , _a = this . filesToDelete ;
_b . label = 1 ;
case 1 :
if ( ! ( _i < _a . length ) ) return [ 3 /*break*/ , 4 ] ;
file = _a [ _i ] ;
return [ 4 /*yield*/ , this . app . vault . trash ( file , true ) ] ;
case 2 :
_b . sent ( ) ;
_b . label = 3 ;
case 3 :
_i ++ ;
return [ 3 /*break*/ , 1 ] ;
case 4 :
this . close ( ) ;
return [ 2 /*return*/ ] ;
}
} ) ;
} ) ; } ) ;
} ;
DeleteFilesModal . prototype . onClose = function ( ) {
var contentEl = this . contentEl ;
contentEl . empty ( ) ;
} ;
return DeleteFilesModal ;
} ( obsidian . Modal ) ) ;
var SettingsTab = /** @class */ ( function ( _super ) {
_ _extends ( SettingsTab , _super ) ;
function SettingsTab ( app , plugin , defaultSettings ) {
var _this = _super . call ( this , app , plugin ) || this ;
_this . defaultSettings = defaultSettings ;
_this . plugin = plugin ;
return _this ;
}
// Add trailing slash to catch files named like the directory. See https://github.com/Vinzent03/find-unlinked-files/issues/24
SettingsTab . prototype . formatPath = function ( path , addDirectorySlash ) {
if ( path . length == 0 )
return path ;
path = obsidian . normalizePath ( path ) ;
if ( addDirectorySlash )
return path + "/" ;
else
return path ;
} ;
SettingsTab . prototype . display = function ( ) {
var _this = this ;
var containerEl = this . containerEl ;
containerEl . empty ( ) ;
containerEl . createEl ( "h2" , { text : this . plugin . manifest . name } ) ;
containerEl . createEl ( "h4" , { text : "Settings for finding orphaned files" } ) ;
new obsidian . Setting ( containerEl )
. setName ( "Open output file" )
. addToggle ( function ( cb ) {
return cb . setValue ( _this . plugin . settings . openOutputFile )
. onChange ( function ( value ) {
_this . plugin . settings . openOutputFile = value ;
_this . plugin . saveSettings ( ) ;
} ) ;
} ) ;
new obsidian . Setting ( containerEl )
. setName ( 'Output file name' )
. setDesc ( 'Set name of output file (without file extension). Make sure no file exists with this name because it will be overwritten! If the name is empty, the default name is set.' )
. addText ( function ( cb ) { return cb . onChange ( function ( value ) {
if ( value . length == 0 ) {
_this . plugin . settings . outputFileName = _this . defaultSettings . outputFileName ;
}
else {
_this . plugin . settings . outputFileName = value ;
}
_this . plugin . saveSettings ( ) ;
} ) . setValue ( _this . plugin . settings . outputFileName ) ; } ) ;
new obsidian . Setting ( containerEl )
. setName ( 'Disable working links' )
. setDesc ( 'Indent lines to disable the link and to clean up the graph view' )
. addToggle ( function ( cb ) { return cb . onChange ( function ( value ) {
_this . plugin . settings . disableWorkingLinks = value ;
_this . plugin . saveSettings ( ) ;
} ) . setValue ( _this . plugin . settings . disableWorkingLinks ) ; } ) ;
new obsidian . Setting ( containerEl )
. setName ( "Exclude files in the given directories" )
. setDesc ( "Enable to exclude files in the given directories. Disable to only include files in the given directories" )
. addToggle ( function ( cb ) {
return cb . setValue ( _this . plugin . settings . ignoreDirectories )
. onChange ( function ( value ) {
_this . plugin . settings . ignoreDirectories = value ;
_this . plugin . saveSettings ( ) ;
} ) ;
} ) ;
new obsidian . Setting ( containerEl )
. setName ( "Directories" )
. setDesc ( "Add each directory path in a new line" )
. addTextArea ( function ( cb ) { return cb
. setPlaceholder ( "Directory/Subdirectory" )
. setValue ( _this . plugin . settings . directoriesToIgnore . join ( "\n" ) )
. onChange ( function ( value ) {
var paths = value . trim ( ) . split ( "\n" ) . map ( function ( value ) { return _this . formatPath ( value , true ) ; } ) ;
_this . plugin . settings . directoriesToIgnore = paths ;
_this . plugin . saveSettings ( ) ;
} ) ; } ) ;
new obsidian . Setting ( containerEl )
. setName ( "Exclude files" )
. setDesc ( "Add each file path in a new line (with file extension!)" )
. addTextArea ( function ( cb ) { return cb
. setPlaceholder ( "Directory/file.md" )
. setValue ( _this . plugin . settings . filesToIgnore . join ( "\n" ) )
. onChange ( function ( value ) {
var paths = value . trim ( ) . split ( "\n" ) . map ( function ( value ) { return _this . formatPath ( value , false ) ; } ) ;
_this . plugin . settings . filesToIgnore = paths ;
_this . plugin . saveSettings ( ) ;
} ) ; } ) ;
new obsidian . Setting ( containerEl )
. setName ( "Exclude links" )
. setDesc ( "Exclude files, which contain the given file as link. Add each file path in a new line (with file extension!). Set it to `*` to exclude files with links." )
. addTextArea ( function ( cb ) { return cb
. setPlaceholder ( "Directory/file.md" )
. setValue ( _this . plugin . settings . linksToIgnore . join ( "\n" ) )
. onChange ( function ( value ) {
var paths = value . trim ( ) . split ( "\n" ) . map ( function ( value ) { return _this . formatPath ( value , false ) ; } ) ;
_this . plugin . settings . linksToIgnore = paths ;
_this . plugin . saveSettings ( ) ;
} ) ; } ) ;
new obsidian . Setting ( containerEl )
. setName ( "Exclude files with the given filetypes" )
. setDesc ( "Enable to exclude files with the given filetypes. Disable to only include files with the given filetypes" )
. addToggle ( function ( cb ) {
return cb . setValue ( _this . plugin . settings . ignoreFileTypes )
. onChange ( function ( value ) {
_this . plugin . settings . ignoreFileTypes = value ;
_this . plugin . saveSettings ( ) ;
} ) ;
} ) ;
new obsidian . Setting ( containerEl )
. setName ( "File types" )
. setDesc ( "Effect depends on toggle above" )
. addTextArea ( function ( cb ) { return cb
. setPlaceholder ( "docx,txt" )
. setValue ( _this . plugin . settings . fileTypesToIgnore . join ( "," ) )
. onChange ( function ( value ) {
var extensions = value . trim ( ) . split ( "," ) ;
_this . plugin . settings . fileTypesToIgnore = extensions ;
_this . plugin . saveSettings ( ) ;
} ) ; } ) ;
new obsidian . Setting ( containerEl )
. setName ( "Exclude tags" )
. setDesc ( "Exclude files, which contain the given tag. Add each tag separated by comma (without `#`)" )
. addTextArea ( function ( cb ) { return cb
. setPlaceholder ( "todo,unfinished" )
. setValue ( _this . plugin . settings . tagsToIgnore . join ( "," ) )
. onChange ( function ( value ) {
var tags = value . trim ( ) . split ( "," ) ;
_this . plugin . settings . tagsToIgnore = tags ;
_this . plugin . saveSettings ( ) ;
} ) ; } ) ;
new obsidian . Setting ( containerEl )
. setName ( "Filetypes to delete per command. See README." )
. setDesc ( "Add each filetype separated by comma. Set to `*` to delete all files." )
. addTextArea ( function ( cb ) { return cb
. setPlaceholder ( "jpg,png" )
. setValue ( _this . plugin . settings . fileTypesToDelete . join ( "," ) )
. onChange ( function ( value ) {
var extensions = value . trim ( ) . split ( "," ) ;
_this . plugin . settings . fileTypesToDelete = extensions ;
_this . plugin . saveSettings ( ) ;
} ) ; } ) ;
/// Settings for find brokenLinks
containerEl . createEl ( "h4" , { text : "Settings for finding broken links" } ) ;
new obsidian . Setting ( containerEl )
. setName ( 'Output file name' )
. setDesc ( 'Set name of output file (without file extension). Make sure no file exists with this name because it will be overwritten! If the name is empty, the default name is set.' )
. addText ( function ( cb ) { return cb . onChange ( function ( value ) {
if ( value . length == 0 ) {
_this . plugin . settings . unresolvedLinksOutputFileName = _this . defaultSettings . unresolvedLinksOutputFileName ;
}
else {
_this . plugin . settings . unresolvedLinksOutputFileName = value ;
}
_this . plugin . saveSettings ( ) ;
} ) . setValue ( _this . plugin . settings . unresolvedLinksOutputFileName ) ; } ) ;
new obsidian . Setting ( containerEl )
. setName ( "Exclude files in the given directories" )
. setDesc ( "Enable to exclude files in the given directories. Disable to only include files in the given directories" )
. addToggle ( function ( cb ) {
return cb . setValue ( _this . plugin . settings . unresolvedLinksIgnoreDirectories )
. onChange ( function ( value ) {
_this . plugin . settings . unresolvedLinksIgnoreDirectories = value ;
_this . plugin . saveSettings ( ) ;
} ) ;
} ) ;
new obsidian . Setting ( containerEl )
. setName ( "Directories" )
. setDesc ( "Add each directory path in a new line" )
. addTextArea ( function ( cb ) { return cb
. setPlaceholder ( "Directory/Subdirectory" )
. setValue ( _this . plugin . settings . unresolvedLinksDirectoriesToIgnore . join ( "\n" ) )
. onChange ( function ( value ) {
var paths = value . trim ( ) . split ( "\n" ) . map ( function ( value ) { return _this . formatPath ( value , true ) ; } ) ;
_this . plugin . settings . unresolvedLinksDirectoriesToIgnore = paths ;
_this . plugin . saveSettings ( ) ;
} ) ; } ) ;
new obsidian . Setting ( containerEl )
. setName ( "Exclude files" )
. setDesc ( "Exclude links in the specified file. Add each file path in a new line (with file extension!)" )
. addTextArea ( function ( cb ) { return cb
. setPlaceholder ( "Directory/file.md" )
. setValue ( _this . plugin . settings . unresolvedLinksFilesToIgnore . join ( "\n" ) )
. onChange ( function ( value ) {
var paths = value . trim ( ) . split ( "\n" ) . map ( function ( value ) { return _this . formatPath ( value , false ) ; } ) ;
_this . plugin . settings . unresolvedLinksFilesToIgnore = paths ;
_this . plugin . saveSettings ( ) ;
} ) ; } ) ;
new obsidian . Setting ( containerEl )
. setName ( "Exclude links" )
. setDesc ( "Exclude files, which contain the given file as link. Add each file path in a new line (with file extension!). Set it to `*` to exclude files with links." )
. addTextArea ( function ( cb ) { return cb
. setPlaceholder ( "Directory/file.md" )
. setValue ( _this . plugin . settings . unresolvedLinksLinksToIgnore . join ( "\n" ) )
. onChange ( function ( value ) {
var paths = value . trim ( ) . split ( "\n" ) . map ( function ( value ) { return _this . formatPath ( value , false ) ; } ) ;
_this . plugin . settings . unresolvedLinksLinksToIgnore = paths ;
_this . plugin . saveSettings ( ) ;
} ) ; } ) ;
new obsidian . Setting ( containerEl )
. setName ( "Exclude filetypes" )
. setDesc ( "Exclude links with the specified filetype. Add each filetype separated by comma" )
. addTextArea ( function ( cb ) { return cb
. setPlaceholder ( "docx,txt" )
. setValue ( _this . plugin . settings . unresolvedLinksFileTypesToIgnore . join ( "," ) )
. onChange ( function ( value ) {
var extensions = value . trim ( ) . split ( "," ) ;
_this . plugin . settings . unresolvedLinksFileTypesToIgnore = extensions ;
_this . plugin . saveSettings ( ) ;
} ) ; } ) ;
new obsidian . Setting ( containerEl )
. setName ( "Exclude tags" )
. setDesc ( "Exclude links in files, which contain the given tag. Add each tag separated by comma (without `#`)" )
. addTextArea ( function ( cb ) { return cb
. setPlaceholder ( "todo,unfinished" )
. setValue ( _this . plugin . settings . unresolvedLinksTagsToIgnore . join ( "," ) )
. onChange ( function ( value ) {
var tags = value . trim ( ) . split ( "," ) ;
_this . plugin . settings . unresolvedLinksTagsToIgnore = tags ;
_this . plugin . saveSettings ( ) ;
} ) ; } ) ;
containerEl . createEl ( "h4" , { text : "Settings for finding files without tags" } ) ;
new obsidian . Setting ( containerEl )
. setName ( 'Output file name' )
. setDesc ( 'Set name of output file (without file extension). Make sure no file exists with this name because it will be overwritten! If the name is empty, the default name is set.' )
. addText ( function ( cb ) { return cb . onChange ( function ( value ) {
if ( value . length == 0 ) {
_this . plugin . settings . withoutTagsOutputFileName = _this . defaultSettings . withoutTagsOutputFileName ;
}
else {
_this . plugin . settings . withoutTagsOutputFileName = value ;
}
_this . plugin . saveSettings ( ) ;
} ) . setValue ( _this . plugin . settings . withoutTagsOutputFileName ) ; } ) ;
new obsidian . Setting ( containerEl )
. setName ( "Exclude files" )
. setDesc ( "Exclude the specific files. Add each file path in a new line (with file extension!)" )
. addTextArea ( function ( cb ) { return cb
. setPlaceholder ( "Directory/file.md" )
. setValue ( _this . plugin . settings . withoutTagsFilesToIgnore . join ( "\n" ) )
. onChange ( function ( value ) {
var paths = value . trim ( ) . split ( "\n" ) . map ( function ( value ) { return _this . formatPath ( value , false ) ; } ) ;
_this . plugin . settings . withoutTagsFilesToIgnore = paths ;
_this . plugin . saveSettings ( ) ;
} ) ; } ) ;
new obsidian . Setting ( containerEl )
. setName ( "Exclude directories" )
. setDesc ( "Exclude files in the specified directories. Add each directory path in a new line" )
. addTextArea ( function ( cb ) { return cb
. setPlaceholder ( "Directory/Subdirectory" )
. setValue ( _this . plugin . settings . withoutTagsDirectoriesToIgnore . join ( "\n" ) )
. onChange ( function ( value ) {
var paths = value . trim ( ) . split ( "\n" ) . map ( function ( value ) { return _this . formatPath ( value , true ) ; } ) ;
_this . plugin . settings . withoutTagsDirectoriesToIgnore = paths ;
_this . plugin . saveSettings ( ) ;
} ) ; } ) ;
/// Settings for empty files
containerEl . createEl ( "h4" , { text : "Settings for finding empty files" } ) ;
new obsidian . Setting ( containerEl )
. setName ( "Exclude files in the given directories" )
. setDesc ( "Enable to exclude files in the given directories. Disable to only include files in the given directories" )
. addToggle ( function ( cb ) {
return cb . setValue ( _this . plugin . settings . emptyFilesIgnoreDirectories )
. onChange ( function ( value ) {
_this . plugin . settings . emptyFilesIgnoreDirectories = value ;
_this . plugin . saveSettings ( ) ;
} ) ;
} ) ;
new obsidian . Setting ( containerEl )
. setName ( "Directories" )
. setDesc ( "Add each directory path in a new line" )
. addTextArea ( function ( cb ) { return cb
. setPlaceholder ( "Directory/Subdirectory" )
. setValue ( _this . plugin . settings . emptyFilesDirectories . join ( "\n" ) )
. onChange ( function ( value ) {
var paths = value . trim ( ) . split ( "\n" ) . map ( function ( value ) { return _this . formatPath ( value , true ) ; } ) ;
_this . plugin . settings . emptyFilesDirectories = paths ;
_this . plugin . saveSettings ( ) ;
} ) ; } ) ;
new obsidian . Setting ( containerEl )
. setName ( "Exclude files" )
. setDesc ( "Add each file path in a new line (with file extension!)" )
. addTextArea ( function ( cb ) { return cb
. setPlaceholder ( "Directory/file.md" )
. setValue ( _this . plugin . settings . emptyFilesFilesToIgnore . join ( "\n" ) )
. onChange ( function ( value ) {
var paths = value . trim ( ) . split ( "\n" ) . map ( function ( value ) { return _this . formatPath ( value , false ) ; } ) ;
_this . plugin . settings . emptyFilesFilesToIgnore = paths ;
_this . plugin . saveSettings ( ) ;
} ) ; } ) ;
new obsidian . Setting ( containerEl )
. setName ( 'Donate' )
. setDesc ( 'If you like this Plugin, consider donating to support continued development.' )
. addButton ( function ( bt ) {
bt . buttonEl . outerHTML = "<a href='https://ko-fi.com/F1F195IQ5' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://cdn.ko-fi.com/cdn/kofi3.png?v=3' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>" ;
} ) ;
} ;
return SettingsTab ;
} ( obsidian . PluginSettingTab ) ) ;
var Utils = /** @class */ ( function ( ) {
/ * *
* Checks for the given settings . Is used for ` Find orphaned files ` and ` Find broken links `
* @ param app
* @ param filePath
* @ param tagsToIgnore
* @ param linksToIgnore
* @ param directoriesToIgnore
* @ param filesToIgnore
* @ param ignoreDirectories
* /
function Utils ( app , filePath , tagsToIgnore , linksToIgnore , directoriesToIgnore , filesToIgnore , ignoreDirectories , dir ) {
if ( ignoreDirectories === void 0 ) { ignoreDirectories = true ; }
this . app = app ;
this . filePath = filePath ;
this . tagsToIgnore = tagsToIgnore ;
this . linksToIgnore = linksToIgnore ;
this . directoriesToIgnore = directoriesToIgnore ;
this . filesToIgnore = filesToIgnore ;
this . ignoreDirectories = ignoreDirectories ;
this . dir = dir ;
this . fileCache = app . metadataCache . getCache ( filePath ) ;
}
Utils . prototype . hasTagsToIgnore = function ( ) {
var _this = this ;
var tags = obsidian . getAllTags ( this . fileCache ) ;
return ( tags === null || tags === void 0 ? void 0 : tags . find ( function ( tag ) { return _this . tagsToIgnore . contains ( tag . substring ( 1 ) ) ; } ) ) !== undefined ;
} ;
Utils . prototype . hasLinksToIgnore = function ( ) {
var _this = this ;
var _a , _b ;
if ( ( ( ( _a = this . fileCache ) === null || _a === void 0 ? void 0 : _a . embeds ) != null || ( ( _b = this . fileCache ) === null || _b === void 0 ? void 0 : _b . links ) != null ) && this . linksToIgnore [ 0 ] == "*" ) {
return true ;
}
return obsidian . iterateCacheRefs ( this . fileCache , function ( cb ) {
var _a ;
var link = ( _a = _this . app . metadataCache . getFirstLinkpathDest ( cb . link , _this . filePath ) ) === null || _a === void 0 ? void 0 : _a . path ;
return _this . linksToIgnore . contains ( link ) ;
} ) ;
} ;
Utils . prototype . checkDirectory = function ( ) {
var _this = this ;
if ( this . dir ) {
if ( ! this . filePath . startsWith ( this . dir ) ) {
return true ;
}
}
var contains = this . directoriesToIgnore . find ( function ( value ) { return value . length != 0 && _this . filePath . startsWith ( value ) ; } ) !== undefined ;
if ( this . ignoreDirectories ) {
return contains ;
}
else {
return ! contains ;
}
} ;
Utils . prototype . isFileToIgnore = function ( ) {
return this . filesToIgnore . contains ( this . filePath ) ;
} ;
Utils . prototype . isValid = function ( ) {
return ! this . hasTagsToIgnore ( ) && ! this . hasLinksToIgnore ( ) && ! this . checkDirectory ( ) && ! this . isFileToIgnore ( ) ;
} ;
/ * *
* Writes the text to the file and opens the file in a new pane if it is not opened yet
* @ param app
* @ param outputFileName name of the output file
* @ param text data to be written to the file
* /
Utils . writeAndOpenFile = function ( app , outputFileName , text , openFile ) {
return _ _awaiter ( this , void 0 , void 0 , function ( ) {
var fileIsAlreadyOpened , newPane , file ;
return _ _generator ( this , function ( _a ) {
switch ( _a . label ) {
case 0 : return [ 4 /*yield*/ , app . vault . adapter . write ( outputFileName , text ) ] ;
case 1 :
_a . sent ( ) ;
if ( ! openFile )
return [ 2 /*return*/ ] ;
fileIsAlreadyOpened = false ;
app . workspace . iterateAllLeaves ( function ( leaf ) {
if ( leaf . getDisplayText ( ) != "" && outputFileName . startsWith ( leaf . getDisplayText ( ) ) ) {
fileIsAlreadyOpened = true ;
}
} ) ;
if ( ! ! fileIsAlreadyOpened ) return [ 3 /*break*/ , 5 ] ;
newPane = app . workspace . getLeavesOfType ( "empty" ) . length == 0 ;
if ( ! newPane ) return [ 3 /*break*/ , 2 ] ;
app . workspace . openLinkText ( outputFileName , "/" , true ) ;
return [ 3 /*break*/ , 5 ] ;
case 2 :
file = app . vault . getAbstractFileByPath ( outputFileName ) ;
if ( ! ( file instanceof obsidian . TFile ) ) return [ 3 /*break*/ , 4 ] ;
return [ 4 /*yield*/ , app . workspace . getLeavesOfType ( "empty" ) [ 0 ] . openFile ( file ) ] ;
case 3 :
_a . sent ( ) ;
return [ 3 /*break*/ , 5 ] ;
case 4 :
app . workspace . openLinkText ( outputFileName , "/" , true ) ;
_a . label = 5 ;
case 5 : return [ 2 /*return*/ ] ;
}
} ) ;
} ) ;
} ;
return Utils ;
} ( ) ) ;
var DEFAULT _SETTINGS = {
outputFileName : "orphaned files output" ,
disableWorkingLinks : false ,
directoriesToIgnore : [ ] ,
filesToIgnore : [ ] ,
fileTypesToIgnore : [ ] ,
linksToIgnore : [ ] ,
tagsToIgnore : [ ] ,
fileTypesToDelete : [ ] ,
ignoreFileTypes : true ,
ignoreDirectories : true ,
unresolvedLinksIgnoreDirectories : true ,
unresolvedLinksOutputFileName : "broken links output" ,
unresolvedLinksDirectoriesToIgnore : [ ] ,
unresolvedLinksFilesToIgnore : [ ] ,
unresolvedLinksFileTypesToIgnore : [ ] ,
unresolvedLinksLinksToIgnore : [ ] ,
unresolvedLinksTagsToIgnore : [ ] ,
withoutTagsDirectoriesToIgnore : [ ] ,
withoutTagsFilesToIgnore : [ ] ,
withoutTagsOutputFileName : "files without tags" ,
emptyFilesOutputFileName : "empty files" ,
emptyFilesDirectories : [ ] ,
emptyFilesFilesToIgnore : [ ] ,
emptyFilesIgnoreDirectories : true ,
openOutputFile : true ,
} ;
var FindOrphanedFilesPlugin = /** @class */ ( function ( _super ) {
_ _extends ( FindOrphanedFilesPlugin , _super ) ;
function FindOrphanedFilesPlugin ( ) {
var _this = _super !== null && _super . apply ( this , arguments ) || this ;
_this . findExtensionRegex = /(\.[^.]+)$/ ;
return _this ;
}
FindOrphanedFilesPlugin . prototype . onload = function ( ) {
return _ _awaiter ( this , void 0 , void 0 , function ( ) {
var _this = this ;
return _ _generator ( this , function ( _a ) {
switch ( _a . label ) {
case 0 :
console . log ( 'loading ' + this . manifest . name + " plugin" ) ;
return [ 4 /*yield*/ , this . loadSettings ( ) ] ;
case 1 :
_a . sent ( ) ;
this . addCommand ( {
id : 'find-unlinked-files' ,
name : 'Find orphaned files' ,
callback : function ( ) { return _this . findOrphanedFiles ( ) ; } ,
} ) ;
this . addCommand ( {
id : 'find-unresolved-link' ,
name : 'Find broken links' ,
callback : function ( ) { return _this . findBrokenLinks ( ) ; } ,
} ) ;
this . addCommand ( {
id : "delete-unlinked-files" ,
name : "Delete orphaned files with certain extension. See README" ,
callback : function ( ) { return _this . deleteOrphanedFiles ( ) ; }
} ) ;
this . addCommand ( {
id : "create-files-of-broken-links" ,
name : "Create files of broken links" ,
callback : function ( ) { return _this . createFilesOfBrokenLinks ( ) ; }
} ) ;
this . addCommand ( {
id : "find-files-without-tags" ,
name : "Find files without tags" ,
callback : function ( ) { return _this . findFilesWithoutTags ( ) ; }
} ) ;
this . addCommand ( {
id : "find-empty-files" ,
name : "Find empty files" ,
callback : function ( ) { return _this . findEmptyFiles ( ) ; }
} ) ;
this . addCommand ( {
id : "delete-empty-files" ,
name : "Delete empty files" ,
callback : function ( ) { return _this . deleteEmptyFiles ( ) ; }
} ) ;
this . addSettingTab ( new SettingsTab ( this . app , this , DEFAULT _SETTINGS ) ) ;
this . app . workspace . on ( "file-menu" , function ( menu , file , source , leaf ) {
if ( file instanceof obsidian . TFolder ) {
menu . addItem ( function ( cb ) {
cb . setIcon ( "search" ) ;
cb . setTitle ( "Find orphaned files" ) ;
// Add trailing slash to catch files named like the directory. See https://github.com/Vinzent03/find-unlinked-files/issues/24
cb . onClick ( function ( e ) { _this . findOrphanedFiles ( file . path + "/" ) ; } ) ;
} ) ;
}
} ) ;
return [ 2 /*return*/ ] ;
}
} ) ;
} ) ;
} ;
FindOrphanedFilesPlugin . prototype . createFilesOfBrokenLinks = function ( ) {
var _a , _b ;
return _ _awaiter ( this , void 0 , void 0 , function ( ) {
var links , filesToCreate , _i , links _1 , link , file , foundType , _c , filesToCreate _1 , file ;
return _ _generator ( this , function ( _d ) {
switch ( _d . label ) {
case 0 : return [ 4 /*yield*/ , this . app . vault . adapter . exists ( this . settings . unresolvedLinksOutputFileName + ".md" ) ] ;
case 1 :
if ( ! ( _d . sent ( ) ) ) {
new obsidian . Notice ( "Can't find file - Please run the `Find broken files' command before" ) ;
return [ 2 /*return*/ ] ;
}
links = ( _a = this . app . metadataCache . getCache ( this . settings . unresolvedLinksOutputFileName + ".md" ) ) === null || _a === void 0 ? void 0 : _a . links ;
if ( ! links ) {
new obsidian . Notice ( "No broken links found" ) ;
return [ 2 /*return*/ ] ;
}
filesToCreate = [ ] ;
for ( _i = 0 , links _1 = links ; _i < links _1 . length ; _i ++ ) {
link = links _1 [ _i ] ;
file = this . app . metadataCache . getFirstLinkpathDest ( link . link , "/" ) ;
if ( file )
continue ;
foundType = ( _b = this . findExtensionRegex . exec ( link . link ) ) === null || _b === void 0 ? void 0 : _b [ 0 ] ;
if ( ( foundType !== null && foundType !== void 0 ? foundType : ".md" ) == ".md" ) {
if ( foundType ) {
filesToCreate . push ( link . link ) ;
}
else {
filesToCreate . push ( link . link + ".md" ) ;
}
}
}
if ( ! filesToCreate ) return [ 3 /*break*/ , 5 ] ;
_c = 0 , filesToCreate _1 = filesToCreate ;
_d . label = 2 ;
case 2 :
if ( ! ( _c < filesToCreate _1 . length ) ) return [ 3 /*break*/ , 5 ] ;
file = filesToCreate _1 [ _c ] ;
return [ 4 /*yield*/ , this . app . vault . create ( file , "" ) ] ;
case 3 :
_d . sent ( ) ;
_d . label = 4 ;
case 4 :
_c ++ ;
return [ 3 /*break*/ , 2 ] ;
case 5 : return [ 2 /*return*/ ] ;
}
} ) ;
} ) ;
} ;
FindOrphanedFilesPlugin . prototype . findEmptyFiles = function ( ) {
return _ _awaiter ( this , void 0 , void 0 , function ( ) {
var files , emptyFiles , _i , files _1 , file , content , trimmedContent , cache , frontmatter , lines , prefix , text ;
return _ _generator ( this , function ( _a ) {
switch ( _a . label ) {
case 0 :
files = this . app . vault . getFiles ( ) ;
emptyFiles = [ ] ;
_i = 0 , files _1 = files ;
_a . label = 1 ;
case 1 :
if ( ! ( _i < files _1 . length ) ) return [ 3 /*break*/ , 4 ] ;
file = files _1 [ _i ] ;
if ( ! new Utils ( this . app , file . path , [ ] , [ ] , this . settings . emptyFilesDirectories , this . settings . emptyFilesFilesToIgnore , this . settings . emptyFilesIgnoreDirectories ) . isValid ( ) ) {
return [ 3 /*break*/ , 3 ] ;
}
return [ 4 /*yield*/ , this . app . vault . read ( file ) ] ;
case 2 :
content = _a . sent ( ) ;
trimmedContent = content . trim ( ) ;
if ( ! trimmedContent ) {
emptyFiles . push ( file ) ;
}
cache = app . metadataCache . getFileCache ( file ) ;
frontmatter = cache === null || cache === void 0 ? void 0 : cache . frontmatter ;
if ( frontmatter ) {
lines = content . trimRight ( ) . split ( "\n" ) . length ;
if ( frontmatter . position . end . line == lines - 1 ) {
emptyFiles . push ( file ) ;
}
}
_a . label = 3 ;
case 3 :
_i ++ ;
return [ 3 /*break*/ , 1 ] ;
case 4 :
console . log ( emptyFiles ) ;
if ( this . settings . disableWorkingLinks )
prefix = " " ;
else
prefix = "" ;
text = emptyFiles . map ( function ( file ) { return prefix + "- [[" + file . path + "]]" ; } ) . join ( "\n" ) ;
Utils . writeAndOpenFile ( this . app , this . settings . emptyFilesOutputFileName + ".md" , text , this . settings . openOutputFile ) ;
return [ 2 /*return*/ ] ;
}
} ) ;
} ) ;
} ;
FindOrphanedFilesPlugin . prototype . findOrphanedFiles = function ( dir ) {
var _this = this ;
var outFileName = this . settings . outputFileName + ".md" ;
var outFile ;
var files = this . app . vault . getFiles ( ) ;
var markdownFiles = this . app . vault . getMarkdownFiles ( ) ;
var links = [ ] ;
markdownFiles . forEach ( function ( markFile ) {
if ( markFile . path == outFileName ) {
outFile = markFile ;
return ;
}
obsidian . iterateCacheRefs ( _this . app . metadataCache . getFileCache ( markFile ) , function ( cb ) {
var txt = _this . app . metadataCache . getFirstLinkpathDest ( obsidian . getLinkpath ( cb . link ) , markFile . path ) ;
if ( txt != null )
links . push ( txt . path ) ;
} ) ;
} ) ;
var notLinkedFiles = files . filter ( function ( file ) { return _this . isValid ( file , links , dir ) ; } ) ;
notLinkedFiles . remove ( outFile ) ;
var text = "" ;
var prefix ;
if ( this . settings . disableWorkingLinks )
prefix = " " ;
else
prefix = "" ;
notLinkedFiles . forEach ( function ( file ) {
text += prefix + "- [[" + _this . app . metadataCache . fileToLinktext ( file , "/" , false ) + "]]\n" ;
} ) ;
Utils . writeAndOpenFile ( this . app , outFileName , text , this . settings . openOutputFile ) ;
} ;
FindOrphanedFilesPlugin . prototype . deleteOrphanedFiles = function ( ) {
var _a , _b ;
return _ _awaiter ( this , void 0 , void 0 , function ( ) {
var links , filesToDelete ;
var _this = this ;
return _ _generator ( this , function ( _c ) {
switch ( _c . label ) {
case 0 : return [ 4 /*yield*/ , this . app . vault . adapter . exists ( this . settings . outputFileName + ".md" ) ] ;
case 1 :
if ( ! ( _c . sent ( ) ) ) {
new obsidian . Notice ( "Can't find file - Please run the `Find orphaned files' command before" ) ;
return [ 2 /*return*/ ] ;
}
links = ( _b = ( _a = this . app . metadataCache . getCache ( this . settings . outputFileName + ".md" ) ) === null || _a === void 0 ? void 0 : _a . links ) !== null && _b !== void 0 ? _b : [ ] ;
filesToDelete = [ ] ;
links . forEach ( function ( link ) {
var file = _this . app . metadataCache . getFirstLinkpathDest ( link . link , "/" ) ;
if ( ! file )
return ;
if ( _this . settings . fileTypesToDelete [ 0 ] == "*" || _this . settings . fileTypesToDelete . contains ( file . extension ) ) {
filesToDelete . push ( file ) ;
}
} ) ;
if ( filesToDelete . length > 0 )
new DeleteFilesModal ( this . app , filesToDelete ) . open ( ) ;
return [ 2 /*return*/ ] ;
}
} ) ;
} ) ;
} ;
FindOrphanedFilesPlugin . prototype . deleteEmptyFiles = function ( ) {
var _a , _b ;
return _ _awaiter ( this , void 0 , void 0 , function ( ) {
var links , filesToDelete , _i , links _2 , link , file ;
return _ _generator ( this , function ( _c ) {
switch ( _c . label ) {
case 0 : return [ 4 /*yield*/ , this . app . vault . adapter . exists ( this . settings . emptyFilesOutputFileName + ".md" ) ] ;
case 1 :
if ( ! ( _c . sent ( ) ) ) {
new obsidian . Notice ( "Can't find file - Please run the `Find orphaned files' command before" ) ;
return [ 2 /*return*/ ] ;
}
links = ( _b = ( _a = this . app . metadataCache . getCache ( this . settings . emptyFilesOutputFileName + ".md" ) ) === null || _a === void 0 ? void 0 : _a . links ) !== null && _b !== void 0 ? _b : [ ] ;
filesToDelete = [ ] ;
for ( _i = 0 , links _2 = links ; _i < links _2 . length ; _i ++ ) {
link = links _2 [ _i ] ;
file = this . app . metadataCache . getFirstLinkpathDest ( link . link , "/" ) ;
if ( ! file )
return [ 2 /*return*/ ] ;
filesToDelete . push ( file ) ;
}
if ( filesToDelete . length > 0 )
new DeleteFilesModal ( this . app , filesToDelete ) . open ( ) ;
return [ 2 /*return*/ ] ;
}
} ) ;
} ) ;
} ;
FindOrphanedFilesPlugin . prototype . findBrokenLinks = function ( ) {
var outFileName = this . settings . unresolvedLinksOutputFileName + ".md" ;
var links = [ ] ;
var brokenLinks = this . app . metadataCache . unresolvedLinks ;
for ( var sourceFilepath in brokenLinks ) {
if ( sourceFilepath == this . settings . unresolvedLinksOutputFileName + ".md" )
continue ;
var fileType = sourceFilepath . substring ( sourceFilepath . lastIndexOf ( "." ) + 1 ) ;
var utils = new Utils ( this . app , sourceFilepath , this . settings . unresolvedLinksTagsToIgnore , this . settings . unresolvedLinksLinksToIgnore , this . settings . unresolvedLinksDirectoriesToIgnore , this . settings . unresolvedLinksFilesToIgnore , this . settings . unresolvedLinksIgnoreDirectories ) ;
if ( ! utils . isValid ( ) )
continue ;
var _loop _1 = function ( link ) {
var linkFileType = link . substring ( link . lastIndexOf ( "." ) + 1 ) ;
if ( this _1 . settings . unresolvedLinksFileTypesToIgnore . contains ( linkFileType ) )
return "continue" ;
var formattedFilePath = sourceFilepath ;
if ( fileType == "md" ) {
formattedFilePath = sourceFilepath . substring ( 0 , sourceFilepath . lastIndexOf ( ".md" ) ) ;
}
var brokenLink = { files : [ formattedFilePath ] , link : link } ;
if ( links . contains ( brokenLink ) )
return "continue" ;
var duplication = links . find ( function ( e ) { return e . link == link ; } ) ;
if ( duplication ) {
duplication . files . push ( formattedFilePath ) ;
}
else {
links . push ( brokenLink ) ;
}
} ;
var this _1 = this ;
for ( var link in brokenLinks [ sourceFilepath ] ) {
_loop _1 ( link ) ;
}
}
Utils . writeAndOpenFile ( this . app , outFileName , _ _spreadArrays ( [
"Don't forget that creating the file from here may create the file in the wrong directory!"
] , links . map ( function ( e ) { return "- [[" + e . link + "]] in [[" + e . files . join ( "]], [[" ) + "]]" ; } ) ) . join ( "\n" ) , this . settings . openOutputFile ) ;
} ;
FindOrphanedFilesPlugin . prototype . findFilesWithoutTags = function ( ) {
var _this = this ;
var outFileName = this . settings . withoutTagsOutputFileName + ".md" ;
var outFile ;
var files = this . app . vault . getMarkdownFiles ( ) ;
var withoutFiles = files . filter ( function ( file ) {
var _a ;
if ( new Utils ( _this . app , file . path , [ ] , [ ] , _this . settings . withoutTagsDirectoriesToIgnore , _this . settings . withoutTagsFilesToIgnore , true ) . isValid ( ) ) {
return ( ( _a = obsidian . getAllTags ( _this . app . metadataCache . getFileCache ( file ) ) . length ) !== null && _a !== void 0 ? _a : 0 ) <= 0 ;
}
else {
return false ;
}
} ) ;
withoutFiles . remove ( outFile ) ;
var prefix ;
if ( this . settings . disableWorkingLinks )
prefix = " " ;
else
prefix = "" ;
var text = withoutFiles . map ( function ( file ) { return prefix + "- [[" + file . path + "]]" ; } ) . join ( "\n" ) ;
Utils . writeAndOpenFile ( this . app , outFileName , text , this . settings . openOutputFile ) ;
} ;
/ * *
* Checks if the given file in an orphaned file
*
* @ param file file to check
* @ param links all links in the vault
* /
FindOrphanedFilesPlugin . prototype . isValid = function ( file , links , dir ) {
if ( links . contains ( file . path ) )
return false ;
//filetypes to ignore by default
if ( file . extension == "css" )
return false ;
if ( this . settings . fileTypesToIgnore [ 0 ] !== "" ) {
var containsFileType = this . settings . fileTypesToIgnore . contains ( file . extension ) ;
if ( this . settings . ignoreFileTypes ) {
if ( containsFileType )
return ;
}
else {
if ( ! containsFileType )
return ;
}
}
var utils = new Utils ( this . app , file . path , this . settings . tagsToIgnore , this . settings . linksToIgnore , this . settings . directoriesToIgnore , this . settings . filesToIgnore , this . settings . ignoreDirectories , dir ) ;
if ( ! utils . isValid ( ) )
return false ;
return true ;
} ;
FindOrphanedFilesPlugin . prototype . onunload = function ( ) {
console . log ( 'unloading ' + this . manifest . name + " plugin" ) ;
} ;
FindOrphanedFilesPlugin . prototype . loadSettings = function ( ) {
return _ _awaiter ( this , void 0 , void 0 , function ( ) {
var _a , _b , _c , _d ;
return _ _generator ( this , function ( _e ) {
switch ( _e . label ) {
case 0 :
_a = this ;
_c = ( _b = Object ) . assign ;
_d = [ DEFAULT _SETTINGS ] ;
return [ 4 /*yield*/ , this . loadData ( ) ] ;
case 1 :
_a . settings = _c . apply ( _b , _d . concat ( [ _e . sent ( ) ] ) ) ;
return [ 2 /*return*/ ] ;
}
} ) ;
} ) ;
} ;
FindOrphanedFilesPlugin . prototype . saveSettings = function ( ) {
return _ _awaiter ( this , void 0 , void 0 , function ( ) {
return _ _generator ( this , function ( _a ) {
switch ( _a . label ) {
case 0 : return [ 4 /*yield*/ , this . saveData ( this . settings ) ] ;
case 1 :
_a . sent ( ) ;
return [ 2 /*return*/ ] ;
}
} ) ;
} ) ;
} ;
return FindOrphanedFilesPlugin ;
} ( obsidian . Plugin ) ) ;
module . exports = FindOrphanedFilesPlugin ;
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFpbi5qcyIsInNvdXJjZXMiOlsibm9kZV9tb2R1bGVzL3RzbGliL3RzbGliLmVzNi5qcyIsInNyYy9kZWxldGVGaWxlc01vZGFsLnRzIiwic3JjL3NldHRpbmdzVGFiLnRzIiwic3JjL3V0aWxzLnRzIiwic3JjL21haW4udHMiXSwic291cmNlc0NvbnRlbnQiOlsiLyoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKlxyXG5Db3B5cmlnaHQgKGMpIE1pY3Jvc29mdCBDb3Jwb3JhdGlvbi5cclxuXHJcblBlcm1pc3Npb24gdG8gdXNlLCBjb3B5LCBtb2RpZnksIGFuZC9vciBkaXN0cmlidXRlIHRoaXMgc29mdHdhcmUgZm9yIGFueVxyXG5wdXJwb3NlIHdpdGggb3Igd2l0aG91dCBmZWUgaXMgaGVyZWJ5IGdyYW50ZWQuXHJcblxyXG5USEUgU09GVFdBUkUgSVMgUFJPVklERUQgXCJBUyBJU1wiIEFORCBUSEUgQVVUSE9SIERJU0NMQUlNUyBBTEwgV0FSUkFOVElFUyBXSVRIXHJcblJFR0FSRCBUTyBUSElTIFNPRlRXQVJFIElOQ0xVRElORyBBTEwgSU1QTElFRCBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWVxyXG5BTkQgRklUTkVTUy4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFIEFVVEhPUiBCRSBMSUFCTEUgRk9SIEFOWSBTUEVDSUFMLCBESVJFQ1QsXHJcbklORElSRUNULCBPUiBDT05TRVFVRU5USUFMIERBTUFHRVMgT1IgQU5ZIERBTUFHRVMgV0hBVFNPRVZFUiBSRVNVTFRJTkcgRlJPTVxyXG5MT1NTIE9GIFVTRSwgREFUQSBPUiBQUk9GSVRTLCBXSEVUSEVSIElOIEFOIEFDVElPTiBPRiBDT05UUkFDVCwgTkVHTElHRU5DRSBPUlxyXG5PVEhFUiBUT1JUSU9VUyBBQ1RJT04sIEFSSVNJTkcgT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgVVNFIE9SXHJcblBFUkZPUk1BTkNFIE9GIFRISVMgU09GVFdBUkUuXHJcbioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqICovXHJcbi8qIGdsb2JhbCBSZWZsZWN0LCBQcm9taXNlICovXHJcblxyXG52YXIgZXh0ZW5kU3RhdGljcyA9IGZ1bmN0aW9uKGQsIGIpIHtcclxuICAgIGV4dGVuZFN0YXRpY3MgPSBPYmplY3Quc2V0UHJvdG90eXBlT2YgfHxcclxuICAgICAgICAoeyBfX3Byb3RvX186IFtdIH0gaW5zdGFuY2VvZiBBcnJheSAmJiBmdW5jdGlvbiAoZCwgYikgeyBkLl9fcHJvdG9fXyA9IGI7IH0pIHx8XHJcbiAgICAgICAgZnVuY3Rpb24gKGQsIGIpIHsgZm9yICh2YXIgcCBpbiBiKSBpZiAoT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKGIsIHApKSBkW3BdID0gYltwXTsgfTtcclxuICAgIHJldHVybiBleHRlbmRTdGF0aWNzKGQsIGIpO1xyXG59O1xyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIF9fZXh0ZW5kcyhkLCBiKSB7XHJcbiAgICBpZiAodHlwZW9mIGIgIT09IFwiZnVuY3Rpb25cIiAmJiBiICE9PSBudWxsKVxyXG4gICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoXCJDbGFzcyBleHRlbmRzIHZhbHVlIFwiICsgU3RyaW5nKGIpICsgXCIgaXMgbm90IGEgY29uc3RydWN0b3Igb3IgbnVsbFwiKTtcclxuICAgIGV4dGVuZFN0YXRpY3MoZCwgYik7XHJcbiAgICBmdW5jdGlvbiBfXygpIHsgdGhpcy5jb25zdHJ1Y3RvciA9IGQ7IH1cclxuICAgIGQucHJvdG90eXBlID0gYiA9PT0gbnVsbCA/IE9iamVjdC5jcmVhdGUoYikgOiAoX18ucHJvdG90eXBlID0gYi5wcm90b3R5cGUsIG5ldyBfXygpKTtcclxufVxyXG5cclxuZXhwb3J0IHZhciBfX2Fzc2lnbiA9IGZ1bmN0aW9uKCkge1xyXG4gICAgX19hc3NpZ24gPSBPYmplY3QuYXNzaWduIHx8IGZ1bmN0aW9uIF9fYXNzaWduKHQpIHtcclxuICAgICAgICBmb3IgKHZhciBzLCBpID0gMSwgbiA9IGFyZ3VtZW50cy5sZW5ndGg7IGkgPCBuOyBpKyspIHtcclxuICAgICAgICAgICAgcyA9IGFyZ3VtZW50c1tpXTtcclxuICAgICAgICAgICAgZm9yICh2YXIgcCBpbiBzKSBpZiAoT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKHMsIHApKSB0W3BdID0gc1twXTtcclxuICAgICAgICB9XHJcbiAgICAgICAgcmV0dXJuIHQ7XHJcbiAgICB9XHJcbiAgICByZXR1cm4gX19hc3NpZ24uYXBwbHkodGhpcywgYXJndW1lbnRzKTtcclxufVxyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIF9fcmVzdChzLCBlKSB7XHJcbiAgICB2YXIgdCA9IHt9O1xyXG4gICAgZm9yICh2YXIgcCBpbiBzKSBpZiAoT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKHMsIHApICYmIGUuaW5kZXhPZihwKSA8IDApXHJcbiAgICAgICAgdFtwXSA9IHNbcF07XHJcbiAgICBpZiAocyAhPSBudWxsICYmIHR5cGVvZiBPYmplY3QuZ2V0T3duUHJvcGVydHlTeW1ib2xzID09PSBcImZ1bmN0aW9uXCIpXHJcbiAgICAgICAgZm9yICh2YXIgaSA9IDAsIHAgPSBPYmplY3QuZ2V0T3duUHJvcGVydHlTeW1ib2xzKHMpOyBpIDwgcC5sZW5ndGg7IGkrKykge1xyXG4gICAgICAgICAgICBpZiAoZS5pbmRleE9mKHBbaV0pIDwgMCAmJiBPYmplY3QucHJvdG90eXBlLnByb3BlcnR5SXNFbnVtZXJhYmxlLmNhbGwocywgcFtpXSkpXHJcbiAgICAgICAgICAgICAgICB0W3BbaV1dID0gc1twW2ldXTtcclxuICAgICAgICB9XHJcbiAgICByZXR1cm4gdDtcclxufVxyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIF9fZGVjb3JhdGUoZGVjb3JhdG9ycywgdGFyZ2V0LCBrZXksIGRlc2MpIHtcclxuICAgIHZhciBjID0gYXJndW1lbnRzLmxlbmd0aCwgciA9IGMgPCAzID8gdGFyZ2V0IDogZGVzYyA9PT0gbnVsbCA/IGRlc2MgPSBPYmplY3QuZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9yKHRhcmdldCwga2V5KSA6IGRlc2MsIGQ7XHJcbiAgICBpZiAodHlwZW9mIFJlZmxlY3QgPT09IFwib2JqZWN0XCIgJiYgdHlwZW9mIFJlZmxlY3QuZGVjb3JhdGUgPT09IFwiZnVuY3Rpb25cIikgciA9IFJlZmxlY3QuZGVjb3JhdGUoZGVjb3JhdG9ycywgdGFyZ2V0LCBrZXksIGRlc2MpO1xyXG4gICAgZWxzZSBmb3IgKHZhciBpID0gZGVjb3JhdG9ycy5