-
Notifications
You must be signed in to change notification settings - Fork 40
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: fallback text option and file conversion (#318)
Additionally fixes up some validation views that were not appearing.
- Loading branch information
Showing
62 changed files
with
1,908 additions
and
412 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
24 changes: 24 additions & 0 deletions
24
apps/client-server/src/app/file-converter/converters/file-converter.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { IFileBuffer } from '@postybirb/types'; | ||
|
||
export interface IFileConverter { | ||
/** | ||
* Determines if the file can be converted to any of the allowable output mime types. | ||
* | ||
* @param {IFileBuffer} file | ||
* @param {string[]} allowableOutputMimeTypes | ||
* @return {*} {boolean} | ||
*/ | ||
canConvert(file: IFileBuffer, allowableOutputMimeTypes: string[]): boolean; | ||
|
||
/** | ||
* Converts the file to one of the allowable output mime types. | ||
* | ||
* @param {IFileBuffer} file | ||
* @param {string[]} allowableOutputMimeTypes | ||
* @return {*} {Promise<IFileBuffer>} | ||
*/ | ||
convert( | ||
file: IFileBuffer, | ||
allowableOutputMimeTypes: string[], | ||
): Promise<IFileBuffer>; | ||
} |
121 changes: 121 additions & 0 deletions
121
apps/client-server/src/app/file-converter/converters/text-file-converter.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
import { IFileBuffer } from '@postybirb/types'; | ||
import { htmlToText } from 'html-to-text'; | ||
import { TurndownService } from 'turndown'; | ||
import { IFileConverter } from './file-converter'; | ||
|
||
const supportedInputMimeTypes = ['text/html'] as const; | ||
const supportedOutputMimeTypes = [ | ||
'text/plain', | ||
'text/html', | ||
'text/markdown', | ||
] as const; | ||
|
||
type SupportedInputMimeTypes = (typeof supportedInputMimeTypes)[number]; | ||
type SupportedOutputMimeTypes = (typeof supportedOutputMimeTypes)[number]; | ||
|
||
type ConversionMap = { | ||
[inputMimeType in SupportedInputMimeTypes]: { | ||
[outputMimeType in SupportedOutputMimeTypes]: ( | ||
file: IFileBuffer, | ||
) => Promise<IFileBuffer>; | ||
}; | ||
}; | ||
|
||
type ConversionWeights = { | ||
[outputMimeType in SupportedOutputMimeTypes]: number; | ||
}; | ||
|
||
// TODO - use this within the post service to convert alt files to acceptable format | ||
// TODO - use overall conversion check within the validator service to see if we can convert the file, this may be useful for the end user | ||
/** | ||
* A class that converts text files to other text formats. | ||
* Largely for use when converting AltFiles (text/html) to other desirable formats. | ||
* @class TextFileConverter | ||
* @implements {IFileConverter} | ||
*/ | ||
export class TextFileConverter implements IFileConverter { | ||
private readonly supportConversionMappers: ConversionMap = { | ||
'text/html': { | ||
'text/html': this.passThrough, | ||
'text/plain': this.convertHtmlToPlaintext, | ||
'text/markdown': this.convertHtmlToMarkdown, | ||
}, | ||
}; | ||
|
||
/** | ||
* Defines the preference of conversion, trying to convert to the most preferred format first. | ||
*/ | ||
private readonly conversionWeights: ConversionWeights = { | ||
'text/plain': Number.MAX_SAFE_INTEGER, | ||
'text/html': 1, | ||
'text/markdown': 2, | ||
}; | ||
|
||
canConvert(file: IFileBuffer, allowableOutputMimeTypes: string[]): boolean { | ||
return ( | ||
supportedInputMimeTypes.includes( | ||
file.mimeType as SupportedInputMimeTypes, | ||
) && | ||
supportedOutputMimeTypes.some((m) => allowableOutputMimeTypes.includes(m)) | ||
); | ||
} | ||
|
||
async convert( | ||
file: IFileBuffer, | ||
allowableOutputMimeTypes: string[], | ||
): Promise<IFileBuffer> { | ||
const conversionMap = | ||
this.supportConversionMappers[file.mimeType as SupportedInputMimeTypes]; | ||
|
||
const sortedOutputMimeTypes = allowableOutputMimeTypes | ||
.filter((mimeType) => mimeType in conversionMap) | ||
.sort( | ||
(a, b) => | ||
this.conversionWeights[a as SupportedOutputMimeTypes] - | ||
this.conversionWeights[b as SupportedOutputMimeTypes], | ||
); | ||
|
||
for (const outputMimeType of sortedOutputMimeTypes) { | ||
const conversionFunction = | ||
conversionMap[outputMimeType as SupportedOutputMimeTypes]; | ||
if (conversionFunction) { | ||
return conversionFunction(file); | ||
} | ||
} | ||
|
||
throw new Error( | ||
`Cannot convert file ${file.fileName} with mime type: ${file.mimeType}`, | ||
); | ||
} | ||
|
||
private async passThrough(file: IFileBuffer): Promise<IFileBuffer> { | ||
return { ...file }; | ||
} | ||
|
||
private async convertHtmlToPlaintext( | ||
file: IFileBuffer, | ||
): Promise<IFileBuffer> { | ||
const text = htmlToText(file.buffer.toString(), { | ||
wordwrap: 120, | ||
}); | ||
return this.toMergedBuffer(file, text, 'text/plain'); | ||
} | ||
|
||
private async convertHtmlToMarkdown(file: IFileBuffer): Promise<IFileBuffer> { | ||
const turndownService = new TurndownService(); | ||
const markdown = turndownService.turndown(file.buffer.toString()); | ||
return this.toMergedBuffer(file, markdown, 'text/markdown'); | ||
} | ||
|
||
private toMergedBuffer( | ||
fb: IFileBuffer, | ||
str: string, | ||
mimeType: string, | ||
): IFileBuffer { | ||
return { | ||
...fb, | ||
buffer: Buffer.from(str), | ||
mimeType, | ||
}; | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
8
apps/client-server/src/app/file-converter/file-converter.module.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { Module } from '@nestjs/common'; | ||
import { FileConverterService } from './file-converter.service'; | ||
|
||
@Module({ | ||
providers: [FileConverterService], | ||
exports: [FileConverterService], | ||
}) | ||
export class FileConverterModule {} |
18 changes: 18 additions & 0 deletions
18
apps/client-server/src/app/file-converter/file-converter.service.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { Test, TestingModule } from '@nestjs/testing'; | ||
import { FileConverterService } from './file-converter.service'; | ||
|
||
describe('FileConverterService', () => { | ||
let service: FileConverterService; | ||
|
||
beforeEach(async () => { | ||
const module: TestingModule = await Test.createTestingModule({ | ||
providers: [FileConverterService], | ||
}).compile(); | ||
|
||
service = module.get<FileConverterService>(FileConverterService); | ||
}); | ||
|
||
it('should be defined', () => { | ||
expect(service).toBeDefined(); | ||
}); | ||
}); |
33 changes: 33 additions & 0 deletions
33
apps/client-server/src/app/file-converter/file-converter.service.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { Injectable } from '@nestjs/common'; | ||
import { IFileBuffer } from '@postybirb/types'; | ||
import { IFileConverter } from './converters/file-converter'; | ||
import { TextFileConverter } from './converters/text-file-converter'; | ||
|
||
@Injectable() | ||
export class FileConverterService { | ||
private readonly converters: IFileConverter[] = [new TextFileConverter()]; | ||
|
||
public async convert( | ||
file: IFileBuffer, | ||
allowableOutputMimeTypes: string[], | ||
): Promise<IFileBuffer> { | ||
const converter = this.converters.find((c) => | ||
c.canConvert(file, allowableOutputMimeTypes), | ||
); | ||
|
||
if (!converter) { | ||
throw new Error('No converter found for file'); | ||
} | ||
|
||
return converter.convert(file, allowableOutputMimeTypes); | ||
} | ||
|
||
public async canConvert( | ||
mimeType: string, | ||
allowableOutputMimeTypes: string[], | ||
): Promise<boolean> { | ||
return this.converters.some((c) => | ||
c.canConvert({ mimeType } as IFileBuffer, allowableOutputMimeTypes), | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.