2021-01-22 19:41:11 +02:00
import InteropService_Exporter_Base from './InteropService_Exporter_Base' ;
import BaseModel from '../../BaseModel' ;
import shim from '../../shim' ;
import markdownUtils from '../../markdownUtils' ;
import Folder from '../../models/Folder' ;
import Note from '../../models/Note' ;
2021-08-23 01:35:45 +02:00
import { NoteEntity , ResourceEntity } from '../database/types' ;
2023-09-25 15:40:56 +02:00
import { basename , dirname , friendlySafeFilename , safeFilename } from '../../path-utils' ;
2021-08-23 01:35:45 +02:00
import { MarkupToHtml } from '@joplin/renderer' ;
2018-09-04 12:59:09 +02:00
2020-10-09 19:35:46 +02:00
export default class InteropService_Exporter_Md extends InteropService_Exporter_Base {
2021-01-22 19:41:11 +02:00
private destDir_ : string ;
private resourceDir_ : string ;
private createdDirs_ : string [ ] ;
2023-03-06 16:22:01 +02:00
public async init ( destDir : string ) {
2018-09-04 12:59:09 +02:00
this . destDir_ = destDir ;
2019-09-19 23:51:18 +02:00
this . resourceDir_ = destDir ? ` ${ destDir } /_resources ` : null ;
2018-09-04 12:59:09 +02:00
this . createdDirs_ = [ ] ;
await shim . fsDriver ( ) . mkdir ( this . destDir_ ) ;
await shim . fsDriver ( ) . mkdir ( this . resourceDir_ ) ;
}
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2023-06-30 10:11:26 +02:00
private async makeDirPath_ ( item : any , pathPart : string = null , findUniqueFilename = true ) {
2018-09-04 12:59:09 +02:00
let output = '' ;
while ( true ) {
if ( item . type_ === BaseModel . TYPE_FOLDER ) {
2018-11-21 02:36:23 +02:00
if ( pathPart ) {
2019-09-19 23:51:18 +02:00
output = ` ${ pathPart } / ${ output } ` ;
2018-11-21 02:36:23 +02:00
} else {
2021-01-22 19:41:11 +02:00
output = ` ${ friendlySafeFilename ( item . title , null ) } / ${ output } ` ;
2021-08-23 01:35:45 +02:00
if ( findUniqueFilename ) output = await shim . fsDriver ( ) . findUniqueFilename ( output , null , true ) ;
2018-11-21 02:36:23 +02:00
}
2018-09-04 12:59:09 +02:00
}
if ( ! item . parent_id ) return output ;
item = await Folder . load ( item . parent_id ) ;
}
}
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2024-02-26 12:16:23 +02:00
private async replaceLinkedItemIdsByRelativePaths_ ( item : any ) {
2020-01-18 15:16:14 +02:00
const relativePathToRoot = await this . makeDirPath_ ( item , '..' ) ;
2020-03-14 01:46:14 +02:00
const newBody = await this . replaceResourceIdsByRelativePaths_ ( item . body , relativePathToRoot ) ;
2020-01-18 15:16:14 +02:00
return await this . replaceNoteIdsByRelativePaths_ ( newBody , relativePathToRoot ) ;
}
2023-03-06 16:22:01 +02:00
private async replaceResourceIdsByRelativePaths_ ( noteBody : string , relativePathToRoot : string ) {
2020-01-18 15:16:14 +02:00
const linkedResourceIds = await Note . linkedResourceIds ( noteBody ) ;
2021-08-23 01:35:45 +02:00
const resourcePaths = this . context ( ) && this . context ( ) . destResourcePaths ? this . context ( ) . destResourcePaths : { } ;
2018-11-21 02:36:23 +02:00
2020-11-12 21:13:28 +02:00
const createRelativePath = function ( resourcePath : string ) {
2020-01-18 15:16:14 +02:00
return ` ${ relativePathToRoot } _resources/ ${ basename ( resourcePath ) } ` ;
} ;
return await this . replaceItemIdsByRelativePaths_ ( noteBody , linkedResourceIds , resourcePaths , createRelativePath ) ;
}
2023-03-06 16:22:01 +02:00
private async replaceNoteIdsByRelativePaths_ ( noteBody : string , relativePathToRoot : string ) {
2020-01-18 15:16:14 +02:00
const linkedNoteIds = await Note . linkedNoteIds ( noteBody ) ;
const notePaths = this . context ( ) && this . context ( ) . notePaths ? this . context ( ) . notePaths : { } ;
2018-11-21 02:36:23 +02:00
2020-11-12 21:13:28 +02:00
const createRelativePath = function ( notePath : string ) {
2020-09-22 13:06:19 +02:00
return markdownUtils . escapeLinkUrl ( ` ${ relativePathToRoot } ${ notePath } ` . trim ( ) ) ;
2020-01-18 15:16:14 +02:00
} ;
return await this . replaceItemIdsByRelativePaths_ ( noteBody , linkedNoteIds , notePaths , createRelativePath ) ;
}
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/no-explicit-any -- Old code before rule was applied, Old code before rule was applied
2023-03-06 16:22:01 +02:00
private async replaceItemIdsByRelativePaths_ ( noteBody : string , linkedItemIds : string [ ] , paths : any , fn_createRelativePath : Function ) {
2020-01-18 15:16:14 +02:00
let newBody = noteBody ;
for ( let i = 0 ; i < linkedItemIds . length ; i ++ ) {
const id = linkedItemIds [ i ] ;
2020-03-14 01:46:14 +02:00
const itemPath = fn_createRelativePath ( paths [ id ] ) ;
2022-11-01 16:35:48 +02:00
newBody = newBody . replace ( new RegExp ( ` :/ ${ id } ` , 'g' ) , markdownUtils . escapeLinkUrl ( itemPath ) ) ;
2018-11-21 02:36:23 +02:00
}
return newBody ;
}
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2023-03-06 16:22:01 +02:00
public async prepareForProcessingItemType ( itemType : number , itemsToExport : any [ ] ) {
2020-10-09 19:35:46 +02:00
if ( itemType === BaseModel . TYPE_NOTE ) {
2020-01-18 15:16:14 +02:00
// Create unique file path for the note
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2020-11-12 21:13:28 +02:00
const context : any = {
2020-01-18 15:16:14 +02:00
notePaths : { } ,
} ;
for ( let i = 0 ; i < itemsToExport . length ; i ++ ) {
2021-08-23 01:35:45 +02:00
const it = itemsToExport [ i ] . type ;
2020-01-18 15:16:14 +02:00
2021-08-23 01:35:45 +02:00
if ( it !== itemType ) continue ;
2020-01-18 15:16:14 +02:00
const itemOrId = itemsToExport [ i ] . itemOrId ;
const note = typeof itemOrId === 'object' ? itemOrId : await Note . load ( itemOrId ) ;
if ( ! note ) continue ;
2021-08-23 01:35:45 +02:00
const ext = note . markup_language === MarkupToHtml . MARKUP_LANGUAGE_HTML ? 'html' : 'md' ;
let notePath = ` ${ await this . makeDirPath_ ( note , null , false ) } ${ friendlySafeFilename ( note . title , null ) } . ${ ext } ` ;
notePath = await shim . fsDriver ( ) . findUniqueFilename ( ` ${ this . destDir_ } / ${ notePath } ` , Object . values ( context . notePaths ) , true ) ;
2020-01-18 15:16:14 +02:00
context . notePaths [ note . id ] = notePath ;
}
// Strip the absolute path to export dir and keep only the relative paths
const destDir = this . destDir_ ;
2023-02-20 17:02:29 +02:00
Object . keys ( context . notePaths ) . map ( ( id ) = > {
2020-01-18 15:16:14 +02:00
context . notePaths [ id ] = context . notePaths [ id ] . substr ( destDir . length + 1 ) ;
} ) ;
this . updateContext ( context ) ;
}
}
2021-10-16 10:59:37 +02:00
protected async getNoteExportContent_ ( modNote : NoteEntity ) {
2021-08-23 01:35:45 +02:00
return await Note . replaceResourceInternalToExternalLinks ( await Note . serialize ( modNote , [ 'body' ] ) ) ;
}
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2023-03-06 16:22:01 +02:00
public async processItem ( _itemType : number , item : any ) {
2018-09-04 12:59:09 +02:00
if ( [ BaseModel . TYPE_NOTE , BaseModel . TYPE_FOLDER ] . indexOf ( item . type_ ) < 0 ) return ;
2020-01-18 15:16:14 +02:00
if ( item . type_ === BaseModel . TYPE_FOLDER ) {
const dirPath = ` ${ this . destDir_ } / ${ await this . makeDirPath_ ( item ) } ` ;
2018-09-04 12:59:09 +02:00
2020-01-18 15:16:14 +02:00
if ( this . createdDirs_ . indexOf ( dirPath ) < 0 ) {
await shim . fsDriver ( ) . mkdir ( dirPath ) ;
this . createdDirs_ . push ( dirPath ) ;
}
} else if ( item . type_ === BaseModel . TYPE_NOTE ) {
const notePaths = this . context ( ) && this . context ( ) . notePaths ? this . context ( ) . notePaths : { } ;
2020-03-14 01:46:14 +02:00
const noteFilePath = ` ${ this . destDir_ } / ${ notePaths [ item . id ] } ` ;
2018-09-04 12:59:09 +02:00
2024-02-26 12:16:23 +02:00
const noteBody = await this . replaceLinkedItemIdsByRelativePaths_ ( item ) ;
2023-06-01 13:02:36 +02:00
const modNote = { . . . item , body : noteBody } ;
2021-08-23 01:35:45 +02:00
const noteContent = await this . getNoteExportContent_ ( modNote ) ;
2020-02-08 01:36:25 +02:00
await shim . fsDriver ( ) . mkdir ( dirname ( noteFilePath ) ) ;
2018-09-04 12:59:09 +02:00
await shim . fsDriver ( ) . writeFile ( noteFilePath , noteContent , 'utf-8' ) ;
}
}
2021-08-23 01:35:45 +02:00
private async findReasonableFilename ( resource : ResourceEntity , filePath : string ) {
let fileName = basename ( filePath ) ;
if ( resource . filename ) {
2023-09-25 15:40:56 +02:00
fileName = safeFilename ( resource . filename ) ;
2021-08-23 01:35:45 +02:00
} else if ( resource . title ) {
2021-11-07 18:41:39 +02:00
fileName = friendlySafeFilename ( resource . title , null , true ) ;
2021-08-23 01:35:45 +02:00
}
// Fall back on the resource filename saved in the users resource folder
return fileName ;
}
2023-03-06 16:22:01 +02:00
public async processResource ( resource : ResourceEntity , filePath : string ) {
2021-08-23 01:35:45 +02:00
const context = this . context ( ) ;
if ( ! context . destResourcePaths ) context . destResourcePaths = { } ;
const fileName = await this . findReasonableFilename ( resource , filePath ) ;
let destResourcePath = ` ${ this . resourceDir_ } / ${ fileName } ` ;
destResourcePath = await shim . fsDriver ( ) . findUniqueFilename ( destResourcePath , Object . values ( context . destResourcePaths ) , true ) ;
2018-09-04 12:59:09 +02:00
await shim . fsDriver ( ) . copy ( filePath , destResourcePath ) ;
2021-08-23 01:35:45 +02:00
context . destResourcePaths [ resource . id ] = destResourcePath ;
this . updateContext ( context ) ;
2018-09-04 12:59:09 +02:00
}
2023-03-06 16:22:01 +02:00
public async close() { }
2018-09-04 12:59:09 +02:00
}