import type BridgePlugin from './main' ; import { App , FileSystemAdapter , getAllTags , CachedMetadata , Notice , parseFrontMatterAliases , LinkCache , EmbedCache , TFolder , TFile , TAbstractFile , } from 'obsidian' ; import type { Metadata , linkToPath , tagNumber , links , backlinks , exceptMd , folder , file , tagCache , extendedMetadataCache , } from './interfaces' ; import { writeFile , writeFileSync } from 'fs' ; import Worker from './workers/metadata.worker' ; import { getAllExceptMd } from './utils' ; function getAll ( allFiles ) { const folders = [ ] ; const files = [ ] ; for ( const TAFile of allFiles ) { if ( TAFile instanceof TFolder ) { folders . push ( { name : TAFile . name , relativePath : TAFile . path } ) ; } else if ( TAFile instanceof TFile ) { files . push ( { name : TAFile . name , basename : TAFile . basename , relativePath : TAFile . path , } ) ; } } return { folders , files } ; } export default class Methods { app : App ; plugin : BridgePlugin ; constructor ( plugin , app ) { this . plugin = plugin ; this . app = app ; } getAbsolutePath ( fileName ) { let basePath ; if ( this . app . vault . adapter instanceof FileSystemAdapter ) { basePath = this . app . vault . adapter . getBasePath ( ) ; } else { throw new Error ( 'Cannot determine base path.' ) ; } const relativePath = `${ this . app . vault . configDir }/plugins/metadata-extractor/${ fileName }` ; return `${ basePath }/${ relativePath }` ; } param currentCache returns getUniqueTags ( currentCache ) { let currentTags = [ ] ; if ( getAllTags ( currentCache ) ) { currentTags = getAllTags ( currentCache ) ; } currentTags = currentTags . map ( ( tag ) => tag . slice ( 1 ) . toLowerCase ( ) ) ; currentTags = Array . from ( new Set ( currentTags ) ) ; return currentTags ; } writeAllExceptMd ( fileName ) { let path = this . plugin . settings . allExceptMdPath ; if ( ! this . plugin . settings . allExceptMdPath ) { path = this . getAbsolutePath ( fileName ) ; } const allFiles = this . app . vault . getAllLoadedFiles ( ) ; const { folders , files } = getAll ( allFiles ) ; const foldersAndFiles = getAllExceptMd ( folders , files ) ; writeFileSync ( path , JSON . stringify ( foldersAndFiles , null , 2 ) ) ; if ( this . plugin . settings . consoleLog ) { console . log ( 'Metadata Extractor plugin: wrote the allExceptMd JSON file' ) ; } } param fileName writeTagsToJSON ( fileName ) { const tags = ( this . app . metadataCache as extendedMetadataCache ) . getTags ( ) ; if ( Object . keys ( tags ) . length === 0 ) { const error = 'There are no tags in your vault.' ; if ( this . plugin . settings . consoleLog ) { console . log ( error ) ; return ; } else { return ; } } let path = this . plugin . settings . tagPath ; if ( ! this . plugin . settings . tagPath ) { path = this . getAbsolutePath ( fileName ) ; } const tagsCache = [ ] ; for ( const tfile of this . app . vault . getMarkdownFiles ( ) ) { let currentCache ! ; if ( this . app . metadataCache . getFileCache ( tfile ) !== null ) { currentCache = this . app . metadataCache . getFileCache ( tfile ) ; } const relativePath = tfile . path ; const currentTags = this . getUniqueTags ( currentCache ) ; if ( currentTags . length !== 0 ) { tagsCache . push ( { name : relativePath , tags : currentTags , } ) ; } } const allTagsFromCache = tagsCache . map ( ( element ) => { return element . tags ; } ) ; const reducedAllTagsFromCache = allTagsFromCache . reduce ( ( acc , tagArray ) => { return acc . concat ( tagArray . map ( ( tag ) => tag . toLowerCase ( ) ) ) ; } ) ; const uniqueAllTagsFromCache = Array . from ( new Set ( reducedAllTagsFromCache ) ) ; const numberOfNotesWithTag = ( this . app . metadataCache as extendedMetadataCache ) . getTags ( ) ; const tagsWithCount = { } ; for ( const [ key , value ] of Object . entries ( numberOfNotesWithTag ) ) { const newKey = key . slice ( 1 ) . toLowerCase ( ) ; const newValue = value ; tagsWithCount [ newKey ] = newValue ; } const tagToFile = [ ] ; for ( const tag of uniqueAllTagsFromCache ) { const fileNameArray = [ ] ; for ( const file of tagsCache ) { if ( file . tags . contains ( tag ) ) { fileNameArray . push ( file . name ) ; } } const numberOfNotes = tagsWithCount [ tag ] ; tagToFile . push ( { tag : tag , tagCount : numberOfNotes , relativePaths : fileNameArray , } ) ; } writeFileSync ( path , JSON . stringify ( tagToFile , null , 2 ) ) ; if ( this . plugin . settings . consoleLog ) { console . log ( 'Metadata Extractor plugin: wrote the tagToFile JSON file' ) ; } } writeCacheToJSON ( fileName ) { let path = this . plugin . settings . metadataPath ; if ( ! this . plugin . settings . metadataPath ) { path = this . getAbsolutePath ( fileName ) ; } let metadataCache = [ ] ; const fileMap = { } ; for ( const [ key , value ] of Object . entries ( this . app . vault . fileMap ) ) { const newKey = key ; let link = '' ; if ( newKey . slice ( - 3 ) === '.md' ) { if ( newKey . includes ( '/' ) ) { const split = newKey . split ( '/' ) . last ( ) ; const isString = typeof split === 'string' ; if ( isString ) { link = split ; } } link = link . slice ( 0 , - 3 ) . toLowerCase ( ) ; fileMap [ link ] = newKey ; } } for ( const tfile of this . app . vault . getMarkdownFiles ( ) ) { const displayName = tfile . basename ; const relativeFilePath = tfile . path ; let currentCache ! ; if ( typeof this . app . metadataCache . getFileCache ( tfile ) !== 'undefined' ) { currentCache = this . app . metadataCache . getFileCache ( tfile ) ; } else { new Notice ( 'Something with accessing the cache went wrong!' ) ; return ; } let currentAliases ; const currentHeadings = [ ] ; const metaObj = { } ; metaObj . fileName = displayName ; metaObj . relativePath = relativeFilePath ; const currentTags = this . getUniqueTags ( currentCache ) ; if ( currentTags !== null ) { if ( currentTags . length > 0 ) { metaObj . tags = currentTags ; } } if ( currentCache . frontmatter ) { currentAliases = parseFrontMatterAliases ( currentCache . frontmatter ) ; if ( currentAliases !== null ) { if ( currentAliases . length > 0 ) { metaObj . aliases = currentAliases ; } } } if ( currentCache . headings ) { currentCache . headings . forEach ( ( headings ) => { currentHeadings . push ( { heading : headings . heading , level : headings . level , } ) ; } ) ; metaObj . headings = currentHeadings ; } const linkMetaObj = calculateLinks ( currentCache , metaObj , fileMap , relativeFilePath , displayName ) ; Object . assign ( metaObj , linkMetaObj ) ; if ( Object . keys ( metaObj ) . length > 0 ) { metadataCache . push ( metaObj ) ; } } const backlinkObj = [ ] ; const worker = Worker ( ) ; worker . postMessage ( [ metadataCache , backlinkObj ] ) ; worker . onerror = ( event ) => { new Notice ( 'Something went wrong with the backlinks calculation.' ) ; } ; worker . onmessage = ( event ) => { metadataCache = event . data ; writeFileSync ( path , JSON . stringify ( metadataCache , null , 2 ) ) ; if ( this . plugin . settings . consoleLog ) { console . log ( 'Metadata Extractor plugin: wrote the metadata JSON file' ) ; } worker . terminate ( ) ; } ; } setWritingSchedule ( tagFileName , metadataFileName , allExceptMdFileName ) { if ( this . plugin . settings . writingFrequency !== '0' ) { const intervalInMinutes = parseInt ( this . plugin . settings . writingFrequency ) ; const milliseconds = intervalInMinutes * 60000 ; window . clearInterval ( this . plugin . intervalId1 ) ; this . plugin . intervalId1 = undefined ; this . plugin . intervalId1 = window . setInterval ( ( ) => this . writeTagsToJSON ( tagFileName ) , milliseconds ) ; this . plugin . registerInterval ( this . plugin . intervalId1 ) ; window . clearInterval ( this . plugin . intervalId2 ) ; this . plugin . intervalId2 = undefined ; this . plugin . intervalId2 = window . setInterval ( ( ) => this . writeCacheToJSON ( metadataFileName ) , milliseconds ) ; this . plugin . registerInterval ( this . plugin . intervalId2 ) ; window . clearInterval ( this . plugin . intervalId3 ) ; this . plugin . intervalId3 = undefined ; this . plugin . intervalId3 = window . setInterval ( ( ) => this . writeAllExceptMd ( allExceptMdFileName ) , milliseconds ) ; this . plugin . registerInterval ( this . plugin . intervalId3 ) ; } else if ( this . plugin . settings . writingFrequency === '0' ) { window . clearInterval ( this . plugin . intervalId1 ) ; window . clearInterval ( this . plugin . intervalId2 ) ; window . clearInterval ( this . plugin . intervalId3 ) ; } } } function calculateLinks ( currentCache , metaObj , fileMap , relativeFilePath , displayName ) { const currentLinks = [ ] ; let bothLinks = [ ] ; linksAndOrEmbeds ( ) ; function linksAndOrEmbeds ( ) { let onlyLinks = [ ] ; let onlyEmbeds = [ ] ; if ( currentCache . links ) { onlyLinks = currentCache . links ; } if ( currentCache . embeds ) { onlyEmbeds = currentCache . embeds . filter ( ( embed ) => { let link = embed . link ; if ( link . includes ( '/' ) ) { link = link . split ( '/' ) . last ( ) ; if ( link . includes ( '#' ) ) { link = link . replace ( /#.+/g , '' ) ; } } if ( link . includes ( '#' ) ) { link = link . replace ( /#.+/g , '' ) ; } if ( fileMap [ link . toLowerCase ( ) ] ) { return embed ; } } ) ; } bothLinks = onlyLinks . concat ( onlyEmbeds ) ; getLinksAndEmbeds ( ) ; } function getLinksAndEmbeds ( ) { for ( const links of bothLinks ) { let fullLink = links . link ; let aliasText = '' ; const currentLinkObject = { } ; if ( typeof links . displayText !== 'undefined' ) { aliasText = links . displayText ; } if ( fullLink . includes ( '/' ) ) { fullLink = fullLink . split ( '/' ) . last ( ) ; } let path = '' ; if ( ! fullLink . includes ( '#' ) ) { path = fileMap [ fullLink . toLowerCase ( ) ] ; currentLinkObject . link = fullLink ; if ( path ) { currentLinkObject . relativePath = path ; } if ( aliasText !== fullLink ) { currentLinkObject . displayText = aliasText ; } } else if ( fullLink . includes ( '#' ) && fullLink . charAt ( 0 ) !== '#' ) { const alias = aliasText ; const cleanLink = fullLink . replace ( /#.+/g , '' ) ; path = fileMap [ cleanLink . toLowerCase ( ) ] ; currentLinkObject . link = fullLink ; currentLinkObject . cleanLink = cleanLink ; if ( ! aliasText . includes ( '#' ) || ! aliasText . includes ( '>' ) ) { currentLinkObject . displayText = alias ; } if ( path ) { currentLinkObject . relativePath = path ; } } else if ( fullLink . charAt ( 0 ) === '#' ) { path = relativeFilePath ; currentLinkObject . link = fullLink ; currentLinkObject . relativePath = path ; currentLinkObject . cleanLink = displayName ; if ( fullLink !== aliasText ) { currentLinkObject . displayText = aliasText ; } } currentLinks . push ( currentLinkObject ) ; } if ( currentLinks . length > 0 ) { metaObj . links = currentLinks ; } } return metaObj ; }