diff --git a/.eslintrc.js b/.eslintrc.js index 10815e079..81a7e8784 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -79,7 +79,6 @@ module.exports = { 'react/jsx-props-no-spreading': 'off', 'react/react-in-jsx-scope': 'off', 'no-restricted-syntax': 'off', - '@typescript-eslint/ban-types': 'warn', 'import/no-extraneous-dependencies': [ 'warn', diff --git a/apps/client-server/src/app/app.module.ts b/apps/client-server/src/app/app.module.ts index 2f5b2362e..c04edb876 100644 --- a/apps/client-server/src/app/app.module.ts +++ b/apps/client-server/src/app/app.module.ts @@ -7,6 +7,7 @@ import { AccountModule } from './account/account.module'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { DirectoryWatchersModule } from './directory-watchers/directory-watchers.module'; +import { FileConverterModule } from './file-converter/file-converter.module'; import { FileModule } from './file/file.module'; import { FormGeneratorModule } from './form-generator/form-generator.module'; import { PostParsersModule } from './post-parsers/post-parsers.module'; @@ -17,10 +18,10 @@ import { TagConvertersModule } from './tag-converters/tag-converters.module'; import { TagGroupsModule } from './tag-groups/tag-groups.module'; import { UpdateModule } from './update/update.module'; import { UserSpecifiedWebsiteOptionsModule } from './user-specified-website-options/user-specified-website-options.module'; +import { ValidationModule } from './validation/validation.module'; import { WebSocketModule } from './web-socket/web-socket.module'; import { WebsiteOptionsModule } from './website-options/website-options.module'; import { WebsitesModule } from './websites/websites.module'; -import { ValidationModule } from './validation/validation.module'; @Module({ imports: [ @@ -45,6 +46,7 @@ import { ValidationModule } from './validation/validation.module'; PostModule, PostParsersModule, ValidationModule, + FileConverterModule, ], controllers: [AppController], providers: [AppService], diff --git a/apps/client-server/src/app/database/entities/submission-file.entity.ts b/apps/client-server/src/app/database/entities/submission-file.entity.ts index 1689e179a..a2a88e429 100644 --- a/apps/client-server/src/app/database/entities/submission-file.entity.ts +++ b/apps/client-server/src/app/database/entities/submission-file.entity.ts @@ -59,7 +59,7 @@ export class SubmissionFile extends PostyBirbEntity implements ISubmissionFile { orphanRemoval: true, lazy: false, nullable: true, - serializer: (s) => s.id, + serializer: (s) => s?.id, }) thumbnail: Rel; @@ -69,8 +69,9 @@ export class SubmissionFile extends PostyBirbEntity implements ISubmissionFile { orphanRemoval: true, lazy: false, nullable: true, + serializer: (s) => s?.id, }) - altFile?: Rel; + altFile: Rel; @Property({ type: 'integer', nullable: false, default: 0 }) size: number; @@ -84,6 +85,9 @@ export class SubmissionFile extends PostyBirbEntity implements ISubmissionFile { @Property({ type: 'boolean', nullable: false, default: false }) hasThumbnail: boolean; + @Property({ type: 'boolean', nullable: false, default: false }) + hasAltFile: boolean; + @Property({ type: 'json', nullable: false, diff --git a/apps/client-server/src/app/file-converter/converters/file-converter.ts b/apps/client-server/src/app/file-converter/converters/file-converter.ts new file mode 100644 index 000000000..6915eb653 --- /dev/null +++ b/apps/client-server/src/app/file-converter/converters/file-converter.ts @@ -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} + */ + convert( + file: IFileBuffer, + allowableOutputMimeTypes: string[], + ): Promise; +} diff --git a/apps/client-server/src/app/file-converter/converters/text-file-converter.ts b/apps/client-server/src/app/file-converter/converters/text-file-converter.ts new file mode 100644 index 000000000..d3304428e --- /dev/null +++ b/apps/client-server/src/app/file-converter/converters/text-file-converter.ts @@ -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; + }; +}; + +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 { + 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 { + return { ...file }; + } + + private async convertHtmlToPlaintext( + file: IFileBuffer, + ): Promise { + const text = htmlToText(file.buffer.toString(), { + wordwrap: 120, + }); + return this.toMergedBuffer(file, text, 'text/plain'); + } + + private async convertHtmlToMarkdown(file: IFileBuffer): Promise { + 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, + }; + } +} diff --git a/apps/client-server/src/app/file-converter/file-converter.module.ts b/apps/client-server/src/app/file-converter/file-converter.module.ts new file mode 100644 index 000000000..776189c0a --- /dev/null +++ b/apps/client-server/src/app/file-converter/file-converter.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { FileConverterService } from './file-converter.service'; + +@Module({ + providers: [FileConverterService], + exports: [FileConverterService], +}) +export class FileConverterModule {} diff --git a/apps/client-server/src/app/file-converter/file-converter.service.spec.ts b/apps/client-server/src/app/file-converter/file-converter.service.spec.ts new file mode 100644 index 000000000..e223fb342 --- /dev/null +++ b/apps/client-server/src/app/file-converter/file-converter.service.spec.ts @@ -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); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/apps/client-server/src/app/file-converter/file-converter.service.ts b/apps/client-server/src/app/file-converter/file-converter.service.ts new file mode 100644 index 000000000..da8ec53c1 --- /dev/null +++ b/apps/client-server/src/app/file-converter/file-converter.service.ts @@ -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 { + 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 { + return this.converters.some((c) => + c.canConvert({ mimeType } as IFileBuffer, allowableOutputMimeTypes), + ); + } +} diff --git a/apps/client-server/src/app/file/file.service.spec.ts b/apps/client-server/src/app/file/file.service.spec.ts index 4ba662666..0644e6aa0 100644 --- a/apps/client-server/src/app/file/file.service.spec.ts +++ b/apps/client-server/src/app/file/file.service.spec.ts @@ -6,6 +6,7 @@ import { readFileSync } from 'fs'; import { join } from 'path'; import { AccountService } from '../account/account.service'; import { DatabaseModule } from '../database/database.module'; +import { FileConverterService } from '../file-converter/file-converter.service'; import { FormGeneratorService } from '../form-generator/form-generator.service'; import { DescriptionParserService } from '../post-parsers/parsers/description-parser.service'; import { TagParserService } from '../post-parsers/parsers/tag-parser.service'; @@ -92,6 +93,7 @@ describe('FileService', () => { TagConvertersService, SettingsService, FormGeneratorService, + FileConverterService, ], }).compile(); @@ -116,7 +118,10 @@ describe('FileService', () => { const path = setup(); const submission = await createSubmission(); const fileInfo = createMulterData(path); - const file = await service.create(fileInfo, submission as FileSubmission); + const file = await service.create( + fileInfo, + submission as unknown as FileSubmission, + ); expect(file.file).toBeDefined(); expect(file.thumbnail).toBeDefined(); expect(file.fileName).toBe(fileInfo.originalname); @@ -137,7 +142,10 @@ describe('FileService', () => { const path = setup(); const submission = await createSubmission(); const fileInfo = createMulterData(path); - const file = await service.create(fileInfo, submission as FileSubmission); + const file = await service.create( + fileInfo, + submission as unknown as FileSubmission, + ); expect(file.file).toBeDefined(); const path2 = setup(); diff --git a/apps/client-server/src/app/file/file.service.ts b/apps/client-server/src/app/file/file.service.ts index b18698618..277fc4dba 100644 --- a/apps/client-server/src/app/file/file.service.ts +++ b/apps/client-server/src/app/file/file.service.ts @@ -3,13 +3,14 @@ import { InjectRepository } from '@mikro-orm/nestjs'; import { BadRequestException, Injectable } from '@nestjs/common'; import { read } from '@postybirb/fs'; import { Logger } from '@postybirb/logger'; -import { FileSubmission } from '@postybirb/types'; +import { EntityId, FileSubmission } from '@postybirb/types'; import type { queueAsPromised } from 'fastq'; import fastq from 'fastq'; import { readFile } from 'fs/promises'; import { cpus } from 'os'; -import { SubmissionFile } from '../database/entities'; +import { AltFile, SubmissionFile } from '../database/entities'; import { PostyBirbRepository } from '../database/repositories/postybirb-repository'; +import { UpdateAltFileDto } from '../submission/dtos/update-alt-file.dto'; import { MulterFileInfo, TaskOrigin } from './models/multer-file-info'; import { CreateTask, Task, UpdateTask } from './models/task'; import { TaskType } from './models/task-type.enum'; @@ -34,6 +35,8 @@ export class FileService { private readonly updateFileService: UpdateFileService, @InjectRepository(SubmissionFile) private readonly fileRepository: PostyBirbRepository, + @InjectRepository(AltFile) + private readonly altFileRepository: PostyBirbRepository, ) {} /** @@ -137,4 +140,28 @@ export class FileService { public async findFile(id: string): Promise { return this.fileRepository.findById(id, { failOnMissing: true }); } + + /** + * Gets the raw text of an alt text file. + * @param {string} id + */ + async getAltText(id: EntityId): Promise { + const altFile = await this.altFileRepository.findOneOrFail({ id }); + if (altFile.size) { + return altFile.buffer.toString(); + } + + return ''; + } + + /** + * Updates the raw text of an alt text file. + * @param {string} id + * @param {UpdateAltFileDto} update + */ + async updateAltText(id: string, update: UpdateAltFileDto) { + const altFile = await this.altFileRepository.findOneOrFail({ id }); + altFile.buffer = Buffer.from(update.html ?? ''); + await this.altFileRepository.persistAndFlush(altFile); + } } diff --git a/apps/client-server/src/app/file/services/create-file.service.ts b/apps/client-server/src/app/file/services/create-file.service.ts index 206803660..6dd372728 100644 --- a/apps/client-server/src/app/file/services/create-file.service.ts +++ b/apps/client-server/src/app/file/services/create-file.service.ts @@ -1,11 +1,16 @@ /* eslint-disable no-param-reassign */ +import * as rtf from '@iarna/rtf-to-html'; import { InjectRepository } from '@mikro-orm/nestjs'; import { BadRequestException, Injectable } from '@nestjs/common'; import { removeFile } from '@postybirb/fs'; import { Logger } from '@postybirb/logger'; -import { FileSubmission, IFileBuffer } from '@postybirb/types'; +import { FileSubmission, FileType, IFileBuffer } from '@postybirb/types'; +import { getFileType } from '@postybirb/utils/file-type'; import { async as hash } from 'hasha'; +import { html as htmlBeautify } from 'js-beautify'; +import * as mammoth from 'mammoth'; import { Sharp } from 'sharp'; +import { promisify } from 'util'; import { v4 as uuid } from 'uuid'; import { AltFile, @@ -58,6 +63,10 @@ export class CreateFileService { await this.populateAsImageFile(entity, file, buf); } + if (getFileType(file.originalname) === FileType.TEXT) { + await this.createSubmissionTextAltFile(entity, file, buf); + } + entity.file = this.createFileBufferEntity(entity, buf, 'primary'); submission.files.add(entity); this.logger @@ -74,6 +83,64 @@ export class CreateFileService { } } + /** + * Populates an alt file containing text data extracted from a file. + * Currently supports docx, rtf, and plaintext. + * + * @param {SubmissionFile} entity + * @param {MulterFileInfo} file + * @param {Buffer} buf + */ + async createSubmissionTextAltFile( + entity: SubmissionFile, + file: MulterFileInfo, + buf: Buffer, + ) { + let altText: string; + if ( + file.mimetype === + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' || + file.mimetype === 'application/msword' || + file.originalname.endsWith('.docx') || + file.originalname.endsWith('.doc') + ) { + this.logger.info('[Mutation] Creating Alt File for Text Document: DOCX'); + altText = (await mammoth.convertToHtml({ buffer: buf })).value; + } + + if ( + file.mimetype === 'application/rtf' || + file.originalname.endsWith('.rtf') + ) { + this.logger.info('[Mutation] Creating Alt File for Text Document: RTF'); + const promisifiedRtf = promisify(rtf.fromString); + altText = await promisifiedRtf(buf.toString(), { + template(_, __, content: string) { + return content; + }, + }); + } + + if (file.mimetype === 'text/plain' || file.originalname.endsWith('.txt')) { + this.logger.info('[Mutation] Creating Alt File for Text Document: TXT'); + altText = buf.toString(); + } + + if (altText) { + const prettifiedBuf = Buffer.from( + htmlBeautify(altText, { wrap_line_length: 100 }), + ); + const altFile = this.createFileBufferEntity(entity, prettifiedBuf, 'alt'); + altFile.mimeType = 'text/html'; + altFile.fileName = `${entity.fileName}.html`; + entity.altFile = altFile; + entity.hasAltFile = true; + this.logger.withMetadata({ id: altFile.id }).info('Alt File Created'); + } else { + this.logger.info('No Alt File Created'); + } + } + /** * Creates a SubmissionFile with pre-populated fields. * diff --git a/apps/client-server/src/app/file/services/update-file.service.ts b/apps/client-server/src/app/file/services/update-file.service.ts index e72901bcb..1b4d3e111 100644 --- a/apps/client-server/src/app/file/services/update-file.service.ts +++ b/apps/client-server/src/app/file/services/update-file.service.ts @@ -1,4 +1,5 @@ /* eslint-disable no-param-reassign */ +import * as rtf from '@iarna/rtf-to-html'; import { InjectRepository } from '@mikro-orm/nestjs'; import { BadRequestException, @@ -9,6 +10,9 @@ import { Logger } from '@postybirb/logger'; import { FileType } from '@postybirb/types'; import { getFileType } from '@postybirb/utils/file-type'; import { async as hash } from 'hasha'; +import { html as htmlBeautify } from 'js-beautify'; +import * as mammoth from 'mammoth'; +import { promisify } from 'util'; import { SubmissionFile } from '../../database/entities'; import { PostyBirbRepository } from '../../database/repositories/postybirb-repository'; import { MulterFileInfo } from '../models/multer-file-info'; @@ -111,7 +115,51 @@ export class UpdateFileService { submissionFile.file.mimeType = submissionFile.mimeType; submissionFile.file.width = submissionFile.width; submissionFile.file.height = submissionFile.height; + + if (getFileType(file.originalname) === FileType.TEXT) { + submissionFile.altFile.buffer = + (await this.repopulateTextFile(file, buf)) || + submissionFile?.altFile.buffer; + submissionFile.hasAltFile = !!submissionFile.altFile?.buffer; + } + } + } + + async repopulateTextFile( + file: MulterFileInfo, + buf: Buffer, + ): Promise { + let altText: string; + if ( + file.mimetype === + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' || + file.originalname.endsWith('.docx') + ) { + this.logger.info('[Mutation] Updating Alt File for Text Document: DOCX'); + altText = (await mammoth.convertToHtml({ buffer: buf })).value; + } + + if ( + file.mimetype === 'application/rtf' || + file.originalname.endsWith('.rtf') + ) { + this.logger.info('[Mutation] Updating Alt File for Text Document: RTF'); + const promisifiedRtf = promisify(rtf.fromString); + altText = await promisifiedRtf(buf.toString(), { + template(_, __, content: string) { + return content; + }, + }); + } + + if (file.mimetype === 'text/plain' || file.originalname.endsWith('.txt')) { + this.logger.info('[Mutation] Updating Alt File for Text Document: TXT'); + altText = buf.toString(); } + + return altText + ? Buffer.from(htmlBeautify(altText, { wrap_line_length: 100 })) + : null; } private async updateImageFileProps( diff --git a/apps/client-server/src/app/post-parsers/models/description-node/inline-description-node.ts b/apps/client-server/src/app/post-parsers/models/description-node/inline-description-node.ts index f83dfed44..64acf0e01 100644 --- a/apps/client-server/src/app/post-parsers/models/description-node/inline-description-node.ts +++ b/apps/client-server/src/app/post-parsers/models/description-node/inline-description-node.ts @@ -48,6 +48,7 @@ export class DescriptionInlineNode } toHtmlString(): string { + // TODO - figure out if we need img/video/audio tags support here if (!this.content.length) return ''; if (this.type === 'link') { diff --git a/apps/client-server/src/app/post/post-file-resizer.service.spec.ts b/apps/client-server/src/app/post/post-file-resizer.service.spec.ts index 40c8ebfa0..d9080fcec 100644 --- a/apps/client-server/src/app/post/post-file-resizer.service.spec.ts +++ b/apps/client-server/src/app/post/post-file-resizer.service.spec.ts @@ -43,6 +43,7 @@ describe('PostFileResizerService', () => { mimeType, size: testFile.length, hasThumbnail: false, + hasAltFile: false, createdAt: new Date(), updatedAt: new Date(), submission: {} as ISubmission, diff --git a/apps/client-server/src/app/post/post-manager.service.spec.ts b/apps/client-server/src/app/post/post-manager.service.spec.ts index 614910cd5..2a164aa4c 100644 --- a/apps/client-server/src/app/post/post-manager.service.spec.ts +++ b/apps/client-server/src/app/post/post-manager.service.spec.ts @@ -9,6 +9,8 @@ import { AccountModule } from '../account/account.module'; import { AccountService } from '../account/account.service'; import { CreateAccountDto } from '../account/dtos/create-account.dto'; import { DatabaseModule } from '../database/database.module'; +import { FileConverterModule } from '../file-converter/file-converter.module'; +import { FileConverterService } from '../file-converter/file-converter.service'; import { PostParsersModule } from '../post-parsers/post-parsers.module'; import { SettingsService } from '../settings/settings.service'; import { CreateSubmissionDto } from '../submission/dtos/create-submission.dto'; @@ -37,44 +39,50 @@ describe('PostManagerService', () => { let registryService: WebsiteRegistryService; beforeEach(async () => { - module = await Test.createTestingModule({ - imports: [ - DatabaseModule, - SubmissionModule, - AccountModule, - WebsiteOptionsModule, - WebsitesModule, - UserSpecifiedWebsiteOptionsModule, - PostParsersModule, - PostModule, - ], - providers: [ - PostManagerService, - PostService, - PostFileResizerService, - ValidationService, - ], - }).compile(); - - service = module.get(PostManagerService); - submissionService = module.get(SubmissionService); - accountService = module.get(AccountService); - const settingsService = module.get(SettingsService); - websiteOptionsService = module.get( - WebsiteOptionsService, - ); - postService = module.get(PostService); - registryService = module.get( - WebsiteRegistryService, - ); - orm = module.get(MikroORM); try { - await orm.getSchemaGenerator().refreshDatabase(); - } catch { - // none + module = await Test.createTestingModule({ + imports: [ + DatabaseModule, + SubmissionModule, + AccountModule, + WebsiteOptionsModule, + WebsitesModule, + UserSpecifiedWebsiteOptionsModule, + PostParsersModule, + PostModule, + FileConverterModule, + ], + providers: [ + PostManagerService, + PostService, + PostFileResizerService, + ValidationService, + FileConverterService, + ], + }).compile(); + + service = module.get(PostManagerService); + submissionService = module.get(SubmissionService); + accountService = module.get(AccountService); + const settingsService = module.get(SettingsService); + websiteOptionsService = module.get( + WebsiteOptionsService, + ); + postService = module.get(PostService); + registryService = module.get( + WebsiteRegistryService, + ); + orm = module.get(MikroORM); + try { + await orm.getSchemaGenerator().refreshDatabase(); + } catch { + // none + } + await accountService.onModuleInit(); + await settingsService.onModuleInit(); + } catch (err) { + console.log(err); } - await accountService.onModuleInit(); - await settingsService.onModuleInit(); }); function createSubmissionDto(): CreateSubmissionDto { diff --git a/apps/client-server/src/app/post/post-manager.service.ts b/apps/client-server/src/app/post/post-manager.service.ts index e94dcf1b6..5436897aa 100644 --- a/apps/client-server/src/app/post/post-manager.service.ts +++ b/apps/client-server/src/app/post/post-manager.service.ts @@ -26,17 +26,16 @@ import { } from '@postybirb/types'; import { getFileType } from '@postybirb/utils/file-type'; import { chunk } from 'lodash'; -import { - PostRecord, - Submission, - WebsiteOptions, - WebsitePostRecord, -} from '../database/entities'; +import { PostRecord, WebsitePostRecord } from '../database/entities'; import { PostyBirbRepository } from '../database/repositories/postybirb-repository'; +import { FileConverterService } from '../file-converter/file-converter.service'; import { PostParsersService } from '../post-parsers/post-parsers.service'; import { IsTestEnvironment } from '../utils/test.util'; import { ValidationService } from '../validation/validation.service'; -import { FileWebsite } from '../websites/models/website-modifiers/file-website'; +import { + ImplementedFileWebsite, + isFileWebsite +} from '../websites/models/website-modifiers/file-website'; import { MessageWebsite } from '../websites/models/website-modifiers/message-website'; import { Website } from '../websites/website'; import { WebsiteRegistryService } from '../websites/website-registry.service'; @@ -47,6 +46,8 @@ import { PostService } from './post.service'; type LoadedPostRecord = Loaded; +// TODO - HEAVILY TEST THIS WITH UNIT AND MANUAL TESTING, ESPECIALLY FILE SUBMISSIONS +// SCENARIOS - ALT FILES, RESIZE, CONVERT, CANCELLATION, BATCHING, SRC INSERTION @Injectable() export class PostManagerService { private readonly logger = Logger(); @@ -74,6 +75,7 @@ export class PostManagerService { private readonly resizerService: PostFileResizerService, private readonly postParserService: PostParsersService, private readonly validationService: ValidationService, + private readonly fileConverterService: FileConverterService, ) { setTimeout(() => this.check(), 60_000); } @@ -148,7 +150,6 @@ export class PostManagerService { const postOrderGroups = this.getPostOrder(entity); this.logger.info(`Posting to websites`); - // eslint-disable-next-line no-restricted-syntax for (const websites of postOrderGroups) { this.cancelToken.throwIfCancelled(); await Promise.allSettled( @@ -362,12 +363,17 @@ export class PostManagerService { instance: Website, data: PostData, ): Promise { + if (!isFileWebsite(instance)) { + throw new Error( + `Website '${instance.decoratedProps.metadata.displayName}' does not support file submissions`, + ); + } // Order files based on submission order const fileBatchSize = Math.max( instance.decoratedProps.fileOptions.fileBatchSize ?? 1, 1, ); - const orderedFiles: Loaded = []; + const orderedFiles: ISubmissionFile[] = []; const metadata = submission.metadata.fileMetadata; const files = submission.files .getItems() @@ -379,68 +385,24 @@ export class PostManagerService { // Only post files that haven't been posted // Ensures CONTINUED posts don't post files that have already been posted. (f) => websitePostRecord.metadata.postedFiles.indexOf(f.id) === -1, - ); + ) + .map((f) => wrap(f).toObject()); submission.metadata.order.forEach((fileId) => { const file = files.find((f) => f.id === fileId); if (file) { - orderedFiles.push(file); + orderedFiles.push(file as unknown as ISubmissionFile); } }); // Split files into batches based on instance file batch size const batches = chunk(orderedFiles, fileBatchSize); - const filePostableInstance = instance as unknown as FileWebsite; - // eslint-disable-next-line no-restricted-syntax for (const batch of batches) { this.cancelToken.throwIfCancelled(); // Resize files if necessary const processedFiles: PostingFile[] = ( await Promise.all( - batch.map((f) => { - const fileMetadata: FileMetadataFields = submission.metadata[f.id]; - let resizeParams: ImageResizeProps | undefined; - const fileType = getFileType(f.mimeType); - if (fileType === FileType.IMAGE) { - resizeParams = this.getResizeParameters( - submission, - filePostableInstance, - f, - ); - - // User defined dimensions - const userDefinedDimensions = - // NOTE: Currently the only place dimensions are set are in 'default'. - // eslint-disable-next-line @typescript-eslint/dot-notation - fileMetadata?.dimensions['default'] ?? - fileMetadata?.dimensions[instance.accountId]; - if (userDefinedDimensions) { - if ( - userDefinedDimensions.width && - userDefinedDimensions.height - ) { - resizeParams = resizeParams ?? {}; - if ( - userDefinedDimensions.width > resizeParams.width && - userDefinedDimensions.height > resizeParams.height - ) { - resizeParams = { - ...resizeParams, - width: userDefinedDimensions.width, - height: userDefinedDimensions.height, - }; - } - } - } - } - - // TODO - Website defined dimensions (i.e. HF) - - return this.resizerService.resize({ - file: f, - resize: resizeParams, - }); - }), + batch.map((f) => this.resizeOrModifyFile(f, submission, instance)), ) ).map((f) => f.withMetadata( @@ -455,9 +417,11 @@ export class PostManagerService { this.cancelToken.throwIfCancelled(); this.logger.info(`Posting file batch to ${instance.id}`); // TODO - Do something with nextBatchNumber - const result = await ( - instance as unknown as FileWebsite - ).onPostFileSubmission(data, processedFiles, this.cancelToken); + const result = await instance.onPostFileSubmission( + data, + processedFiles, + this.cancelToken, + ); const batchIds = batch.map((f) => f.id); if (result.exception) { @@ -469,6 +433,77 @@ export class PostManagerService { } } + private async resizeOrModifyFile( + file: ISubmissionFile, + submission: FileSubmission, + instance: ImplementedFileWebsite, + ): Promise { + const fileMetadata: FileMetadataFields = submission.metadata[file.id]; + let resizeParams: ImageResizeProps | undefined; + const { fileOptions } = instance.decoratedProps; + const allowedMimeTypes = fileOptions.acceptedMimeTypes ?? []; + const fileType = getFileType(file.mimeType); + if (fileType === FileType.IMAGE) { + if ( + this.fileConverterService.canConvert(file.mimeType, allowedMimeTypes) + ) { + file.file = await this.fileConverterService.convert( + file.file, + allowedMimeTypes, + ); + } + resizeParams = this.getResizeParameters(submission, instance, file); + + // User defined dimensions + const userDefinedDimensions = + // NOTE: Currently the only place dimensions are set are in 'default'. + // eslint-disable-next-line @typescript-eslint/dot-notation + fileMetadata?.dimensions['default'] ?? + fileMetadata?.dimensions[instance.accountId]; // TODO - actually have this be a thing to use + if (userDefinedDimensions) { + if (userDefinedDimensions.width && userDefinedDimensions.height) { + resizeParams = resizeParams ?? {}; + if ( + userDefinedDimensions.width > resizeParams.width && + userDefinedDimensions.height > resizeParams.height + ) { + resizeParams = { + ...resizeParams, + width: userDefinedDimensions.width, + height: userDefinedDimensions.height, + }; + } + } + } + + return this.resizerService.resize({ + file, + resize: resizeParams, + }); + } + + if ( + fileType === FileType.TEXT && + file.hasAltFile && + !allowedMimeTypes.includes(file.mimeType) + ) { + // Use alt file if it exists and is a text file + if ( + this.fileConverterService.canConvert( + file.altFile.mimeType, + allowedMimeTypes, + ) + ) { + file.file = await this.fileConverterService.convert( + file.altFile, + allowedMimeTypes, + ); + } + } + + return new PostingFile(file.id, file.file, file.thumbnail); + } + private async markFilesAsPosted( websitePostRecord: IWebsitePostRecord, submission: FileSubmission, @@ -482,7 +517,7 @@ export class PostManagerService { private getResizeParameters( submission: FileSubmission, - instance: FileWebsite, + instance: ImplementedFileWebsite, file: ISubmissionFile, ) { const params = instance.calculateImageResize(file); diff --git a/apps/client-server/src/app/post/post.module.ts b/apps/client-server/src/app/post/post.module.ts index 93f557272..c3350694e 100644 --- a/apps/client-server/src/app/post/post.module.ts +++ b/apps/client-server/src/app/post/post.module.ts @@ -1,5 +1,6 @@ import { Module } from '@nestjs/common'; import { DatabaseModule } from '../database/database.module'; +import { FileConverterModule } from '../file-converter/file-converter.module'; import { PostParsersModule } from '../post-parsers/post-parsers.module'; import { ValidationModule } from '../validation/validation.module'; import { WebsiteOptionsModule } from '../website-options/website-options.module'; @@ -17,6 +18,7 @@ import { PostService } from './post.service'; WebsitesModule, PostParsersModule, ValidationModule, + FileConverterModule, ], controllers: [PostController], providers: [ diff --git a/apps/client-server/src/app/post/post.service.spec.ts b/apps/client-server/src/app/post/post.service.spec.ts index 82fbb47d6..800628ef6 100644 --- a/apps/client-server/src/app/post/post.service.spec.ts +++ b/apps/client-server/src/app/post/post.service.spec.ts @@ -4,6 +4,7 @@ import { SubmissionType } from '@postybirb/types'; import { AccountModule } from '../account/account.module'; import { AccountService } from '../account/account.service'; import { DatabaseModule } from '../database/database.module'; +import { FileConverterService } from '../file-converter/file-converter.service'; import { PostParsersModule } from '../post-parsers/post-parsers.module'; import { SettingsService } from '../settings/settings.service'; import { CreateSubmissionDto } from '../submission/dtos/create-submission.dto'; @@ -38,7 +39,12 @@ describe('PostService', () => { PostModule, ValidationModule, ], - providers: [PostService, PostManagerService, PostFileResizerService], + providers: [ + PostService, + PostManagerService, + PostFileResizerService, + FileConverterService, + ], }).compile(); service = module.get(PostService); diff --git a/apps/client-server/src/app/submission/dtos/update-alt-file.dto.ts b/apps/client-server/src/app/submission/dtos/update-alt-file.dto.ts new file mode 100644 index 000000000..9d2eff8d6 --- /dev/null +++ b/apps/client-server/src/app/submission/dtos/update-alt-file.dto.ts @@ -0,0 +1,7 @@ +import { IUpdateAltFileDto } from '@postybirb/types'; +import { IsString } from 'class-validator'; + +export class UpdateAltFileDto implements IUpdateAltFileDto { + @IsString() + html: string; +} diff --git a/apps/client-server/src/app/submission/file-submission.controller.ts b/apps/client-server/src/app/submission/file-submission.controller.ts index 8bd06aac2..7fd66807f 100644 --- a/apps/client-server/src/app/submission/file-submission.controller.ts +++ b/apps/client-server/src/app/submission/file-submission.controller.ts @@ -1,8 +1,11 @@ import { BadRequestException, + Body, Controller, Delete, + Get, Param, + Patch, Post, UploadedFile, UploadedFiles, @@ -15,8 +18,9 @@ import { ApiOkResponse, ApiTags, } from '@nestjs/swagger'; -import { SubmissionId } from '@postybirb/types'; +import { EntityId, SubmissionId } from '@postybirb/types'; import { MulterFileInfo } from '../file/models/multer-file-info'; +import { UpdateAltFileDto } from './dtos/update-alt-file.dto'; import { FileSubmissionService } from './services/file-submission.service'; import { SubmissionService } from './services/submission.service'; @@ -109,4 +113,19 @@ export class FileSubmissionController { return this.findOne(id); } + + @Get('alt/:id') + @ApiOkResponse({ description: 'Alt File Text.' }) + async getAltFileText(@Param('id') id: EntityId) { + return this.service.getAltFileText(id); + } + + @Patch('alt/:id') + @ApiOkResponse({ description: 'Updated Alt File Text.' }) + async updateAltFileText( + @Param('id') id: EntityId, + @Body() update: UpdateAltFileDto, + ) { + return this.service.updateAltFileText(id, update); + } } diff --git a/apps/client-server/src/app/submission/services/file-submission.service.ts b/apps/client-server/src/app/submission/services/file-submission.service.ts index 55ad4fb55..a2e92116f 100644 --- a/apps/client-server/src/app/submission/services/file-submission.service.ts +++ b/apps/client-server/src/app/submission/services/file-submission.service.ts @@ -1,6 +1,7 @@ import { InjectRepository } from '@mikro-orm/nestjs'; import { BadRequestException, Injectable } from '@nestjs/common'; import { + EntityId, FileMetadataFields, FileSubmission, ISubmission, @@ -14,6 +15,7 @@ import { PostyBirbRepository } from '../../database/repositories/postybirb-repos import { FileService } from '../../file/file.service'; import { MulterFileInfo } from '../../file/models/multer-file-info'; import { CreateSubmissionDto } from '../dtos/create-submission.dto'; +import { UpdateAltFileDto } from '../dtos/update-alt-file.dto'; import { ISubmissionService } from './submission-service.interface'; type SubmissionEntity = Submission; @@ -163,4 +165,12 @@ export class FileSubmissionService ); await this.repository.persistAndFlush(submission); } + + getAltFileText(id: EntityId) { + return this.fileService.getAltText(id); + } + + updateAltFileText(id: EntityId, update: UpdateAltFileDto) { + return this.fileService.updateAltText(id, update); + } } diff --git a/apps/client-server/src/app/submission/services/submission.service.spec.ts b/apps/client-server/src/app/submission/services/submission.service.spec.ts index 3311dc1c3..e0a88619a 100644 --- a/apps/client-server/src/app/submission/services/submission.service.spec.ts +++ b/apps/client-server/src/app/submission/services/submission.service.spec.ts @@ -16,6 +16,7 @@ import { join } from 'path'; import { AccountModule } from '../../account/account.module'; import { AccountService } from '../../account/account.service'; import { DatabaseModule } from '../../database/database.module'; +import { FileConverterService } from '../../file-converter/file-converter.service'; import { FileService } from '../../file/file.service'; import { MulterFileInfo } from '../../file/models/multer-file-info'; import { CreateFileService } from '../../file/services/create-file.service'; @@ -72,6 +73,7 @@ describe('SubmissionService', () => { ValidationService, WebsiteOptionsService, WebsiteImplProvider, + FileConverterService, ], }).compile(); @@ -239,6 +241,7 @@ describe('SubmissionService', () => { file: file.file.id, fileName: fileInfo.originalname, hasThumbnail: true, + hasAltFile: false, hash: file.hash, height: 202, width: 138, diff --git a/apps/client-server/src/app/validation/validation.module.ts b/apps/client-server/src/app/validation/validation.module.ts index c94242dad..8bd4c269a 100644 --- a/apps/client-server/src/app/validation/validation.module.ts +++ b/apps/client-server/src/app/validation/validation.module.ts @@ -1,10 +1,11 @@ import { Module } from '@nestjs/common'; +import { FileConverterModule } from '../file-converter/file-converter.module'; import { PostParsersModule } from '../post-parsers/post-parsers.module'; import { WebsitesModule } from '../websites/websites.module'; import { ValidationService } from './validation.service'; @Module({ - imports: [WebsitesModule, PostParsersModule], + imports: [WebsitesModule, PostParsersModule, FileConverterModule], providers: [ValidationService], exports: [ValidationService], }) diff --git a/apps/client-server/src/app/validation/validation.service.spec.ts b/apps/client-server/src/app/validation/validation.service.spec.ts index 0092b0629..06b1bdd2d 100644 --- a/apps/client-server/src/app/validation/validation.service.spec.ts +++ b/apps/client-server/src/app/validation/validation.service.spec.ts @@ -1,6 +1,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { DatabaseModule } from '../database/database.module'; import { DatabaseUpdateSubscriber } from '../database/subscribers/database.subscriber'; +import { FileConverterService } from '../file-converter/file-converter.service'; import { PostParsersModule } from '../post-parsers/post-parsers.module'; import { PostParsersService } from '../post-parsers/post-parsers.service'; import { WebsiteImplProvider } from '../websites/implementations'; @@ -20,6 +21,7 @@ describe('ValidationService', () => { DatabaseUpdateSubscriber, WebsiteRegistryService, PostParsersService, + FileConverterService, ], }).compile(); diff --git a/apps/client-server/src/app/validation/validation.service.ts b/apps/client-server/src/app/validation/validation.service.ts index 49bea483d..7bf63f4f2 100644 --- a/apps/client-server/src/app/validation/validation.service.ts +++ b/apps/client-server/src/app/validation/validation.service.ts @@ -11,6 +11,7 @@ import { SubmissionType, ValidationResult, } from '@postybirb/types'; +import { FileConverterService } from '../file-converter/file-converter.service'; import { PostParsersService } from '../post-parsers/post-parsers.service'; import DefaultWebsite from '../websites/implementations/default/default.website'; import { isFileWebsite } from '../websites/models/website-modifiers/file-website'; @@ -29,6 +30,7 @@ export class ValidationService { constructor( private readonly websiteRegistry: WebsiteRegistryService, private readonly postParserService: PostParsersService, + private readonly fileConverterService: FileConverterService, ) {} /** @@ -86,6 +88,7 @@ export class ValidationService { websiteInstance: website, data, submission, + fileConverterService: this.fileConverterService, }; // eslint-disable-next-line no-restricted-syntax for (const validation of this.validations) { diff --git a/apps/client-server/src/app/validation/validators/file-submission-validators.ts b/apps/client-server/src/app/validation/validators/file-submission-validators.ts index 23cd2acf2..167458410 100644 --- a/apps/client-server/src/app/validation/validators/file-submission-validators.ts +++ b/apps/client-server/src/app/validation/validators/file-submission-validators.ts @@ -1,47 +1,143 @@ -import { FileType, ISubmission, SubmissionType } from '@postybirb/types'; +import { + FileSubmission, + FileType, + ISubmission, + ISubmissionFile, + SubmissionType, + ValidationMessage, +} from '@postybirb/types'; import { getFileType } from '@postybirb/utils/file-type'; import { parse } from 'path'; import { - FileWebsite, + ImplementedFileWebsite, isFileWebsite, } from '../../websites/models/website-modifiers/file-website'; import { UnknownWebsite } from '../../websites/website'; import { ValidatorParams } from './validator.type'; -function canProcessFiles( +function isFileHandlingWebsite( + websiteInstance: UnknownWebsite, +): websiteInstance is ImplementedFileWebsite { + return isFileWebsite(websiteInstance); +} + +function isFileSubmission( submission: ISubmission, +): submission is FileSubmission { + return submission.type === SubmissionType.FILE; +} + +function isFileFiltered( + file: ISubmissionFile, + submission: FileSubmission, websiteInstance: UnknownWebsite, -) { - return ( - isFileWebsite(websiteInstance) && submission.type === SubmissionType.FILE - ); +): boolean { + const { metadata } = submission; + if ( + metadata?.fileMetadata[file.id]?.ignoredWebsites.includes( + websiteInstance.accountId, + ) + ) { + return true; + } + return false; +} + +function validateTextFileRequiresFallback({ + result, + websiteInstance, + submission, +}: ValidatorParams & { file: ISubmissionFile }) { + if ( + !isFileHandlingWebsite(websiteInstance) || + !isFileSubmission(submission) + ) { + return; + } + + submission.files.getItems().forEach((file) => { + if (isFileFiltered(file, submission, websiteInstance)) { + return; + } + if (getFileType(file.fileName) === FileType.TEXT) { + const supportedMimeTypes = + websiteInstance.decoratedProps.fileOptions?.acceptedMimeTypes ?? []; + // Fail validation if the file is not supported and no alt file is provided + if (!supportedMimeTypes.includes(file.mimeType) && !file.hasAltFile) { + result.errors.push({ + id: 'validation.file.text-file-no-fallback', + field: 'files', + values: { + fileName: file.fileName, + fileExtension: parse(file.fileName).ext, + fileId: file.id, + }, + }); + } + } + }); } export async function validateAcceptedFiles({ result, websiteInstance, submission, + data, + fileConverterService, }: ValidatorParams) { - if (!canProcessFiles(submission, websiteInstance)) { + if ( + !isFileHandlingWebsite(websiteInstance) || + !isFileSubmission(submission) + ) { return; } const acceptedMimeTypes = websiteInstance.decoratedProps.fileOptions?.acceptedMimeTypes ?? []; + const supportedFileTypes = + websiteInstance.decoratedProps.fileOptions?.supportedFileTypes ?? []; submission.files.getItems().forEach((file) => { - if ( - !acceptedMimeTypes.includes(file.mimeType) || - acceptedMimeTypes.includes(parse(file.fileName).ext) - ) { - result.errors.push({ - id: 'validation.file.invalid-mime-type', - field: 'files', - values: { - mimeType: file.mimeType, - acceptedMimeTypes, - }, - }); + if (isFileFiltered(file, submission, websiteInstance)) { + return; + } + if (!acceptedMimeTypes.includes(file.mimeType)) { + const fileType = getFileType(file.fileName); + if (!supportedFileTypes.includes(fileType)) { + result.errors.push({ + id: 'validation.file.unsupported-file-type', + field: 'files', + values: { + fileName: file.fileName, + fileType: getFileType(file.fileName), + fileId: file.id, + }, + }); + } + + if (fileType === FileType.TEXT) { + validateTextFileRequiresFallback({ + result, + websiteInstance, + submission, + file, + data, + fileConverterService, + }); + return; + } + + if (!fileConverterService.canConvert(file.mimeType, acceptedMimeTypes)) { + result.errors.push({ + id: 'validation.file.invalid-mime-type', + field: 'files', + values: { + mimeType: file.mimeType, + acceptedMimeTypes, + fileId: file.id, + }, + }); + } } }); } @@ -51,13 +147,20 @@ export async function validateFileBatchSize({ websiteInstance, submission, }: ValidatorParams) { - if (!canProcessFiles(submission, websiteInstance)) { + if ( + !isFileHandlingWebsite(websiteInstance) || + !isFileSubmission(submission) + ) { return; } const maxBatchSize = websiteInstance.decoratedProps.fileOptions?.fileBatchSize ?? 0; - const numFiles = submission.files.getItems().length; + const numFiles = submission.files + .getItems() + .filter( + (file) => !isFileFiltered(file, submission, websiteInstance), + ).length; if (numFiles > maxBatchSize) { const expectedBatchesToCreate = Math.ceil(numFiles / maxBatchSize); result.warnings.push({ @@ -77,8 +180,8 @@ export async function validateFileSize({ submission, }: ValidatorParams) { if ( - submission.type !== SubmissionType.FILE || - !isFileWebsite(websiteInstance) + !isFileHandlingWebsite(websiteInstance) || + !isFileSubmission(submission) ) { return; } @@ -87,6 +190,9 @@ export async function validateFileSize({ websiteInstance.decoratedProps.fileOptions?.acceptedFileSizes ?? {}; submission.files.getItems().forEach((file) => { + if (isFileFiltered(file, submission, websiteInstance)) { + return; + } const maxFileSize = acceptedFileSizes[file.mimeType] ?? acceptedFileSizes[parse(file.fileName).ext] ?? @@ -94,15 +200,21 @@ export async function validateFileSize({ acceptedFileSizes['*']; if (maxFileSize && file.size > maxFileSize * 1024 * 1024) { - result.warnings.push({ + const issue: ValidationMessage = { id: 'validation.file.file-size', field: 'files', values: { maxFileSize, fileSize: 0, fileName: file.fileName, + fileId: file.id, }, - }); + }; + if (getFileType(file.fileName) === FileType.IMAGE) { + result.warnings.push(issue); + } else { + result.errors.push(issue); + } } }); } @@ -112,15 +224,19 @@ export async function validateImageFileDimensions({ websiteInstance, submission, }: ValidatorParams) { - if (!canProcessFiles(submission, websiteInstance)) { + if ( + !isFileHandlingWebsite(websiteInstance) || + !isFileSubmission(submission) + ) { return; } submission.files.getItems().forEach((file) => { + if (isFileFiltered(file, submission, websiteInstance)) { + return; + } if (getFileType(file.fileName) === FileType.IMAGE) { - const resizeProps = ( - websiteInstance as unknown as FileWebsite - ).calculateImageResize(file); + const resizeProps = websiteInstance.calculateImageResize(file); if (resizeProps) { result.warnings.push({ id: 'validation.file.image-resize', @@ -128,6 +244,7 @@ export async function validateImageFileDimensions({ values: { fileName: file.fileName, resizeProps, + fileId: file.id, }, }); } diff --git a/apps/client-server/src/app/validation/validators/validator.type.ts b/apps/client-server/src/app/validation/validators/validator.type.ts index fe74ab2a9..f53789d8a 100644 --- a/apps/client-server/src/app/validation/validators/validator.type.ts +++ b/apps/client-server/src/app/validation/validators/validator.type.ts @@ -4,6 +4,7 @@ import { PostData, ValidationResult, } from '@postybirb/types'; +import { FileConverterService } from '../../file-converter/file-converter.service'; import { UnknownWebsite } from '../../websites/website'; export type ValidatorParams = { @@ -11,6 +12,7 @@ export type ValidatorParams = { websiteInstance: UnknownWebsite; data: PostData; submission: ISubmission; + fileConverterService: FileConverterService; }; export type Validator = (props: ValidatorParams) => Promise; diff --git a/apps/client-server/src/app/website-options/website-options.service.spec.ts b/apps/client-server/src/app/website-options/website-options.service.spec.ts index a8381c5cd..7d3b51d18 100644 --- a/apps/client-server/src/app/website-options/website-options.service.spec.ts +++ b/apps/client-server/src/app/website-options/website-options.service.spec.ts @@ -11,6 +11,7 @@ import { AccountModule } from '../account/account.module'; import { AccountService } from '../account/account.service'; import { CreateAccountDto } from '../account/dtos/create-account.dto'; import { DatabaseModule } from '../database/database.module'; +import { FileConverterService } from '../file-converter/file-converter.service'; import { FileService } from '../file/file.service'; import { CreateFileService } from '../file/services/create-file.service'; import { UpdateFileService } from '../file/services/update-file.service'; @@ -81,6 +82,7 @@ describe('WebsiteOptionsService', () => { WebsiteOptionsService, WebsiteImplProvider, UserSpecifiedWebsiteOptionsService, + FileConverterService, ], }).compile(); diff --git a/apps/client-server/src/app/websites/decorators/supports-files.decorator.ts b/apps/client-server/src/app/websites/decorators/supports-files.decorator.ts index 9c23499ca..b01dcfdb2 100644 --- a/apps/client-server/src/app/websites/decorators/supports-files.decorator.ts +++ b/apps/client-server/src/app/websites/decorators/supports-files.decorator.ts @@ -1,19 +1,27 @@ import { WebsiteFileOptions } from '@postybirb/types'; +import { getFileTypeFromMimeType } from '@postybirb/utils/file-type'; import { Class } from 'type-fest'; import { UnknownWebsite } from '../website'; import { injectWebsiteDecoratorProps } from './website-decorator-props'; -export function SupportsFiles(websiteFileOptions: WebsiteFileOptions); +export function SupportsFiles( + websiteFileOptions: Omit, +); export function SupportsFiles(acceptedMimeTypes: string[]); export function SupportsFiles( - websiteFileOptionsOrMimeTypes: WebsiteFileOptions | string[], + websiteFileOptionsOrMimeTypes: + | Omit + | string[], ) { return function website(constructor: Class) { let websiteFileOptions: WebsiteFileOptions = Array.isArray( websiteFileOptionsOrMimeTypes, ) - ? { acceptedMimeTypes: websiteFileOptionsOrMimeTypes } - : websiteFileOptionsOrMimeTypes; + ? { + acceptedMimeTypes: websiteFileOptionsOrMimeTypes, + supportedFileTypes: [], + } + : { ...websiteFileOptionsOrMimeTypes, supportedFileTypes: [] }; websiteFileOptions = { acceptedFileSizes: {}, @@ -23,6 +31,13 @@ export function SupportsFiles( ...websiteFileOptions, }; + websiteFileOptions.acceptedMimeTypes.forEach((mimeType) => { + const fileType = getFileTypeFromMimeType(mimeType); + if (!websiteFileOptions.supportedFileTypes.includes(fileType)) { + websiteFileOptions.supportedFileTypes.push(fileType); + } + }); + injectWebsiteDecoratorProps(constructor, { fileOptions: websiteFileOptions, }); diff --git a/apps/client-server/src/app/websites/models/website-modifiers/file-website.ts b/apps/client-server/src/app/websites/models/website-modifiers/file-website.ts index 695dadd23..a59263017 100644 --- a/apps/client-server/src/app/websites/models/website-modifiers/file-website.ts +++ b/apps/client-server/src/app/websites/models/website-modifiers/file-website.ts @@ -14,6 +14,9 @@ import { UnknownWebsite } from '../../website'; export const FileWebsiteKey = 'FileModel'; +export type ImplementedFileWebsite = FileWebsite & + UnknownWebsite; + /** * Defines methods for allowing file based posting. * Generally this will always be used by each supported website. @@ -39,9 +42,6 @@ export interface FileWebsite { export function isFileWebsite( websiteInstance: UnknownWebsite, -): websiteInstance is FileWebsite & UnknownWebsite { - return Boolean( - (websiteInstance as FileWebsite & UnknownWebsite) - .supportsFile, - ); +): websiteInstance is ImplementedFileWebsite { + return Boolean((websiteInstance as ImplementedFileWebsite).supportsFile); } diff --git a/apps/client-server/src/app/websites/website.ts b/apps/client-server/src/app/websites/website.ts index 6782bd7b5..293c4663f 100644 --- a/apps/client-server/src/app/websites/website.ts +++ b/apps/client-server/src/app/websites/website.ts @@ -172,33 +172,21 @@ export abstract class Website { public async onInitialize( websiteDataRepository: PostyBirbRepository>, ): Promise { - this.logger.trace('onInitialize'); - await this.websiteDataStore.initialize(websiteDataRepository); - - this.logger.trace('Done onInitialize'); } /** * Method that runs before onLogin to set pending flag. */ public onBeforeLogin() { - this.logger.trace('onBeforeLogin'); - this.loginState.pending = true; - - this.logger.trace('Done onBeforeLogin'); } /** * Method that runs after onLogin completes to remove pending flag. */ public onAfterLogin() { - this.logger.trace('onAfterLogin'); - this.loginState.pending = false; - - this.logger.trace('Done onAfterLogin'); } /** diff --git a/apps/postybirb-ui/src/api/file-submission.api.ts b/apps/postybirb-ui/src/api/file-submission.api.ts index 2a21c49dc..385c22423 100644 --- a/apps/postybirb-ui/src/api/file-submission.api.ts +++ b/apps/postybirb-ui/src/api/file-submission.api.ts @@ -28,6 +28,14 @@ class FileSubmissionsApi { `remove/${target}/${id}/${fileId}`, ); } + + getAltText(id: EntityId) { + return this.client.get(`alt/${id}`); + } + + updateAltText(altFileId: EntityId, html: string) { + return this.client.patch(`alt/${altFileId}`, { html }); + } } export default new FileSubmissionsApi(); diff --git a/apps/postybirb-ui/src/components/shared/postybirb-editor/postybirb-editor.tsx b/apps/postybirb-ui/src/components/shared/postybirb-editor/postybirb-editor.tsx index 60814a476..6b48484b5 100644 --- a/apps/postybirb-ui/src/components/shared/postybirb-editor/postybirb-editor.tsx +++ b/apps/postybirb-ui/src/components/shared/postybirb-editor/postybirb-editor.tsx @@ -36,8 +36,6 @@ export function PostyBirbEditor(props: PostyBirbEditorProps) { ?.filter((w) => w.usernameShortcut) .map((w) => w.usernameShortcut as UsernameShortcut) || []; - // Renders the editor instance using a React component. - // TODO - remove media menu return ( { delete (blockSpecs as Partial).table; delete (blockSpecs as Partial).image; + delete (blockSpecs as Partial).file; + + return blockSpecs; +}; + +const getModifiedBlockSpecsForAltText = () => { + const blockSpecs = { + ...defaultBlockSpecs, + }; + + delete (blockSpecs as Partial).table; + delete (blockSpecs as Partial).image; + delete (blockSpecs as Partial).audio; + delete (blockSpecs as Partial).video; + delete (blockSpecs as Partial).file; return blockSpecs; }; @@ -31,3 +46,15 @@ export const schema = BlockNoteSchema.create({ ...defaultStyleSpecs, }, }); + +export const altFileSchema = BlockNoteSchema.create({ + blockSpecs: { + ...getModifiedBlockSpecsForAltText(), + }, + inlineContentSpecs: { + ...defaultInlineContentSpecs, + }, + styleSpecs: { + ...defaultStyleSpecs, + }, +}); diff --git a/apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx b/apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx index 578960832..8bc444034 100644 --- a/apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx +++ b/apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx @@ -40,7 +40,9 @@ function CardImageProvider(file: ISubmissionFileDto) { ); case FileType.TEXT: - return ; + return ( + + ); case FileType.VIDEO: return ( // eslint-disable-next-line jsx-a11y/media-has-caption diff --git a/apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx b/apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx index 8bf2b79e5..4e207e861 100644 --- a/apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx +++ b/apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx @@ -1,11 +1,13 @@ /* eslint-disable react/no-unescaped-entities */ import { Trans } from '@lingui/macro'; import { Grid, Group, NumberInput, TextInput } from '@mantine/core'; +import { notifications } from '@mantine/notifications'; import { FileMetadataFields, FileSubmissionMetadata, FileType, ISubmissionFileDto, + SubmissionId, } from '@postybirb/types'; import { getFileType } from '@postybirb/utils/file-type'; import { filesize } from 'filesize'; @@ -16,6 +18,7 @@ import { BasicWebsiteSelect } from '../../../../form/website-select/basic-websit type FileMetadataManagerProps = { file: ISubmissionFileDto; metadata: FileSubmissionMetadata; + submissionId: SubmissionId; }; type FileDetailProps = { @@ -146,7 +149,7 @@ function FileMetadata(props: FileDetailProps) { const { metadata, save } = props; return ( - + Don't post to} @@ -185,11 +188,17 @@ function FileMetadata(props: FileDetailProps) { } export function FileMetadataManager(props: FileMetadataManagerProps) { - const { file, metadata } = props; + const { submissionId, file, metadata } = props; const fileType = getFileType(file.fileName); const meta = metadata.fileMetadata[file.id]; const save = () => { - submissionApi.update(file.id, { metadata }); + submissionApi.update(submissionId, { metadata }).catch((e) => { + notifications.show({ + title: Failed to save file metadata, + message: e.message, + color: 'red', + }); + }); }; const detailProps: FileDetailProps = { file, metadata: meta, save }; return ( diff --git a/apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-text-alt.tsx b/apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-text-alt.tsx new file mode 100644 index 000000000..7ee6c7cec --- /dev/null +++ b/apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-text-alt.tsx @@ -0,0 +1,94 @@ +import { BlockNoteEditor, filterSuggestionItems } from '@blocknote/core'; +import { BlockNoteView } from '@blocknote/mantine'; +import { + SuggestionMenuController, + getDefaultReactSlashMenuItems, + useCreateBlockNote, +} from '@blocknote/react'; +import { Trans } from '@lingui/macro'; +import { + Input, + Loader, + ScrollArea, + useMantineColorScheme, +} from '@mantine/core'; +import { ISubmissionFileDto } from '@postybirb/types'; +import { debounce } from 'lodash'; +import { useCallback, useEffect } from 'react'; +import { useQuery } from 'react-query'; +import fileSubmissionApi from '../../../../../api/file-submission.api'; +import { altFileSchema } from '../../../../shared/postybirb-editor/schema'; + +type FileTextFileAltProps = { + file: ISubmissionFileDto; +}; + +export function FileTextAlt(props: FileTextFileAltProps) { + const { file } = props; + const { + data: initialHTML, + isLoading, + isFetching, + } = useQuery([], () => + fileSubmissionApi.getAltText(file.altFile).then((res) => res.body), + ); + const theme = useMantineColorScheme(); + // Creates a new editor instance. + const editor = useCreateBlockNote({ + initialContent: undefined, + schema: altFileSchema, + }) as unknown as BlockNoteEditor; + + // eslint-disable-next-line react-hooks/exhaustive-deps + const onChange = useCallback( + debounce(async () => { + const blocks = editor.document; + const html = await editor.blocksToHTMLLossy(blocks); + fileSubmissionApi.updateAltText(file.altFile, html); + }, 500), + [editor], + ); + + // For initialization; on mount, convert the initial HTML to blocks and replace the default editor's content + useEffect(() => { + async function loadInitialHTML() { + const blocks = await editor.tryParseHTMLToBlocks(initialHTML ?? ''); + editor.replaceBlocks(editor.document, blocks); + } + loadInitialHTML(); + }, [editor, initialHTML]); + + if (isLoading || isFetching) { + return ; + } + + return ( + <> + + Fallback Text + + + { + onChange(); + }} + > + + // Gets all default slash menu items and `insertAlert` item. + filterSuggestionItems( + [...getDefaultReactSlashMenuItems(editor)], + query, + ) + } + /> + + + + ); +} diff --git a/apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-validations.tsx b/apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-validations.tsx new file mode 100644 index 000000000..9eece4232 --- /dev/null +++ b/apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-validations.tsx @@ -0,0 +1,153 @@ +import { Alert, List } from '@mantine/core'; +import { + IAccountDto, + ISubmissionFileDto, + ValidationResult, +} from '@postybirb/types'; +import { IconAlertCircle, IconAlertTriangle } from '@tabler/icons-react'; +import { useMemo } from 'react'; +import { useWebsites } from '../../../../../hooks/account/use-websites'; +import { SubmissionDto } from '../../../../../models/dtos/submission.dto'; +import { ValidationTranslation } from '../../../../translations/translation'; + +type FileValidationsProps = { + submission: SubmissionDto; + file: ISubmissionFileDto; +}; + +type ModifiedValidationResult = Required & { + account: IAccountDto; +}; + +function PerAccountValidationList({ + validations, +}: { + validations: ModifiedValidationResult; +}) { + const { account } = validations; + const items: Array = [ +
+ {account.websiteInfo.websiteDisplayName} +
, + validations.errors.length ? ( + + {validations.errors.map((error) => ( + } + > + + + ))} + + ) : null, + validations.warnings.length ? ( + + {validations.warnings.map((warning) => ( + } + > + + + ))} + + ) : null, + ]; + + return items; +} + +function ValidationList({ + validations, +}: { + validations: ModifiedValidationResult[]; +}) { + return ( + + {validations.map((validation) => ( + + ))} + + ); +} + +export function FileValidations(props: FileValidationsProps) { + const { submission, file } = props; + const { validations } = submission; + const { accounts } = useWebsites(); + const filteredValidations = useMemo(() => { + const fileValidations: ModifiedValidationResult[] = []; + let hasFileValidations = false; + validations.forEach((validation) => { + const websiteOption = submission.options.find( + (o) => o.id === validation.id, + ); + if (!websiteOption) { + return; + } + const account = accounts.find((a) => a.id === websiteOption.account); + if (!account) { + return; + } + + const filteredResult: Required = { + id: account.website, + account, + errors: + validation.errors + ?.filter((error) => error.field === 'files') + .filter( + (v) => 'fileId' in v.values && v.values.fileId === file.id, + ) ?? [], + warnings: + validation.warnings + ?.filter((warning) => warning.field === 'files') + .filter( + (v) => 'fileId' in v.values && v.values.fileId === file.id, + ) ?? [], + }; + + if (filteredResult.errors.length || filteredResult.warnings.length) { + const existing = fileValidations.find( + (v) => v.id === filteredResult.id, + ); + hasFileValidations = true; + if (existing) { + existing.errors.push(...filteredResult.errors); + existing.warnings.push(...filteredResult.warnings); + } else { + fileValidations.push(filteredResult); + } + } + }); + return { fileValidations, hasFileValidations }; + }, [accounts, file.id, submission.options, validations]); + + if (!filteredValidations.hasFileValidations) { + return null; + } + + return filteredValidations.fileValidations.length ? ( + + + + ) : null; +} diff --git a/apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/submission-file-card.tsx b/apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/submission-file-card.tsx index 0deab1cb3..796cda676 100644 --- a/apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/submission-file-card.tsx +++ b/apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/submission-file-card.tsx @@ -1,28 +1,31 @@ import { Box, Flex, Paper } from '@mantine/core'; import { FileSubmissionMetadata, + FileType, ISubmissionFileDto, - SubmissionId, } from '@postybirb/types'; +import { getFileType } from '@postybirb/utils/file-type'; +import { SubmissionDto } from '../../../../../models/dtos/submission.dto'; import { FileCardDeleteAction } from './file-card-delete-action'; import { FileCardFileActions } from './file-card-file-actions'; import { FileMetadataManager } from './file-metadata-manager'; +import { FileTextAlt } from './file-text-alt'; +import { FileValidations } from './file-validations'; export const DRAGGABLE_SUBMISSION_FILE_CLASS_NAME = 'sortable-file'; export function SubmissionFileCard({ file, draggable, - metadata, totalFiles, - submissionId, + submission, }: { - submissionId: SubmissionId; + submission: SubmissionDto; file: ISubmissionFileDto; draggable: boolean; - metadata: FileSubmissionMetadata; totalFiles: number; }) { + const metadata = submission.metadata as FileSubmissionMetadata; return ( - + - + + + {getFileType(file.fileName) === FileType.TEXT ? ( + + + + ) : null} diff --git a/apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-manager.tsx b/apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-manager.tsx index 1e299b114..bc210c981 100644 --- a/apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-manager.tsx +++ b/apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-manager.tsx @@ -100,8 +100,7 @@ function FileView({ submission }: SubmissionEditFormFileManagerProps) { key={`${file.id}:${file.hash}`} file={file} draggable={orderedFiles.length > 1} - metadata={submission.metadata} - submissionId={submission.id} + submission={submission} totalFiles={orderedFiles.length} /> ))} diff --git a/apps/postybirb-ui/src/components/submissions/submission-file-preview/submission-file-preview.tsx b/apps/postybirb-ui/src/components/submissions/submission-file-preview/submission-file-preview.tsx new file mode 100644 index 000000000..5f921e89e --- /dev/null +++ b/apps/postybirb-ui/src/components/submissions/submission-file-preview/submission-file-preview.tsx @@ -0,0 +1,61 @@ +import { Image, Tooltip } from '@mantine/core'; +import { FileType, ISubmissionFileDto } from '@postybirb/types'; +import { getFileType } from '@postybirb/utils/file-type'; +import { + IconDeviceAudioTape, + IconTextCaption, + IconVideo, +} from '@tabler/icons-react'; +import { defaultTargetProvider } from '../../../transports/http-client'; + +type SubmissionFilePreviewProps = { + file: ISubmissionFileDto; + height: string | number; + width: string | number; +}; + +export function SubmissionFilePreview(props: SubmissionFilePreviewProps) { + const { file, height, width } = props; + const type = getFileType(file.fileName); + if (type === FileType.VIDEO) { + return ( + + + + ); + } + + if (type === FileType.AUDIO) { + return ( + + + + ); + } + + if (type === FileType.TEXT) { + return ( + + + + ); + } + + if (type === FileType.IMAGE) { + const src = `${defaultTargetProvider()}/api/file/thumbnail/${file.id}`; + return ( + + {file.fileName} + + ); + } + + return null; +} diff --git a/apps/postybirb-ui/src/components/submissions/submission-uploader/submission-uploader.tsx b/apps/postybirb-ui/src/components/submissions/submission-uploader/submission-uploader.tsx index 115047663..5528f4115 100644 --- a/apps/postybirb-ui/src/components/submissions/submission-uploader/submission-uploader.tsx +++ b/apps/postybirb-ui/src/components/submissions/submission-uploader/submission-uploader.tsx @@ -47,6 +47,9 @@ export const TEXT_MIME_TYPES = [ 'application/json', 'application/xml', 'text/*', + 'application/rtf', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'application/msword', ]; function Preview({ @@ -58,7 +61,6 @@ function Preview({ onEdit: (file: FileWithPath) => void; onDelete: (file: FileWithPath) => void; }) { - const imageUrl = URL.createObjectURL(file); const height = '40px'; const width = '40px'; @@ -73,6 +75,7 @@ function Preview({ } else if (type === FileType.TEXT) { view = ; } else if (type === FileType.IMAGE) { + const imageUrl = URL.createObjectURL(file); view = ( {file.name} - {type === FileType.IMAGE && ( + {type === FileType.IMAGE && !file.type.includes('gif') && ( Crop} position="top" withArrow> onEdit(file)}> @@ -187,10 +190,6 @@ export function SubmissionUploader(props: SubmissionUploaderProps) { const [files, setFiles] = useState([]); const [cropFile, setCropFile] = useState(null); - const imageFiles = files.filter( - (file) => file.type.startsWith('image/') && !file.type.includes('gif'), - ); - const onDelete = (file: FileWithPath) => { const index = files.findIndex((f) => f.name === file.name); if (index !== -1) { @@ -270,7 +269,7 @@ export function SubmissionUploader(props: SubmissionUploaderProps) {
- Drag images here or click to select files + Drag files here or click to select files - {imageFiles.length ? ( + {files.length ? ( ; } - // ! TODO - Fix all internal anchor/links to use the old way of opening a window as the current form is busted return ( - + - {type === SubmissionType.FILE && src ? ( - + {type === SubmissionType.FILE && files.length ? ( + ) : null} @@ -221,7 +209,7 @@ export function SubmissionViewCard(props: SubmissionViewCardProps) { - + ); diff --git a/apps/postybirb-ui/src/components/translations/validation-translation.tsx b/apps/postybirb-ui/src/components/translations/validation-translation.tsx index 542ce037c..1a7e96535 100644 --- a/apps/postybirb-ui/src/components/translations/validation-translation.tsx +++ b/apps/postybirb-ui/src/components/translations/validation-translation.tsx @@ -1,5 +1,9 @@ import { Trans } from '@lingui/macro'; -import { ValidationMessage, ValidationMessages } from '@postybirb/types'; +import { + FileType, + ValidationMessage, + ValidationMessages, +} from '@postybirb/types'; import { filesize } from 'filesize'; type TranslationsMap = { @@ -35,6 +39,15 @@ export const TranslationMessages: Partial = { ); }, + 'validation.file.text-file-no-fallback': (props) => { + const { fileExtension } = props.values; + return ( + + Unsupported file type {fileExtension}. Please provide fallback text. + + ); + }, + 'validation.file.invalid-mime-type': (props) => { const { mimeType, acceptedMimeTypes } = props.values; return ( @@ -45,24 +58,44 @@ export const TranslationMessages: Partial = { ); }, + 'validation.file.unsupported-file-type': (props) => { + const { fileType } = props.values; + let fileTypeString; + switch (fileType) { + case FileType.IMAGE: + fileTypeString = Image; + break; + case FileType.VIDEO: + fileTypeString = Video; + break; + case FileType.TEXT: + fileTypeString = Text; + break; + case FileType.AUDIO: + fileTypeString = Audio; + break; + default: + fileTypeString = Unknown; + break; + } + return Unsupported submission type: {fileTypeString}; + }, + 'validation.file.file-size': (props) => { - const { maxFileSize, fileSize, fileName } = props.values; + const { maxFileSize, fileSize } = props.values; const fileSizeString = filesize(fileSize); const maxFileSizeString = filesize(maxFileSize); return ( - {fileName} ({fileSizeString}) is too large (max {maxFileSizeString}) and - an attempt will be made to reduce size when posting + ({fileSizeString}) is too large (max {maxFileSizeString}) and an attempt + will be made to reduce size when posting ); }, - 'validation.file.image-resize': (props) => { - const { fileName } = props.values; - return ( - {fileName} will be modified to support website requirements - ); - }, + 'validation.file.image-resize': () => ( + File will be modified to support website requirements + ), 'validation.tags.max-tags': (props) => { // @@ -81,12 +114,12 @@ export const TranslationMessages: Partial = { 'validation.tags.min-tags': (props) => { const { minLength, currentLength } = props.values; return ( - - Requires at least {minLength} tags{' '} + <> + Requires at least {minLength} tags ({currentLength} / {minLength}) - + ); }, diff --git a/apps/postybirb-ui/src/transports/http-client.ts b/apps/postybirb-ui/src/transports/http-client.ts index 4be8881e4..0e4af4cd5 100644 --- a/apps/postybirb-ui/src/transports/http-client.ts +++ b/apps/postybirb-ui/src/transports/http-client.ts @@ -187,6 +187,6 @@ export class HttpClient { } private async processText(res: Response): Promise { - return (await res.body?.getReader().read()) as T; + return (await res.text()) as unknown as T; } } diff --git a/apps/postybirb/src/app/app.ts b/apps/postybirb/src/app/app.ts index a5cd1354b..6d1f73283 100644 --- a/apps/postybirb/src/app/app.ts +++ b/apps/postybirb/src/app/app.ts @@ -207,15 +207,20 @@ export default class PostyBirb { } static main(electronApp: Electron.App, browserWindow: typeof BrowserWindow) { - globalShortcut.registerAll(['f5', 'CommandOrControl+R'], () => { - if (PostyBirb.mainWindow) { - PostyBirb.mainWindow.reload(); - } - }); - PostyBirb.BrowserWindow = browserWindow; PostyBirb.application = electronApp; + PostyBirb.application.on('browser-window-focus', () => { + globalShortcut.registerAll(['f5', 'CommandOrControl+R'], () => { + if (PostyBirb.mainWindow && PostyBirb.mainWindow.isFocused()) { + PostyBirb.mainWindow.reload(); + } + }); + }); + PostyBirb.application.on('browser-window-blur', () => { + globalShortcut.unregister('f5'); + globalShortcut.unregister('CommandOrControl+R'); + }); PostyBirb.application.on('window-all-closed', PostyBirb.onWindowAllClosed); // Quit when all windows are closed. PostyBirb.application.on('ready', PostyBirb.onReady); // App is ready to load data PostyBirb.application.on('activate', PostyBirb.onActivate); // App is activated diff --git a/lang/en.po b/lang/en.po index fb92c982d..09817dbdc 100644 --- a/lang/en.po +++ b/lang/en.po @@ -13,12 +13,8 @@ msgstr "" "Language-Team: \n" "Plural-Forms: \n" -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:53 -msgid "{fileName} ({fileSizeString}) is too large (max {maxFileSizeString}) and an attempt will be made to reduce size when posting" -msgstr "" - -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:63 -msgid "{fileName} will be modified to support website requirements" +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:89 +msgid "({fileSizeString}) is too large (max {maxFileSizeString}) and an attempt will be made to reduce size when posting" msgstr "" #: apps/postybirb-ui/src/app/postybirb-layout/drawers/account-drawer/account-drawer.tsx:50 @@ -46,7 +42,7 @@ msgstr "" msgid "Allow resizing image" msgstr "" -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:163 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:166 msgid "Alt Text" msgstr "" @@ -58,16 +54,22 @@ msgstr "" msgid "App Server Port" msgstr "" -#: apps/postybirb-ui/src/components/submission-templates/template-picker-modal/template-picker-modal.tsx:283 -#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:275 +#: apps/postybirb-ui/src/components/submission-templates/template-picker-modal/template-picker-modal.tsx:290 +#: apps/postybirb-ui/src/components/submissions/submission-picker/submission-picker-modal.tsx:51 +#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:283 msgid "Apply" msgstr "" #: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-actions/apply-submission-template-action.tsx:25 #: apps/postybirb-ui/src/pages/submission/edit-submission-page.tsx:138 +#: apps/postybirb-ui/src/pages/submission/multi-edit-submission-page.tsx:66 msgid "Apply Template" msgstr "" +#: apps/postybirb-ui/src/pages/submission/multi-edit-submission-page.tsx:142 +msgid "Apply to Submissions" +msgstr "" + #: apps/postybirb-ui/src/components/shared/delete-action-popover/delete-action-popover.tsx:24 #: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-delete-action.tsx:32 #: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-actions/delete-submissions-action.tsx:38 @@ -78,13 +80,18 @@ msgstr "" msgid "Are you sure you want to post all selected submissions?" msgstr "" +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:75 +msgid "Audio" +msgstr "" + #: apps/postybirb-ui/src/pages/submission/file-submission-management-page.tsx:58 msgid "Auto Importers (File Watcher)" msgstr "" -#: apps/postybirb-ui/src/components/submission-templates/template-picker-modal/template-picker-modal.tsx:253 +#: apps/postybirb-ui/src/components/submission-templates/template-picker-modal/template-picker-modal.tsx:260 +#: apps/postybirb-ui/src/components/submissions/submission-picker/submission-picker-modal.tsx:43 #: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-card-actions.tsx:173 -#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:272 +#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:280 #: apps/postybirb-ui/src/pages/submission/edit-submission-page.tsx:157 msgid "Cancel" msgstr "" @@ -93,6 +100,10 @@ msgstr "" msgid "Choose Fields" msgstr "" +#: apps/postybirb-ui/src/components/submissions/submission-picker/submission-picker-modal.tsx:26 +msgid "Choose Submissions" +msgstr "" + #: apps/postybirb-ui/src/components/submission-templates/template-picker-modal/template-picker-modal.tsx:211 msgctxt "template.picker-modal-header" msgid "Choose Templates" @@ -144,19 +155,19 @@ msgstr "" #: apps/postybirb-ui/src/components/submissions/submission-uploader/edit-image-modal.tsx:72 #: apps/postybirb-ui/src/components/submissions/submission-uploader/edit-image-modal.tsx:141 -#: apps/postybirb-ui/src/components/submissions/submission-uploader/submission-uploader.tsx:107 +#: apps/postybirb-ui/src/components/submissions/submission-uploader/submission-uploader.tsx:110 msgid "Crop" msgstr "" -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:212 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:214 msgid "Crop new thumbnail from original file" msgstr "" -#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:215 +#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:223 msgid "Date" msgstr "" -#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:226 +#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:234 msgid "Days" msgstr "" @@ -165,7 +176,7 @@ msgstr "" #: apps/postybirb-ui/src/components/submission-templates/template-picker-modal/template-picker-modal.tsx:61 #: apps/postybirb-ui/src/components/submission-templates/template-picker-modal/template-picker-modal.tsx:63 #: apps/postybirb-ui/src/components/submission-templates/template-picker-modal/template-picker-modal.tsx:154 -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-edit-form.tsx:83 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-edit-form.tsx:90 msgid "Default" msgstr "" @@ -177,7 +188,7 @@ msgstr "" #: apps/postybirb-ui/src/components/shared/delete-action-popover/delete-action-popover.tsx:16 #: apps/postybirb-ui/src/components/shared/delete-action-popover/delete-action-popover.tsx:38 #: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-delete-action.tsx:46 -#: apps/postybirb-ui/src/components/submissions/submission-uploader/submission-uploader.tsx:113 +#: apps/postybirb-ui/src/components/submissions/submission-uploader/submission-uploader.tsx:116 #: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-actions/delete-submissions-action.tsx:52 msgid "Delete" msgstr "" @@ -186,11 +197,11 @@ msgstr "" msgid "Description" msgstr "" -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:19 +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:23 msgid "Description is greater than {maxLength} characters long" msgstr "" -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:25 +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:29 msgid "Description is less than {minLength} characters long" msgstr "" @@ -198,15 +209,15 @@ msgstr "" msgid "Description Settings" msgstr "" -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:152 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:155 msgid "Don't post to" msgstr "" -#: apps/postybirb-ui/src/components/submissions/submission-uploader/submission-uploader.tsx:273 -msgid "Drag images here or click to select files" +#: apps/postybirb-ui/src/components/submissions/submission-uploader/submission-uploader.tsx:272 +msgid "Drag files here or click to select files" msgstr "" -#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:105 +#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:109 msgid "Drag or use arrow keys to change order." msgstr "" @@ -219,6 +230,14 @@ msgstr "" msgid "Edit" msgstr "" +#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-actions/multi-edit-submissions-action.tsx:23 +msgid "Edit Many" +msgstr "" + +#: apps/postybirb-ui/src/pages/submission/multi-edit-submission-page.tsx:170 +msgid "Edit Multiple" +msgstr "" + #: apps/postybirb-ui/src/app/languages.tsx:6 msgid "English" msgstr "" @@ -252,6 +271,10 @@ msgstr "" msgid "Failed to save defaults" msgstr "" +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:197 +msgid "Failed to save file metadata" +msgstr "" + #: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-card-actions.tsx:122 #: apps/postybirb-ui/src/pages/submission/edit-submission-page.tsx:84 msgid "Failed to schedule submission" @@ -262,10 +285,14 @@ msgstr "" msgid "Failed to update" msgstr "" -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:13 +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:17 msgid "Failed to validate submission: {message}" msgstr "" +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-text-alt.tsx:68 +msgid "Fallback Text" +msgstr "" + #: apps/postybirb-ui/src/components/form/fields/field-translations.ts:16 msgid "Feature" msgstr "" @@ -276,13 +303,18 @@ msgstr "" #: apps/postybirb-ui/src/pages/submission/edit-submission-page.tsx:227 #: apps/postybirb-ui/src/pages/submission/file-submission-management-page.tsx:45 +#: apps/postybirb-ui/src/pages/submission/multi-edit-submission-page.tsx:174 msgid "File Submissions" msgstr "" -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:158 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:160 msgid "File types do not match. Please upload a file of the same type." msgstr "" +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:97 +msgid "File will be modified to support website requirements" +msgstr "" + #: apps/postybirb-ui/src/components/submissions/directory-watchers-view/directory-watchers-view.tsx:65 msgid "Folder" msgstr "" @@ -301,7 +333,7 @@ msgstr "" msgid "Group Name" msgstr "" -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:101 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:104 msgid "Height" msgstr "" @@ -315,7 +347,7 @@ msgstr "" msgid "Home Page" msgstr "" -#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:234 +#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:242 msgid "Hours" msgstr "" @@ -329,6 +361,10 @@ msgctxt "override-default" msgid "Ignore default tags" msgstr "" +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:66 +msgid "Image" +msgstr "" + #: apps/postybirb-ui/src/components/form/fields/description-field.tsx:42 msgid "Insert tags at end" msgstr "" @@ -341,7 +377,7 @@ msgstr "" msgid "Invalid CRON string" msgstr "" -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:156 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:158 msgid "Invalid file type" msgstr "" @@ -353,22 +389,27 @@ msgstr "" msgid "Login" msgstr "" +#: apps/postybirb-ui/src/pages/submission/multi-edit-submission-page.tsx:108 +msgid "Merge" +msgstr "" + #: apps/postybirb-ui/src/app/postybirb-layout/postybirb-spotlight/postybirb-spotlight.tsx:64 msgid "Message submissions" msgstr "" #: apps/postybirb-ui/src/pages/submission/edit-submission-page.tsx:229 #: apps/postybirb-ui/src/pages/submission/message-submission-management-page.tsx:94 +#: apps/postybirb-ui/src/pages/submission/multi-edit-submission-page.tsx:176 msgid "Message Submissions" msgstr "" -#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:242 +#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:250 msgid "Minutes" msgstr "" #: apps/postybirb-ui/src/app/postybirb-layout/drawers/account-drawer/website-card.tsx:102 #: apps/postybirb-ui/src/app/postybirb-layout/drawers/account-drawer/website-card.tsx:171 -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:36 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:39 msgid "Name" msgstr "" @@ -410,14 +451,22 @@ msgctxt "internalSchedule.single" msgid "Once" msgstr "" -#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:265 +#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:273 msgid "Only set scheduled date" msgstr "" +#: apps/postybirb-ui/src/pages/submission/multi-edit-submission-page.tsx:126 +msgid "Only use website options specified and delete website options missing from multi-update form." +msgstr "" + #: apps/postybirb-ui/src/app/postybirb-layout/drawers/settings-drawer.tsx:72 msgid "Open PostyBirb on computer startup" msgstr "" +#: apps/postybirb-ui/src/pages/submission/multi-edit-submission-page.tsx:116 +msgid "Overwrite overlapping website options only. This will keep any website options that already exist and only overwrite ones specified in the multi-update form." +msgstr "" + #: apps/postybirb-ui/src/pages/not-found/not-found.tsx:10 msgid "Page not found" msgstr "" @@ -444,7 +493,7 @@ msgid "Post Selected" msgstr "" #. Main file data -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:145 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:147 msgid "Primary" msgstr "" @@ -467,7 +516,7 @@ msgctxt "import.override-title" msgid "Replace title" msgstr "" -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:80 +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:118 msgid "Requires at least {minLength} tags" msgstr "" @@ -500,14 +549,14 @@ msgstr "" msgid "Save folder upload changes" msgstr "" -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-edit-form.tsx:146 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-edit-form.tsx:153 #: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-card-actions.tsx:129 -#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-card.tsx:144 +#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-card.tsx:141 #: apps/postybirb-ui/src/pages/submission/edit-submission-page.tsx:91 msgid "Schedule" msgstr "" -#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:201 +#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:209 msgctxt "schedule.modal-header" msgid "Schedule" msgstr "" @@ -517,7 +566,7 @@ msgid "Schedule Many" msgstr "" #: apps/postybirb-ui/src/app/postybirb-layout/postybirb-layout.tsx:148 -#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-actions/submission-view-actions.tsx:56 +#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-actions/submission-view-actions.tsx:57 msgid "Search" msgstr "" @@ -534,7 +583,7 @@ msgstr "" msgid "Settings" msgstr "" -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:43 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:46 msgctxt "submission.file-size" msgid "Size" msgstr "" @@ -547,7 +596,7 @@ msgstr "" msgid "Species" msgstr "" -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:174 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:177 msgid "Spoiler Text" msgstr "" @@ -573,11 +622,12 @@ msgstr "" msgid "Submission templates" msgstr "" -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:31 +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:35 msgid "Submission will be split into {expectedBatchesToCreate} different submissions with {maxBatchSize} files each." msgstr "" #: apps/postybirb-ui/src/components/submission-templates/template-picker-modal/template-picker-modal.tsx:114 +#: apps/postybirb-ui/src/components/submissions/submission-picker/submission-picker.tsx:67 #: apps/postybirb-ui/src/pages/submission/file-submission-management-page.tsx:48 #: apps/postybirb-ui/src/pages/submission/message-submission-management-page.tsx:97 msgid "Submissions" @@ -605,7 +655,7 @@ msgstr "" msgid "Tag Groups" msgstr "" -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:70 +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:108 msgid "Tag limit reached ({currentLength} / {maxLength})" msgstr "" @@ -615,17 +665,18 @@ msgstr "" msgid "Tags" msgstr "" -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:92 +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:130 msgid "Tags longer than {maxLength} characters will be skipped" msgstr "" #: apps/postybirb-ui/src/components/submissions/directory-watchers-view/directory-watchers-view.tsx:98 -#: apps/postybirb-ui/src/models/dtos/submission.dto.ts:126 +#: apps/postybirb-ui/src/models/dtos/submission.dto.ts:130 msgid "Template" msgstr "" #: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-actions/apply-submission-template-action.tsx:48 #: apps/postybirb-ui/src/pages/submission/edit-submission-page.tsx:118 +#: apps/postybirb-ui/src/pages/submission/multi-edit-submission-page.tsx:46 msgid "Template applied" msgstr "" @@ -648,6 +699,10 @@ msgstr "" msgid "Templates" msgstr "" +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:72 +msgid "Text" +msgstr "" + #: apps/postybirb-ui/src/app/postybirb-layout/drawers/settings-drawer.tsx:105 msgid "This is the folder where the app will store its data. You must restart the app for this to take effect." msgstr "" @@ -656,7 +711,7 @@ msgstr "" msgid "This will clear all data associated with this account. You will need to log in again." msgstr "" -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:195 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:197 msgid "Thumbnail" msgstr "" @@ -664,30 +719,32 @@ msgstr "" msgid "Title" msgstr "" -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:104 +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:142 msgid "Title is too long" msgstr "" -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:102 +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:140 msgid "Title is too long and will be truncated" msgstr "" -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:118 +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:156 msgid "Title is too short" msgstr "" -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:136 +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:174 msgid "Translation {id} not found" msgstr "" -#: apps/postybirb-ui/src/components/form/website-option-form/website-option-form.tsx:231 +#: apps/postybirb-ui/src/components/form/website-option-form/website-option-form.tsx:239 msgid "Unable to display form" msgstr "" #: apps/postybirb-ui/src/app/postybirb-layout/drawers/account-drawer/website-card.tsx:116 #: apps/postybirb-ui/src/components/submission-templates/template-picker-modal/template-picker-modal.tsx:104 #: apps/postybirb-ui/src/components/submission-templates/template-picker-modal/template-picker-modal.tsx:145 -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-edit-form.tsx:90 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-edit-form.tsx:97 +#: apps/postybirb-ui/src/components/submissions/submission-picker/submission-picker.tsx:55 +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:78 msgid "Unknown" msgstr "" @@ -700,16 +757,24 @@ msgstr "" msgid "Unschedule" msgstr "" -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:42 +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:45 +msgid "Unsupported file type {fileExtension}. Please provide fallback text." +msgstr "" + +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:55 msgid "Unsupported file type {mimeType}" msgstr "" +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:81 +msgid "Unsupported submission type: {fileTypeString}" +msgstr "" + #: apps/postybirb-ui/src/app/postybirb-layout/drawers/tag-converter-drawer.tsx:91 #: apps/postybirb-ui/src/app/postybirb-layout/drawers/tag-group-drawer.tsx:98 msgid "Update" msgstr "" -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:184 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:186 msgid "Update file" msgstr "" @@ -717,11 +782,15 @@ msgstr "" msgid "Update PostyBirb" msgstr "" -#: apps/postybirb-ui/src/components/submissions/submission-uploader/submission-uploader.tsx:176 +#: apps/postybirb-ui/src/pages/submission/multi-edit-submission-page.tsx:94 +msgid "Updates applied" +msgstr "" + +#: apps/postybirb-ui/src/components/submissions/submission-uploader/submission-uploader.tsx:179 msgid "Upload" msgstr "" -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:233 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:235 msgid "Upload new thumbnail" msgstr "" @@ -733,6 +802,10 @@ msgstr "" msgid "Use thumbnail" msgstr "" +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:69 +msgid "Video" +msgstr "" + #: apps/postybirb-ui/src/website-components/discord/discord-login-view.tsx:86 msgid "Webhook is required" msgstr "" @@ -742,11 +815,11 @@ msgid "Webhook name {name} is invalid" msgstr "" #: apps/postybirb-ui/src/app/postybirb-layout/drawers/account-drawer/website-visibility-picker.tsx:19 -#: apps/postybirb-ui/src/components/form/website-select/website-select.tsx:51 +#: apps/postybirb-ui/src/components/form/website-select/website-select.tsx:52 msgid "Websites" msgstr "" -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:122 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:125 msgid "Width" msgstr "" @@ -754,6 +827,6 @@ msgstr "" msgid "Your browser does not support the audio tag." msgstr "" -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:50 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:52 msgid "Your browser does not support the video tag." msgstr "" diff --git a/lang/es.po b/lang/es.po index 92956b527..086087387 100644 --- a/lang/es.po +++ b/lang/es.po @@ -1,13 +1,19 @@ msgid "" -msgstr "Project-Id-Version: \nReport-Msgid-Bugs-To: \nPOT-Creation-Date: \nPO-Revision-Date: 2024-11-13 14:00+0000\nLast-Translator: gallegonovato \nLanguage-Team: Spanish \nLanguage: es\nContent-Type: \nContent-Transfer-Encoding: \nPlural-Forms: nplurals=2; plural=n != 1;\nX-Generator: Weblate 5.9-dev\n" - -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:53 -msgid "{fileName} ({fileSizeString}) is too large (max {maxFileSizeString}) and an attempt will be made to reduce size when posting" -msgstr "{fileName} ({fileSizeString}) es demasiado grande (máx. {maxFileSizeString}) y se intentará reducir el tamaño al publicarlo" - -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:63 -msgid "{fileName} will be modified to support website requirements" -msgstr "{fileName} se modificará para cumplir los requisitos de la página web" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: \n" +"PO-Revision-Date: \n" +"Last-Translator: \n" +"Language: \n" +"Language-Team: \n" +"Content-Type: \n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:89 +msgid "({fileSizeString}) is too large (max {maxFileSizeString}) and an attempt will be made to reduce size when posting" +msgstr "({fileSizeString}) es demasiado grande (máx. {maxFileSizeString}) y se intentará reducir el tamaño al publicarlo" #: apps/postybirb-ui/src/app/postybirb-layout/drawers/account-drawer/account-drawer.tsx:50 #: apps/postybirb-ui/src/app/postybirb-layout/postybirb-layout.tsx:57 @@ -34,7 +40,7 @@ msgstr "Permitir a PostyBirb insertar un anuncio en la descripción" msgid "Allow resizing image" msgstr "Permitir cambiar el tamaño de la imagen" -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:163 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:166 msgid "Alt Text" msgstr "Texto alternativo" @@ -46,16 +52,22 @@ msgstr "Folder de applicación" msgid "App Server Port" msgstr "Puerto del Servidor de la Aplicación" -#: apps/postybirb-ui/src/components/submission-templates/template-picker-modal/template-picker-modal.tsx:283 -#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:275 +#: apps/postybirb-ui/src/components/submission-templates/template-picker-modal/template-picker-modal.tsx:290 +#: apps/postybirb-ui/src/components/submissions/submission-picker/submission-picker-modal.tsx:51 +#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:283 msgid "Apply" msgstr "Aplicar" #: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-actions/apply-submission-template-action.tsx:25 #: apps/postybirb-ui/src/pages/submission/edit-submission-page.tsx:138 +#: apps/postybirb-ui/src/pages/submission/multi-edit-submission-page.tsx:66 msgid "Apply Template" msgstr "Aplicar plantilla" +#: apps/postybirb-ui/src/pages/submission/multi-edit-submission-page.tsx:142 +msgid "Apply to Submissions" +msgstr "" + #: apps/postybirb-ui/src/components/shared/delete-action-popover/delete-action-popover.tsx:24 #: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-delete-action.tsx:32 #: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-actions/delete-submissions-action.tsx:38 @@ -66,13 +78,18 @@ msgstr "¿Estás seguro de que deseas eliminar esto? Esta acción no se puede de msgid "Are you sure you want to post all selected submissions?" msgstr "¿Está seguro de que desea publicar todos los envíos seleccionados ?" +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:75 +msgid "Audio" +msgstr "" + #: apps/postybirb-ui/src/pages/submission/file-submission-management-page.tsx:58 msgid "Auto Importers (File Watcher)" msgstr "Importadores automáticos (al cambiar de carpeta)" -#: apps/postybirb-ui/src/components/submission-templates/template-picker-modal/template-picker-modal.tsx:253 +#: apps/postybirb-ui/src/components/submission-templates/template-picker-modal/template-picker-modal.tsx:260 +#: apps/postybirb-ui/src/components/submissions/submission-picker/submission-picker-modal.tsx:43 #: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-card-actions.tsx:173 -#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:272 +#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:280 #: apps/postybirb-ui/src/pages/submission/edit-submission-page.tsx:157 msgid "Cancel" msgstr "Cancelar" @@ -81,6 +98,10 @@ msgstr "Cancelar" msgid "Choose Fields" msgstr "Seleccione campos" +#: apps/postybirb-ui/src/components/submissions/submission-picker/submission-picker-modal.tsx:26 +msgid "Choose Submissions" +msgstr "" + #: apps/postybirb-ui/src/components/submission-templates/template-picker-modal/template-picker-modal.tsx:211 msgctxt "template.picker-modal-header" msgid "Choose Templates" @@ -132,19 +153,19 @@ msgstr "Formato CRON" #: apps/postybirb-ui/src/components/submissions/submission-uploader/edit-image-modal.tsx:72 #: apps/postybirb-ui/src/components/submissions/submission-uploader/edit-image-modal.tsx:141 -#: apps/postybirb-ui/src/components/submissions/submission-uploader/submission-uploader.tsx:107 +#: apps/postybirb-ui/src/components/submissions/submission-uploader/submission-uploader.tsx:110 msgid "Crop" msgstr "Recortar" -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:212 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:214 msgid "Crop new thumbnail from original file" msgstr "Recortar nueva miniatura del archivo original" -#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:215 +#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:223 msgid "Date" msgstr "Fecha" -#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:226 +#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:234 msgid "Days" msgstr "Días" @@ -153,7 +174,7 @@ msgstr "Días" #: apps/postybirb-ui/src/components/submission-templates/template-picker-modal/template-picker-modal.tsx:61 #: apps/postybirb-ui/src/components/submission-templates/template-picker-modal/template-picker-modal.tsx:63 #: apps/postybirb-ui/src/components/submission-templates/template-picker-modal/template-picker-modal.tsx:154 -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-edit-form.tsx:83 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-edit-form.tsx:90 msgid "Default" msgstr "Por Defecto" @@ -165,7 +186,7 @@ msgstr "Guardados por Defecto" #: apps/postybirb-ui/src/components/shared/delete-action-popover/delete-action-popover.tsx:16 #: apps/postybirb-ui/src/components/shared/delete-action-popover/delete-action-popover.tsx:38 #: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-delete-action.tsx:46 -#: apps/postybirb-ui/src/components/submissions/submission-uploader/submission-uploader.tsx:113 +#: apps/postybirb-ui/src/components/submissions/submission-uploader/submission-uploader.tsx:116 #: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-actions/delete-submissions-action.tsx:52 msgid "Delete" msgstr "Borrar" @@ -174,11 +195,11 @@ msgstr "Borrar" msgid "Description" msgstr "Descripción" -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:19 +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:23 msgid "Description is greater than {maxLength} characters long" msgstr "La descripción sobrepasa los {maxLength} caracteres" -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:25 +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:29 msgid "Description is less than {minLength} characters long" msgstr "Descripción es menor que {minLength} caracteres de largo" @@ -186,15 +207,15 @@ msgstr "Descripción es menor que {minLength} caracteres de largo" msgid "Description Settings" msgstr "Ajustes de la descripción" -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:152 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:155 msgid "Don't post to" msgstr "No publicar en" -#: apps/postybirb-ui/src/components/submissions/submission-uploader/submission-uploader.tsx:273 -msgid "Drag images here or click to select files" -msgstr "Arrastre las imágenes aquí o haga clic para seleccionar los archivos" +#: apps/postybirb-ui/src/components/submissions/submission-uploader/submission-uploader.tsx:272 +msgid "Drag files here or click to select files" +msgstr "Arrastre los archivos aquí o haga clic para seleccionar los archivos" -#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:105 +#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:109 msgid "Drag or use arrow keys to change order." msgstr "Arrastra o utiliza las teclas de flecha para cambiar el orden." @@ -207,6 +228,14 @@ msgstr "Duplicar" msgid "Edit" msgstr "Editar" +#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-actions/multi-edit-submissions-action.tsx:23 +msgid "Edit Many" +msgstr "" + +#: apps/postybirb-ui/src/pages/submission/multi-edit-submission-page.tsx:170 +msgid "Edit Multiple" +msgstr "" + #: apps/postybirb-ui/src/app/languages.tsx:6 msgid "English" msgstr "Inglés" @@ -240,6 +269,10 @@ msgstr "Error al poner en cola el envío" msgid "Failed to save defaults" msgstr "No se pudieron guardar los valores predeterminados" +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:197 +msgid "Failed to save file metadata" +msgstr "" + #: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-card-actions.tsx:122 #: apps/postybirb-ui/src/pages/submission/edit-submission-page.tsx:84 msgid "Failed to schedule submission" @@ -250,10 +283,14 @@ msgstr "No se pudo programar el envío" msgid "Failed to update" msgstr "Error al actualizar" -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:13 +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:17 msgid "Failed to validate submission: {message}" msgstr "No se pudo validar el envío: {message}" +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-text-alt.tsx:68 +msgid "Fallback Text" +msgstr "" + #: apps/postybirb-ui/src/components/form/fields/field-translations.ts:16 msgid "Feature" msgstr "Característica" @@ -264,13 +301,18 @@ msgstr "Envío de archivos" #: apps/postybirb-ui/src/pages/submission/edit-submission-page.tsx:227 #: apps/postybirb-ui/src/pages/submission/file-submission-management-page.tsx:45 +#: apps/postybirb-ui/src/pages/submission/multi-edit-submission-page.tsx:174 msgid "File Submissions" msgstr "Cargas de Archivos" -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:158 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:160 msgid "File types do not match. Please upload a file of the same type." msgstr "Los tipos de archivo no coinciden. Por favor, cargue un archivo del mismo tipo." +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:97 +msgid "File will be modified to support website requirements" +msgstr "Se modificará para cumplir los requisitos de la página web" + #: apps/postybirb-ui/src/components/submissions/directory-watchers-view/directory-watchers-view.tsx:65 msgid "Folder" msgstr "Carpeta" @@ -289,7 +331,7 @@ msgstr "Regresar" msgid "Group Name" msgstr "Nombre del grupo" -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:101 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:104 msgid "Height" msgstr "Altura" @@ -303,7 +345,7 @@ msgstr "Página Principal" msgid "Home Page" msgstr "Página de inicio" -#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:234 +#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:242 msgid "Hours" msgstr "Horas" @@ -317,6 +359,10 @@ msgctxt "override-default" msgid "Ignore default tags" msgstr "Ignorar etiquetas predeterminadas" +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:66 +msgid "Image" +msgstr "" + #: apps/postybirb-ui/src/components/form/fields/description-field.tsx:42 msgid "Insert tags at end" msgstr "Insertar etiquetas al final" @@ -329,7 +375,7 @@ msgstr "Insertar título al inicio" msgid "Invalid CRON string" msgstr "String CRON inválido" -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:156 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:158 msgid "Invalid file type" msgstr "Tipo de archivo no válido" @@ -341,22 +387,27 @@ msgstr "Conectado como" msgid "Login" msgstr "Conectarse" +#: apps/postybirb-ui/src/pages/submission/multi-edit-submission-page.tsx:108 +msgid "Merge" +msgstr "" + #: apps/postybirb-ui/src/app/postybirb-layout/postybirb-spotlight/postybirb-spotlight.tsx:64 msgid "Message submissions" msgstr "Envíos de mensajes" #: apps/postybirb-ui/src/pages/submission/edit-submission-page.tsx:229 #: apps/postybirb-ui/src/pages/submission/message-submission-management-page.tsx:94 +#: apps/postybirb-ui/src/pages/submission/multi-edit-submission-page.tsx:176 msgid "Message Submissions" msgstr "Publicaciones de Mensajes" -#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:242 +#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:250 msgid "Minutes" msgstr "Minutos" #: apps/postybirb-ui/src/app/postybirb-layout/drawers/account-drawer/website-card.tsx:102 #: apps/postybirb-ui/src/app/postybirb-layout/drawers/account-drawer/website-card.tsx:171 -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:36 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:39 msgid "Name" msgstr "Usuario" @@ -398,14 +449,22 @@ msgctxt "internalSchedule.single" msgid "Once" msgstr "Una vez" -#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:265 +#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:273 msgid "Only set scheduled date" msgstr "Solo establecer fecha programada" +#: apps/postybirb-ui/src/pages/submission/multi-edit-submission-page.tsx:126 +msgid "Only use website options specified and delete website options missing from multi-update form." +msgstr "" + #: apps/postybirb-ui/src/app/postybirb-layout/drawers/settings-drawer.tsx:72 msgid "Open PostyBirb on computer startup" msgstr "Abrir PostyBirb al iniciar el ordenador" +#: apps/postybirb-ui/src/pages/submission/multi-edit-submission-page.tsx:116 +msgid "Overwrite overlapping website options only. This will keep any website options that already exist and only overwrite ones specified in the multi-update form." +msgstr "" + #: apps/postybirb-ui/src/pages/not-found/not-found.tsx:10 msgid "Page not found" msgstr "Página no encontrada" @@ -432,7 +491,7 @@ msgid "Post Selected" msgstr "Post seleccionado" #. Main file data -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:145 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:147 msgid "Primary" msgstr "Primario" @@ -455,7 +514,7 @@ msgctxt "import.override-title" msgid "Replace title" msgstr "Reemplazar Título" -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:80 +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:118 msgid "Requires at least {minLength} tags" msgstr "Requiere al menos {minLength} etiquetas" @@ -488,14 +547,14 @@ msgstr "Guardar valores predeterminados" msgid "Save folder upload changes" msgstr "Guardar cambios de carpeta de publicación" -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-edit-form.tsx:146 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-edit-form.tsx:153 #: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-card-actions.tsx:129 -#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-card.tsx:144 +#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-card.tsx:141 #: apps/postybirb-ui/src/pages/submission/edit-submission-page.tsx:91 msgid "Schedule" msgstr "Programar" -#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:201 +#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:209 msgctxt "schedule.modal-header" msgid "Schedule" msgstr "Programar" @@ -505,7 +564,7 @@ msgid "Schedule Many" msgstr "Programar varios" #: apps/postybirb-ui/src/app/postybirb-layout/postybirb-layout.tsx:148 -#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-actions/submission-view-actions.tsx:56 +#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-actions/submission-view-actions.tsx:57 msgid "Search" msgstr "Buscar" @@ -522,7 +581,7 @@ msgstr "Enviar mensajes" msgid "Settings" msgstr "Ajustes" -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:43 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:46 msgctxt "submission.file-size" msgid "Size" msgstr "Tamaño" @@ -535,7 +594,7 @@ msgstr "Español" msgid "Species" msgstr "Especies" -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:174 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:177 msgid "Spoiler Text" msgstr "Texto del spoiler" @@ -561,11 +620,12 @@ msgstr "Publicación programada" msgid "Submission templates" msgstr "Plantillas de envío" -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:31 +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:35 msgid "Submission will be split into {expectedBatchesToCreate} different submissions with {maxBatchSize} files each." msgstr "El envío se dividirá en {expectedBatchesToCreate} envíos diferentes con {maxBatchSize} archivos cada uno." #: apps/postybirb-ui/src/components/submission-templates/template-picker-modal/template-picker-modal.tsx:114 +#: apps/postybirb-ui/src/components/submissions/submission-picker/submission-picker.tsx:67 #: apps/postybirb-ui/src/pages/submission/file-submission-management-page.tsx:48 #: apps/postybirb-ui/src/pages/submission/message-submission-management-page.tsx:97 msgid "Submissions" @@ -593,7 +653,7 @@ msgstr "Grupo de etiquetas actualizado" msgid "Tag Groups" msgstr "Grupos de Etiquetas" -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:70 +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:108 msgid "Tag limit reached ({currentLength} / {maxLength})" msgstr "Se alcanzó el límite de etiquetas ({currentLength} / {maxLength})" @@ -603,17 +663,18 @@ msgstr "Se alcanzó el límite de etiquetas ({currentLength} / {maxLength})" msgid "Tags" msgstr "Etiquetas" -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:92 +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:130 msgid "Tags longer than {maxLength} characters will be skipped" msgstr "Se omitirán las etiquetas con más de {maxLength} caracteres" #: apps/postybirb-ui/src/components/submissions/directory-watchers-view/directory-watchers-view.tsx:98 -#: apps/postybirb-ui/src/models/dtos/submission.dto.ts:126 +#: apps/postybirb-ui/src/models/dtos/submission.dto.ts:130 msgid "Template" msgstr "Plantilla" #: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-actions/apply-submission-template-action.tsx:48 #: apps/postybirb-ui/src/pages/submission/edit-submission-page.tsx:118 +#: apps/postybirb-ui/src/pages/submission/multi-edit-submission-page.tsx:46 msgid "Template applied" msgstr "Plantilla aplicada" @@ -636,6 +697,10 @@ msgstr "Se ha actualizado el nombre de la plantilla" msgid "Templates" msgstr "Plantillas" +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:72 +msgid "Text" +msgstr "" + #: apps/postybirb-ui/src/app/postybirb-layout/drawers/settings-drawer.tsx:105 msgid "This is the folder where the app will store its data. You must restart the app for this to take effect." msgstr "Esta es la carpeta donde la aplicación guarda sus datos. Debe de reiniciar la aplicación para que esto tome efecto." @@ -644,7 +709,7 @@ msgstr "Esta es la carpeta donde la aplicación guarda sus datos. Debe de reinic msgid "This will clear all data associated with this account. You will need to log in again." msgstr "Esto borrará todos los datos asociados a esta cuenta. Deberá iniciar sesión nuevamente." -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:195 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:197 msgid "Thumbnail" msgstr "Miniatura" @@ -652,30 +717,32 @@ msgstr "Miniatura" msgid "Title" msgstr "Título" -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:104 +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:142 msgid "Title is too long" msgstr "El título es demasiado largo" -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:102 +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:140 msgid "Title is too long and will be truncated" msgstr "El título es demasiado largo y se truncará" -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:118 +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:156 msgid "Title is too short" msgstr "El título es demasiado corto" -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:136 +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:174 msgid "Translation {id} not found" msgstr "Traducción {id} no encontrada" -#: apps/postybirb-ui/src/components/form/website-option-form/website-option-form.tsx:231 +#: apps/postybirb-ui/src/components/form/website-option-form/website-option-form.tsx:239 msgid "Unable to display form" msgstr "No se puede mostrar el formulario" #: apps/postybirb-ui/src/app/postybirb-layout/drawers/account-drawer/website-card.tsx:116 #: apps/postybirb-ui/src/components/submission-templates/template-picker-modal/template-picker-modal.tsx:104 #: apps/postybirb-ui/src/components/submission-templates/template-picker-modal/template-picker-modal.tsx:145 -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-edit-form.tsx:90 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-edit-form.tsx:97 +#: apps/postybirb-ui/src/components/submissions/submission-picker/submission-picker.tsx:55 +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:78 msgid "Unknown" msgstr "Desconocido" @@ -688,16 +755,24 @@ msgstr "Publicación desconocida" msgid "Unschedule" msgstr "Sin programar" -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:42 +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:45 +msgid "Unsupported file type {fileExtension}. Please provide fallback text." +msgstr "" + +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:55 msgid "Unsupported file type {mimeType}" msgstr "Archivo no compatible {mimeType}" +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:81 +msgid "Unsupported submission type: {fileTypeString}" +msgstr "" + #: apps/postybirb-ui/src/app/postybirb-layout/drawers/tag-converter-drawer.tsx:91 #: apps/postybirb-ui/src/app/postybirb-layout/drawers/tag-group-drawer.tsx:98 msgid "Update" msgstr "Actualizar" -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:184 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:186 msgid "Update file" msgstr "Actualizar archivo" @@ -705,11 +780,15 @@ msgstr "Actualizar archivo" msgid "Update PostyBirb" msgstr "Actualizar PostyBirb" -#: apps/postybirb-ui/src/components/submissions/submission-uploader/submission-uploader.tsx:176 +#: apps/postybirb-ui/src/pages/submission/multi-edit-submission-page.tsx:94 +msgid "Updates applied" +msgstr "" + +#: apps/postybirb-ui/src/components/submissions/submission-uploader/submission-uploader.tsx:179 msgid "Upload" msgstr "Cargar" -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:233 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:235 msgid "Upload new thumbnail" msgstr "Cargar nueva miniatura" @@ -721,6 +800,10 @@ msgstr "Utilizar una descripción personalizada" msgid "Use thumbnail" msgstr "Usar miniaturas" +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:69 +msgid "Video" +msgstr "" + #: apps/postybirb-ui/src/website-components/discord/discord-login-view.tsx:86 msgid "Webhook is required" msgstr "Se requiere webhook" @@ -730,11 +813,11 @@ msgid "Webhook name {name} is invalid" msgstr "Nombre {name} del Webhook no es correcto" #: apps/postybirb-ui/src/app/postybirb-layout/drawers/account-drawer/website-visibility-picker.tsx:19 -#: apps/postybirb-ui/src/components/form/website-select/website-select.tsx:51 +#: apps/postybirb-ui/src/components/form/website-select/website-select.tsx:52 msgid "Websites" msgstr "Páginas web" -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:122 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:125 msgid "Width" msgstr "Ancho" @@ -742,6 +825,6 @@ msgstr "Ancho" msgid "Your browser does not support the audio tag." msgstr "Su navegador no es compatible con la etiqueta de audio." -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:50 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:52 msgid "Your browser does not support the video tag." msgstr "Su navegador no es compatible con la etiqueta de video." diff --git a/lang/ru.po b/lang/ru.po index 946e28d3b..33b485d12 100644 --- a/lang/ru.po +++ b/lang/ru.po @@ -1,12 +1,18 @@ msgid "" -msgstr "Project-Id-Version: \nReport-Msgid-Bugs-To: \nPOT-Creation-Date: \nPO-Revision-Date: 2024-11-12 13:23+0000\nLast-Translator: Anonymous \nLanguage-Team: Russian \nLanguage: ru\nContent-Type: \nContent-Transfer-Encoding: \nPlural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\nX-Generator: Weblate 5.8.2\n" - -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:53 -msgid "{fileName} ({fileSizeString}) is too large (max {maxFileSizeString}) and an attempt will be made to reduce size when posting" msgstr "" - -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:63 -msgid "{fileName} will be modified to support website requirements" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: \n" +"PO-Revision-Date: \n" +"Last-Translator: \n" +"Language: \n" +"Language-Team: \n" +"Content-Type: \n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:89 +msgid "({fileSizeString}) is too large (max {maxFileSizeString}) and an attempt will be made to reduce size when posting" msgstr "" #: apps/postybirb-ui/src/app/postybirb-layout/drawers/account-drawer/account-drawer.tsx:50 @@ -34,7 +40,7 @@ msgstr "Вставлять рекламу PostyBirb в описания пост msgid "Allow resizing image" msgstr "Масштабировать изображения" -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:163 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:166 msgid "Alt Text" msgstr "Alt текст" @@ -46,16 +52,22 @@ msgstr "Папка приложения" msgid "App Server Port" msgstr "Порт сервера" -#: apps/postybirb-ui/src/components/submission-templates/template-picker-modal/template-picker-modal.tsx:283 -#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:275 +#: apps/postybirb-ui/src/components/submission-templates/template-picker-modal/template-picker-modal.tsx:290 +#: apps/postybirb-ui/src/components/submissions/submission-picker/submission-picker-modal.tsx:51 +#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:283 msgid "Apply" msgstr "Применить" #: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-actions/apply-submission-template-action.tsx:25 #: apps/postybirb-ui/src/pages/submission/edit-submission-page.tsx:138 +#: apps/postybirb-ui/src/pages/submission/multi-edit-submission-page.tsx:66 msgid "Apply Template" msgstr "Применить шаблон" +#: apps/postybirb-ui/src/pages/submission/multi-edit-submission-page.tsx:142 +msgid "Apply to Submissions" +msgstr "" + #: apps/postybirb-ui/src/components/shared/delete-action-popover/delete-action-popover.tsx:24 #: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-delete-action.tsx:32 #: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-actions/delete-submissions-action.tsx:38 @@ -66,13 +78,18 @@ msgstr "Вы уверены, что хотите удалить? Это дейс msgid "Are you sure you want to post all selected submissions?" msgstr "Вы уверены, что хотите опубликовать все выбранные посты?" +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:75 +msgid "Audio" +msgstr "" + #: apps/postybirb-ui/src/pages/submission/file-submission-management-page.tsx:58 msgid "Auto Importers (File Watcher)" msgstr "Авто-импортеры (При изменении папки)" -#: apps/postybirb-ui/src/components/submission-templates/template-picker-modal/template-picker-modal.tsx:253 +#: apps/postybirb-ui/src/components/submission-templates/template-picker-modal/template-picker-modal.tsx:260 +#: apps/postybirb-ui/src/components/submissions/submission-picker/submission-picker-modal.tsx:43 #: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-card-actions.tsx:173 -#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:272 +#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:280 #: apps/postybirb-ui/src/pages/submission/edit-submission-page.tsx:157 msgid "Cancel" msgstr "Отмена" @@ -81,6 +98,10 @@ msgstr "Отмена" msgid "Choose Fields" msgstr "Выберите поля" +#: apps/postybirb-ui/src/components/submissions/submission-picker/submission-picker-modal.tsx:26 +msgid "Choose Submissions" +msgstr "" + #: apps/postybirb-ui/src/components/submission-templates/template-picker-modal/template-picker-modal.tsx:211 msgctxt "template.picker-modal-header" msgid "Choose Templates" @@ -132,19 +153,19 @@ msgstr "Формат CRON" #: apps/postybirb-ui/src/components/submissions/submission-uploader/edit-image-modal.tsx:72 #: apps/postybirb-ui/src/components/submissions/submission-uploader/edit-image-modal.tsx:141 -#: apps/postybirb-ui/src/components/submissions/submission-uploader/submission-uploader.tsx:107 +#: apps/postybirb-ui/src/components/submissions/submission-uploader/submission-uploader.tsx:110 msgid "Crop" msgstr "Обрезать" -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:212 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:214 msgid "Crop new thumbnail from original file" msgstr "Выбрать миниатюру из оригинального файла" -#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:215 +#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:223 msgid "Date" msgstr "Дата" -#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:226 +#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:234 msgid "Days" msgstr "Дни" @@ -153,7 +174,7 @@ msgstr "Дни" #: apps/postybirb-ui/src/components/submission-templates/template-picker-modal/template-picker-modal.tsx:61 #: apps/postybirb-ui/src/components/submission-templates/template-picker-modal/template-picker-modal.tsx:63 #: apps/postybirb-ui/src/components/submission-templates/template-picker-modal/template-picker-modal.tsx:154 -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-edit-form.tsx:83 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-edit-form.tsx:90 msgid "Default" msgstr "По умолчанию" @@ -165,7 +186,7 @@ msgstr "Опции по умолчанию сохранены" #: apps/postybirb-ui/src/components/shared/delete-action-popover/delete-action-popover.tsx:16 #: apps/postybirb-ui/src/components/shared/delete-action-popover/delete-action-popover.tsx:38 #: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-delete-action.tsx:46 -#: apps/postybirb-ui/src/components/submissions/submission-uploader/submission-uploader.tsx:113 +#: apps/postybirb-ui/src/components/submissions/submission-uploader/submission-uploader.tsx:116 #: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-actions/delete-submissions-action.tsx:52 msgid "Delete" msgstr "Удалить" @@ -174,11 +195,11 @@ msgstr "Удалить" msgid "Description" msgstr "Описание" -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:19 +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:23 msgid "Description is greater than {maxLength} characters long" msgstr "Описание больше, чем {maxLength} символов длиной" -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:25 +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:29 msgid "Description is less than {minLength} characters long" msgstr "" @@ -186,15 +207,15 @@ msgstr "" msgid "Description Settings" msgstr "Настройки описания" -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:152 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:155 msgid "Don't post to" msgstr "Не публиковать в" -#: apps/postybirb-ui/src/components/submissions/submission-uploader/submission-uploader.tsx:273 -msgid "Drag images here or click to select files" -msgstr "Перетащите сюда изображения или нажмите для выбора файлов" +#: apps/postybirb-ui/src/components/submissions/submission-uploader/submission-uploader.tsx:272 +msgid "Drag files here or click to select files" +msgstr "" -#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:105 +#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:109 msgid "Drag or use arrow keys to change order." msgstr "Перетащите или используйте стрелки для изменения порядка." @@ -207,6 +228,14 @@ msgstr "Дублировать" msgid "Edit" msgstr "Редактировать" +#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-actions/multi-edit-submissions-action.tsx:23 +msgid "Edit Many" +msgstr "" + +#: apps/postybirb-ui/src/pages/submission/multi-edit-submission-page.tsx:170 +msgid "Edit Multiple" +msgstr "" + #: apps/postybirb-ui/src/app/languages.tsx:6 msgid "English" msgstr "Английский" @@ -240,6 +269,10 @@ msgstr "Не удалось поставить пост в очередь" msgid "Failed to save defaults" msgstr "Не удалось сохранить по умолчанию" +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:197 +msgid "Failed to save file metadata" +msgstr "" + #: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-card-actions.tsx:122 #: apps/postybirb-ui/src/pages/submission/edit-submission-page.tsx:84 msgid "Failed to schedule submission" @@ -250,10 +283,14 @@ msgstr "Не удалось отложить пост" msgid "Failed to update" msgstr "Не удалось обновить" -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:13 +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:17 msgid "Failed to validate submission: {message}" msgstr "" +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-text-alt.tsx:68 +msgid "Fallback Text" +msgstr "" + #: apps/postybirb-ui/src/components/form/fields/field-translations.ts:16 msgid "Feature" msgstr "" @@ -264,13 +301,18 @@ msgstr "Посты с файлами" #: apps/postybirb-ui/src/pages/submission/edit-submission-page.tsx:227 #: apps/postybirb-ui/src/pages/submission/file-submission-management-page.tsx:45 +#: apps/postybirb-ui/src/pages/submission/multi-edit-submission-page.tsx:174 msgid "File Submissions" msgstr "Посты с файлами" -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:158 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:160 msgid "File types do not match. Please upload a file of the same type." msgstr "Типы файлов не совпадают. Пожалуйста, загрузите файл того же типа." +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:97 +msgid "File will be modified to support website requirements" +msgstr "" + #: apps/postybirb-ui/src/components/submissions/directory-watchers-view/directory-watchers-view.tsx:65 msgid "Folder" msgstr "Папка" @@ -289,7 +331,7 @@ msgstr "Вернуться назад" msgid "Group Name" msgstr "Имя группы" -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:101 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:104 msgid "Height" msgstr "Высота" @@ -303,7 +345,7 @@ msgstr "Главная" msgid "Home Page" msgstr "Главная страница" -#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:234 +#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:242 msgid "Hours" msgstr "Часы" @@ -317,6 +359,10 @@ msgctxt "override-default" msgid "Ignore default tags" msgstr "Игнорировать теги по умолчанию" +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:66 +msgid "Image" +msgstr "" + #: apps/postybirb-ui/src/components/form/fields/description-field.tsx:42 msgid "Insert tags at end" msgstr "Вставить теги в конце" @@ -329,7 +375,7 @@ msgstr "Вставить заголовок в начале" msgid "Invalid CRON string" msgstr "Неверная строка CRON" -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:156 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:158 msgid "Invalid file type" msgstr "Неверный тип файла" @@ -341,22 +387,27 @@ msgstr "Выполнен вход как" msgid "Login" msgstr "Войти" +#: apps/postybirb-ui/src/pages/submission/multi-edit-submission-page.tsx:108 +msgid "Merge" +msgstr "" + #: apps/postybirb-ui/src/app/postybirb-layout/postybirb-spotlight/postybirb-spotlight.tsx:64 msgid "Message submissions" msgstr "Текстовые посты" #: apps/postybirb-ui/src/pages/submission/edit-submission-page.tsx:229 #: apps/postybirb-ui/src/pages/submission/message-submission-management-page.tsx:94 +#: apps/postybirb-ui/src/pages/submission/multi-edit-submission-page.tsx:176 msgid "Message Submissions" msgstr "Посты с текстом" -#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:242 +#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:250 msgid "Minutes" msgstr "Минуты" #: apps/postybirb-ui/src/app/postybirb-layout/drawers/account-drawer/website-card.tsx:102 #: apps/postybirb-ui/src/app/postybirb-layout/drawers/account-drawer/website-card.tsx:171 -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:36 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:39 msgid "Name" msgstr "Имя" @@ -398,14 +449,22 @@ msgctxt "internalSchedule.single" msgid "Once" msgstr "Один раз" -#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:265 +#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:273 msgid "Only set scheduled date" msgstr "Только установленная дата" +#: apps/postybirb-ui/src/pages/submission/multi-edit-submission-page.tsx:126 +msgid "Only use website options specified and delete website options missing from multi-update form." +msgstr "" + #: apps/postybirb-ui/src/app/postybirb-layout/drawers/settings-drawer.tsx:72 msgid "Open PostyBirb on computer startup" msgstr "Открыть PostyBirb при запуске компьютера" +#: apps/postybirb-ui/src/pages/submission/multi-edit-submission-page.tsx:116 +msgid "Overwrite overlapping website options only. This will keep any website options that already exist and only overwrite ones specified in the multi-update form." +msgstr "" + #: apps/postybirb-ui/src/pages/not-found/not-found.tsx:10 msgid "Page not found" msgstr "Страница не найдена" @@ -432,7 +491,7 @@ msgid "Post Selected" msgstr "" #. Main file data -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:145 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:147 msgid "Primary" msgstr "Основные значения" @@ -455,9 +514,9 @@ msgctxt "import.override-title" msgid "Replace title" msgstr "Заменить заголовок" -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:80 +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:118 msgid "Requires at least {minLength} tags" -msgstr "Нужно минимум {minLength} тегов" +msgstr "" #: apps/postybirb-ui/src/app/postybirb-layout/drawers/account-drawer/website-card.tsx:48 msgid "Reset" @@ -488,14 +547,14 @@ msgstr "Сохранить по умолчанию" msgid "Save folder upload changes" msgstr "Сохранить изменения загрузки папки" -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-edit-form.tsx:146 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-edit-form.tsx:153 #: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-card-actions.tsx:129 -#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-card.tsx:144 +#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-card.tsx:141 #: apps/postybirb-ui/src/pages/submission/edit-submission-page.tsx:91 msgid "Schedule" msgstr "Отложить" -#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:201 +#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-multi-scheduler-modal.tsx:209 msgctxt "schedule.modal-header" msgid "Schedule" msgstr "Отложить" @@ -505,7 +564,7 @@ msgid "Schedule Many" msgstr "Отложить несколько" #: apps/postybirb-ui/src/app/postybirb-layout/postybirb-layout.tsx:148 -#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-actions/submission-view-actions.tsx:56 +#: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-actions/submission-view-actions.tsx:57 msgid "Search" msgstr "Поиск" @@ -522,7 +581,7 @@ msgstr "Сообщения" msgid "Settings" msgstr "Настройки" -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:43 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:46 msgctxt "submission.file-size" msgid "Size" msgstr "Размер" @@ -535,7 +594,7 @@ msgstr "Испанский" msgid "Species" msgstr "Виды" -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:174 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:177 msgid "Spoiler Text" msgstr "Спойлер" @@ -561,11 +620,12 @@ msgstr "Пост отложен" msgid "Submission templates" msgstr "Шаблоны постов" -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:31 +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:35 msgid "Submission will be split into {expectedBatchesToCreate} different submissions with {maxBatchSize} files each." msgstr "" #: apps/postybirb-ui/src/components/submission-templates/template-picker-modal/template-picker-modal.tsx:114 +#: apps/postybirb-ui/src/components/submissions/submission-picker/submission-picker.tsx:67 #: apps/postybirb-ui/src/pages/submission/file-submission-management-page.tsx:48 #: apps/postybirb-ui/src/pages/submission/message-submission-management-page.tsx:97 msgid "Submissions" @@ -593,7 +653,7 @@ msgstr "Группа тегов обновлена" msgid "Tag Groups" msgstr "Группы тегов" -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:70 +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:108 msgid "Tag limit reached ({currentLength} / {maxLength})" msgstr "" @@ -603,17 +663,18 @@ msgstr "" msgid "Tags" msgstr "Теги" -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:92 +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:130 msgid "Tags longer than {maxLength} characters will be skipped" msgstr "" #: apps/postybirb-ui/src/components/submissions/directory-watchers-view/directory-watchers-view.tsx:98 -#: apps/postybirb-ui/src/models/dtos/submission.dto.ts:126 +#: apps/postybirb-ui/src/models/dtos/submission.dto.ts:130 msgid "Template" msgstr "Шаблон" #: apps/postybirb-ui/src/components/submissions/submission-view/submission-view-card/submission-view-actions/apply-submission-template-action.tsx:48 #: apps/postybirb-ui/src/pages/submission/edit-submission-page.tsx:118 +#: apps/postybirb-ui/src/pages/submission/multi-edit-submission-page.tsx:46 msgid "Template applied" msgstr "" @@ -636,6 +697,10 @@ msgstr "" msgid "Templates" msgstr "Шаблоны" +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:72 +msgid "Text" +msgstr "" + #: apps/postybirb-ui/src/app/postybirb-layout/drawers/settings-drawer.tsx:105 msgid "This is the folder where the app will store its data. You must restart the app for this to take effect." msgstr "Это папка, где приложение будет хранить данные. Вы должны перезапустить приложение чтобы применить изменения." @@ -644,7 +709,7 @@ msgstr "Это папка, где приложение будет хранить msgid "This will clear all data associated with this account. You will need to log in again." msgstr "" -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:195 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:197 msgid "Thumbnail" msgstr "Набросок (Маленькая картинка)" @@ -652,30 +717,32 @@ msgstr "Набросок (Маленькая картинка)" msgid "Title" msgstr "Название" -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:104 +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:142 msgid "Title is too long" msgstr "Название слишком длинное" -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:102 +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:140 msgid "Title is too long and will be truncated" msgstr "Название слишком длинное и будет сокращено" -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:118 +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:156 msgid "Title is too short" msgstr "Название слишком короткое" -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:136 +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:174 msgid "Translation {id} not found" msgstr "Перевод {id} не найден" -#: apps/postybirb-ui/src/components/form/website-option-form/website-option-form.tsx:231 +#: apps/postybirb-ui/src/components/form/website-option-form/website-option-form.tsx:239 msgid "Unable to display form" msgstr "Не удалось показать форму" #: apps/postybirb-ui/src/app/postybirb-layout/drawers/account-drawer/website-card.tsx:116 #: apps/postybirb-ui/src/components/submission-templates/template-picker-modal/template-picker-modal.tsx:104 #: apps/postybirb-ui/src/components/submission-templates/template-picker-modal/template-picker-modal.tsx:145 -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-edit-form.tsx:90 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-edit-form.tsx:97 +#: apps/postybirb-ui/src/components/submissions/submission-picker/submission-picker.tsx:55 +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:78 msgid "Unknown" msgstr "Неизвестно" @@ -688,16 +755,24 @@ msgstr "Неизвестный пост" msgid "Unschedule" msgstr "" -#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:42 +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:45 +msgid "Unsupported file type {fileExtension}. Please provide fallback text." +msgstr "" + +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:55 msgid "Unsupported file type {mimeType}" msgstr "Неподдерживаемый тип файла {mimeType}" +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:81 +msgid "Unsupported submission type: {fileTypeString}" +msgstr "" + #: apps/postybirb-ui/src/app/postybirb-layout/drawers/tag-converter-drawer.tsx:91 #: apps/postybirb-ui/src/app/postybirb-layout/drawers/tag-group-drawer.tsx:98 msgid "Update" msgstr "Обновить" -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:184 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:186 msgid "Update file" msgstr "Загрузить файл" @@ -705,11 +780,15 @@ msgstr "Загрузить файл" msgid "Update PostyBirb" msgstr "Обновить PostyBirb" -#: apps/postybirb-ui/src/components/submissions/submission-uploader/submission-uploader.tsx:176 +#: apps/postybirb-ui/src/pages/submission/multi-edit-submission-page.tsx:94 +msgid "Updates applied" +msgstr "" + +#: apps/postybirb-ui/src/components/submissions/submission-uploader/submission-uploader.tsx:179 msgid "Upload" msgstr "Загрузить" -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:233 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:235 msgid "Upload new thumbnail" msgstr "Загрузить новую миниатюру" @@ -721,6 +800,10 @@ msgstr "Кастомное описание" msgid "Use thumbnail" msgstr "Использовать миниатюру" +#: apps/postybirb-ui/src/components/translations/validation-translation.tsx:69 +msgid "Video" +msgstr "" + #: apps/postybirb-ui/src/website-components/discord/discord-login-view.tsx:86 msgid "Webhook is required" msgstr "" @@ -730,11 +813,11 @@ msgid "Webhook name {name} is invalid" msgstr "Имя Webhook'а {name} недопустимо" #: apps/postybirb-ui/src/app/postybirb-layout/drawers/account-drawer/website-visibility-picker.tsx:19 -#: apps/postybirb-ui/src/components/form/website-select/website-select.tsx:51 +#: apps/postybirb-ui/src/components/form/website-select/website-select.tsx:52 msgid "Websites" msgstr "Сайты" -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:122 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-metadata-manager.tsx:125 msgid "Width" msgstr "Ширина" @@ -742,6 +825,6 @@ msgstr "Ширина" msgid "Your browser does not support the audio tag." msgstr "Ваш браузер не поддерживает аудио-тег." -#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:50 +#: apps/postybirb-ui/src/components/submissions/submission-edit-form/submission-file-manager/submission-file-card/file-card-file-actions.tsx:52 msgid "Your browser does not support the video tag." msgstr "Ваш браузер не поддерживает тег видео." diff --git a/libs/types/src/dtos/index.ts b/libs/types/src/dtos/index.ts index 8eddf3e0e..8e8443c57 100644 --- a/libs/types/src/dtos/index.ts +++ b/libs/types/src/dtos/index.ts @@ -14,6 +14,7 @@ export * from './submission/apply-multi-submission.dto'; export * from './submission/create-submission.dto'; export * from './submission/submission-file.dto'; export * from './submission/submission.dto'; +export * from './submission/update-alt-file.dto'; export * from './submission/update-submission-template-name.dto'; export * from './submission/update-submission.dto'; export * from './tag/create-tag-converter.dto'; diff --git a/libs/types/src/dtos/submission/submission-file.dto.ts b/libs/types/src/dtos/submission/submission-file.dto.ts index 87a18bb81..e9b8ec58b 100644 --- a/libs/types/src/dtos/submission/submission-file.dto.ts +++ b/libs/types/src/dtos/submission/submission-file.dto.ts @@ -1,12 +1,12 @@ -import { ISubmissionFile } from '../../models'; +import { EntityId, ISubmissionFile } from '../../models'; import { IEntityDto } from '../database/entity.dto'; export type ISubmissionFileDto = Omit< IEntityDto, 'altFile' | 'thumbnail' | 'file' | 'parent' | 'submission' | 'buffer' > & { - altFile?: ISubSubmissionFileDto; - thumbnail?: ISubSubmissionFileDto; + altFile: EntityId; + thumbnail: EntityId; file: ISubSubmissionFileDto; }; diff --git a/libs/types/src/dtos/submission/update-alt-file.dto.ts b/libs/types/src/dtos/submission/update-alt-file.dto.ts new file mode 100644 index 000000000..3a0a5d13e --- /dev/null +++ b/libs/types/src/dtos/submission/update-alt-file.dto.ts @@ -0,0 +1,3 @@ +export interface IUpdateAltFileDto { + html: string; +} diff --git a/libs/types/src/models/submission/submission-file.interface.ts b/libs/types/src/models/submission/submission-file.interface.ts index 298bc0451..6eb942d84 100644 --- a/libs/types/src/models/submission/submission-file.interface.ts +++ b/libs/types/src/models/submission/submission-file.interface.ts @@ -22,30 +22,35 @@ export interface ISubmissionFile extends IFileDimensions, IEntity { * @type {ISubmission} */ submission: Rel>; + /** * Name of the file. * * @type {string} */ fileName: string; + /** * Hash of the file. * * @type {string} */ hash: string; + /** * Mime Type of the file. * * @type {string} */ mimeType: string; + /** * Reference to the buffer entity. * * @type {IFileBuffer} */ file: IFileBuffer; + /** * Optional thumbnail for the file. * Should be autogenerated if possible. @@ -53,6 +58,7 @@ export interface ISubmissionFile extends IFileDimensions, IEntity { * @type {(IFileBuffer | undefined)} */ thumbnail?: Rel; + /** * Alternate file to post instead of the main. * Primarily used for Text based submission. @@ -60,12 +66,21 @@ export interface ISubmissionFile extends IFileDimensions, IEntity { * @type {(IFileBuffer | undefined)} */ altFile?: Rel; + /** * Status flag for internal processing. * * @type {boolean} */ hasThumbnail: boolean; + + /** + * Status flag for internal processing. + * + * @type {boolean} + */ + hasAltFile: boolean; + /** * Additional props for modifying or tracking state * of the file. diff --git a/libs/types/src/models/submission/validation-result.type.ts b/libs/types/src/models/submission/validation-result.type.ts index 6073cf07e..91e7c6be6 100644 --- a/libs/types/src/models/submission/validation-result.type.ts +++ b/libs/types/src/models/submission/validation-result.type.ts @@ -1,3 +1,4 @@ +import { FileType } from '../../enums'; import { ImageResizeProps } from '../website/image-resize-props'; import { IWebsiteFormFields } from './website-form-fields.interface'; @@ -58,6 +59,13 @@ export interface ValidationMessages { 'validation.file.invalid-mime-type': { mimeType: string; acceptedMimeTypes: string[]; + fileId: string; + }; + + 'validation.file.unsupported-file-type': { + fileName: string; + fileType: FileType; + fileId: string; }; 'validation.file.file-batch-size': { @@ -65,15 +73,23 @@ export interface ValidationMessages { expectedBatchesToCreate: number; }; + 'validation.file.text-file-no-fallback': { + fileName: string; + fileExtension: string; + fileId: string; + }; + 'validation.file.file-size': { maxFileSize: number; fileSize: number; fileName: string; + fileId: string; }; 'validation.file.image-resize': { fileName: string; resizeProps: ImageResizeProps; + fileId: string; }; 'validation.description.max-length': { diff --git a/libs/types/src/website-modifiers/website-file-options.ts b/libs/types/src/website-modifiers/website-file-options.ts index e0284a9cc..54ad6ae7d 100644 --- a/libs/types/src/website-modifiers/website-file-options.ts +++ b/libs/types/src/website-modifiers/website-file-options.ts @@ -1,3 +1,5 @@ +import { FileType } from '../enums'; + export type WebsiteFileOptions = { /** * A list of accepted Mime types. @@ -30,4 +32,9 @@ export type WebsiteFileOptions = { * Defaults to 1. */ fileBatchSize?: number; + + /** + * The supported file types for the website. + */ + supportedFileTypes: FileType[]; }; diff --git a/libs/utils/file-type/src/lib/get-file-type.ts b/libs/utils/file-type/src/lib/get-file-type.ts index 70a22cd4d..654befc69 100644 --- a/libs/utils/file-type/src/lib/get-file-type.ts +++ b/libs/utils/file-type/src/lib/get-file-type.ts @@ -1,8 +1,8 @@ import { FileType } from '@postybirb/types'; -import { isAudio } from './is-audio'; -import { isImage } from './is-image'; -import { isText } from './is-text'; -import { isVideo } from './is-video'; +import { isAudio, supportsAudio } from './is-audio'; +import { isImage, supportsImage } from './is-image'; +import { isText, supportsText } from './is-text'; +import { isVideo, supportsVideo } from './is-video'; export function getFileType(filenameOrExtension: string): FileType { if (isImage(filenameOrExtension)) return FileType.IMAGE; @@ -12,3 +12,12 @@ export function getFileType(filenameOrExtension: string): FileType { return FileType.UNKNOWN; } + +export function getFileTypeFromMimeType(mimeType: string): FileType { + if (supportsImage(mimeType)) return FileType.IMAGE; + if (supportsText(mimeType)) return FileType.TEXT; + if (supportsVideo(mimeType)) return FileType.VIDEO; + if (supportsAudio(mimeType)) return FileType.AUDIO; + + return FileType.UNKNOWN; +} diff --git a/libs/utils/file-type/src/lib/is-audio.ts b/libs/utils/file-type/src/lib/is-audio.ts index f454cf576..db7c5d94f 100644 --- a/libs/utils/file-type/src/lib/is-audio.ts +++ b/libs/utils/file-type/src/lib/is-audio.ts @@ -44,3 +44,7 @@ export function isAudio(filenameOrExtension: string): boolean { const mimeType = getMimeTypeForFile(filenameOrExtension); return SUPPORTED_MIME.includes(mimeType ?? ''); } + +export function supportsAudio(mimeType: string): boolean { + return SUPPORTED_MIME.includes(mimeType); +} diff --git a/libs/utils/file-type/src/lib/is-image.ts b/libs/utils/file-type/src/lib/is-image.ts index 801b66827..14cb9449f 100644 --- a/libs/utils/file-type/src/lib/is-image.ts +++ b/libs/utils/file-type/src/lib/is-image.ts @@ -68,3 +68,7 @@ export function isImage(filenameOrExtension: string): boolean { const mimeType = getMimeTypeForFile(filenameOrExtension); return SUPPORTED_MIME.includes(mimeType ?? ''); } + +export function supportsImage(mimeType: string): boolean { + return SUPPORTED_MIME.includes(mimeType); +} diff --git a/libs/utils/file-type/src/lib/is-text.ts b/libs/utils/file-type/src/lib/is-text.ts index 90ece85ab..294d3dc8c 100644 --- a/libs/utils/file-type/src/lib/is-text.ts +++ b/libs/utils/file-type/src/lib/is-text.ts @@ -66,3 +66,7 @@ export function isText(filenameOrExtension: string): boolean { const mimeType = getMimeTypeForFile(filenameOrExtension); return SUPPORTED_MIME.includes(mimeType ?? ''); } + +export function supportsText(mimeType: string): boolean { + return SUPPORTED_MIME.includes(mimeType); +} diff --git a/libs/utils/file-type/src/lib/is-video.ts b/libs/utils/file-type/src/lib/is-video.ts index 80e4822b1..8c76774c4 100644 --- a/libs/utils/file-type/src/lib/is-video.ts +++ b/libs/utils/file-type/src/lib/is-video.ts @@ -38,3 +38,7 @@ export function isVideo(filenameOrExtension: string): boolean { const mimeType = getMimeTypeForFile(filenameOrExtension); return SUPPORTED_MIME.includes(mimeType ?? ''); } + +export function supportsVideo(mimeType: string): boolean { + return SUPPORTED_MIME.includes(mimeType); +} diff --git a/package.json b/package.json index 2f3dad979..c677680f6 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "@blocknote/core": "^0.15.11", "@blocknote/mantine": "^0.15.11", "@blocknote/react": "^0.15.11", + "@iarna/rtf-to-html": "^1.1.0", "@lingui/core": "^4.7.0", "@lingui/macro": "^4.7.0", "@lingui/react": "^4.7.0", @@ -82,8 +83,11 @@ "form-urlencoded": "^6.1.5", "hasha": "^5.2.2", "history": "^5.3.0", + "html-to-text": "^9.0.5", + "js-beautify": "^1.15.1", "lodash": "^4.17.21", "loglayer": "^4.8.0", + "mammoth": "^1.8.0", "mime": "^3.0.0", "minimist": "^1.2.8", "moment": "^2.30.1", @@ -203,4 +207,4 @@ "webpack-node-externals": "^3.0.0" }, "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" -} \ No newline at end of file +} diff --git a/yarn.lock b/yarn.lock index 9332d0441..57bf4299a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2419,6 +2419,13 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== +"@iarna/rtf-to-html@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@iarna/rtf-to-html/-/rtf-to-html-1.1.0.tgz#f6950f376865f995e9b8d815fe89e9bf3e1236ee" + integrity sha512-2bmUxotlWMfDMkjMidIMyoQfuK+QitW0Ys2T098WrgBvPZUV5NN5EvD1vuONrmAtweCDwagmQf6NxRtSbq+siQ== + dependencies: + rtf-parser "^1.0.4" + "@img/sharp-darwin-arm64@0.33.5": version "0.33.5" resolved "https://registry.yarnpkg.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz#ef5b5a07862805f1e8145a377c8ba6e98813ca08" @@ -3660,6 +3667,11 @@ tslib "^2.3.0" yargs-parser "21.1.1" +"@one-ini/wasm@0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@one-ini/wasm/-/wasm-0.1.1.tgz#6013659736c9dbfccc96e8a9c2b3de317df39323" + integrity sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw== + "@opentelemetry/api-logs@0.52.1": version "0.52.1" resolved "https://registry.yarnpkg.com/@opentelemetry/api-logs/-/api-logs-0.52.1.tgz#52906375da4d64c206b0c4cb8ffa209214654ecc" @@ -4261,6 +4273,14 @@ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz#0574d7e87b44ee8511d08cc7f914bcb802b70818" integrity sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw== +"@selderee/plugin-htmlparser2@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.11.0.tgz#d5b5e29a7ba6d3958a1972c7be16f4b2c188c517" + integrity sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ== + dependencies: + domhandler "^5.0.3" + selderee "^0.11.0" + "@sinclair/typebox@^0.24.1": version "0.24.51" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.51.tgz#645f33fe4e02defe26f2f5c0410e1c094eac7f5f" @@ -5626,7 +5646,7 @@ "@webassemblyjs/ast" "1.12.1" "@xtuc/long" "4.2.2" -"@xmldom/xmldom@^0.8.8": +"@xmldom/xmldom@^0.8.6", "@xmldom/xmldom@^0.8.8": version "0.8.10" resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.10.tgz#a1337ca426aa61cef9fe15b5b28e340a72f6fa99" integrity sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw== @@ -5684,6 +5704,11 @@ abbrev@1: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== +abbrev@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-2.0.0.tgz#cf59829b8b4f03f89dda2771cb7f3653828c89bf" + integrity sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ== + abort-controller@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" @@ -6006,7 +6031,7 @@ arg@^5.0.2: resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== -argparse@^1.0.7: +argparse@^1.0.7, argparse@~1.0.3: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== @@ -6446,6 +6471,11 @@ bluebird@^3.5.5, bluebird@^3.7.2: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== +bluebird@~3.4.0: + version "3.4.7" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3" + integrity sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA== + body-parser@1.20.1: version "1.20.1" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" @@ -7261,6 +7291,14 @@ confbox@^0.1.7: resolved "https://registry.yarnpkg.com/confbox/-/confbox-0.1.7.tgz#ccfc0a2bcae36a84838e83a3b7f770fb17d6c579" integrity sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA== +config-chain@^1.1.13: + version "1.1.13" + resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4" + integrity sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ== + dependencies: + ini "^1.3.4" + proto-list "~1.2.1" + config-file-ts@^0.2.4: version "0.2.6" resolved "https://registry.yarnpkg.com/config-file-ts/-/config-file-ts-0.2.6.tgz#b424ff74612fb37f626d6528f08f92ddf5d22027" @@ -8040,6 +8078,11 @@ diff@^5.0.0: resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.0.tgz#26ded047cd1179b78b9537d5ef725503ce1ae531" integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A== +dingbat-to-unicode@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dingbat-to-unicode/-/dingbat-to-unicode-1.0.1.tgz#5091dd673241453e6b5865e26e5a4452cdef5c83" + integrity sha512-98l0sW87ZT58pU4i61wa2OHwxbiYSbuxsCBozaVnYX2iCnr3bLM3fIes1/ej7h1YdOKuKt/MLs706TVnALA65w== + dir-compare@^3.0.0: version "3.3.0" resolved "https://registry.yarnpkg.com/dir-compare/-/dir-compare-3.3.0.tgz#2c749f973b5c4b5d087f11edaae730db31788416" @@ -8229,6 +8272,13 @@ dotenv@~16.3.1: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.2.tgz#3cb611ce5a63002dbabf7c281bc331f69d28f03f" integrity sha512-HTlk5nmhkm8F6JcdXvHIzaorzCoziNQT9mGxLPVXW8wJF1TiGSL60ZGB4gHWabHOaMmWmhvk2/lPHfnBiT78AQ== +duck@^0.1.12: + version "0.1.12" + resolved "https://registry.yarnpkg.com/duck/-/duck-0.1.12.tgz#de7adf758421230b6d7aee799ce42670586b9efa" + integrity sha512-wkctla1O6VfP89gQ+J/yDesM0S7B7XLXjKGzXxMDVFg7uEn706niAtyYovKbyq1oT9YwDcly721/iUWoc8MVRg== + dependencies: + underscore "^1.13.1" + duplexer@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" @@ -8259,6 +8309,16 @@ ecdsa-sig-formatter@1.0.11: dependencies: safe-buffer "^5.0.1" +editorconfig@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-1.0.4.tgz#040c9a8e9a6c5288388b87c2db07028aa89f53a3" + integrity sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q== + dependencies: + "@one-ini/wasm" "0.1.1" + commander "^10.0.0" + minimatch "9.0.1" + semver "^7.5.3" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -9864,7 +9924,7 @@ glob-to-regexp@^0.4.1: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== -glob@^10.3.10: +glob@^10.3.10, glob@^10.3.3: version "10.4.5" resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== @@ -10354,6 +10414,17 @@ html-escaper@^2.0.0: resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== +html-to-text@^9.0.5: + version "9.0.5" + resolved "https://registry.yarnpkg.com/html-to-text/-/html-to-text-9.0.5.tgz#6149a0f618ae7a0db8085dca9bbf96d32bb8368d" + integrity sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg== + dependencies: + "@selderee/plugin-htmlparser2" "^0.11.0" + deepmerge "^4.3.1" + dom-serializer "^2.0.0" + htmlparser2 "^8.0.2" + selderee "^0.11.0" + html-void-elements@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-2.0.1.tgz#29459b8b05c200b6c5ee98743c41b979d577549f" @@ -10364,7 +10435,7 @@ html-whitespace-sensitive-tag-names@^3.0.0: resolved "https://registry.yarnpkg.com/html-whitespace-sensitive-tag-names/-/html-whitespace-sensitive-tag-names-3.0.1.tgz#c35edd28205f3bf8c1fd03274608d60b923de5b2" integrity sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA== -htmlparser2@^8.0.0, htmlparser2@^8.0.1: +htmlparser2@^8.0.0, htmlparser2@^8.0.1, htmlparser2@^8.0.2: version "8.0.2" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz#f002151705b383e62433b5cf466f5b716edaec21" integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA== @@ -10522,7 +10593,7 @@ iconv-corefoundation@^1.1.7: cli-truncate "^2.1.0" node-addon-api "^1.6.3" -iconv-lite@0.4.24, iconv-lite@^0.4.24: +iconv-lite@0.4.24, iconv-lite@^0.4.15, iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -10563,6 +10634,11 @@ ignore@^5.0.4, ignore@^5.1.1, ignore@^5.2.0, ignore@^5.2.4: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== +immediate@~3.0.5: + version "3.0.6" + resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" + integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== + import-cwd@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-3.0.0.tgz#20845547718015126ea9b3676b7592fb8bd4cf92" @@ -10646,6 +10722,11 @@ ini@4.1.1: resolved "https://registry.yarnpkg.com/ini/-/ini-4.1.1.tgz#d95b3d843b1e906e56d6747d5447904ff50ce7a1" integrity sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g== +ini@^1.3.4: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + inline-style-prefixer@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/inline-style-prefixer/-/inline-style-prefixer-7.0.1.tgz#9310f3cfa2c6f3901d1480f373981c02691781e8" @@ -11596,11 +11677,27 @@ jiti@^1.17.1, jiti@^1.19.1: resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.6.tgz#6c7f7398dd4b3142767f9a168af2f317a428d268" integrity sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w== +js-beautify@^1.15.1: + version "1.15.1" + resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.15.1.tgz#4695afb508c324e1084ee0b952a102023fc65b64" + integrity sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA== + dependencies: + config-chain "^1.1.13" + editorconfig "^1.0.4" + glob "^10.3.3" + js-cookie "^3.0.5" + nopt "^7.2.0" + js-cookie@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8" integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ== +js-cookie@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.5.tgz#0b7e2fd0c01552c58ba86e0841f94dc2557dcdbc" + integrity sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw== + js-message@1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/js-message/-/js-message-1.0.7.tgz#fbddd053c7a47021871bb8b2c95397cc17c20e47" @@ -11874,6 +11971,16 @@ jsprim@^2.0.2: object.assign "^4.1.4" object.values "^1.1.6" +jszip@^3.7.1: + version "3.10.1" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.10.1.tgz#34aee70eb18ea1faec2f589208a157d1feb091c2" + integrity sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g== + dependencies: + lie "~3.3.0" + pako "~1.0.2" + readable-stream "~2.3.6" + setimmediate "^1.0.5" + jwa@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" @@ -11989,6 +12096,11 @@ lazy-val@^1.0.4, lazy-val@^1.0.5: resolved "https://registry.yarnpkg.com/lazy-val/-/lazy-val-1.0.5.tgz#6cf3b9f5bc31cee7ee3e369c0832b7583dcd923d" integrity sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q== +leac@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/leac/-/leac-0.6.0.tgz#dcf136e382e666bd2475f44a1096061b70dc0912" + integrity sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg== + leven@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" @@ -12021,6 +12133,13 @@ license-webpack-plugin@^4.0.2: dependencies: webpack-sources "^3.0.0" +lie@~3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" + integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ== + dependencies: + immediate "~3.0.5" + lightercollective@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/lightercollective/-/lightercollective-0.1.0.tgz#70df102c530dcb8d0ccabfe6175a8d00d5f61300" @@ -12300,6 +12419,15 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" +lop@^0.4.1: + version "0.4.2" + resolved "https://registry.yarnpkg.com/lop/-/lop-0.4.2.tgz#c9c2f958a39b9da1c2f36ca9ad66891a9fe84640" + integrity sha512-RefILVDQ4DKoRZsJ4Pj22TxE3omDO47yFpkIBoDKzkqPRISs5U1cnAdg/5583YPkWPaLIYHOKRMQSvjFsO26cw== + dependencies: + duck "^0.1.12" + option "~0.2.1" + underscore "^1.13.1" + loupe@^2.3.6, loupe@^2.3.7: version "2.3.7" resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.7.tgz#6e69b7d4db7d3ab436328013d37d1c8c3540c697" @@ -12417,6 +12545,22 @@ makeerror@1.0.12: dependencies: tmpl "1.0.5" +mammoth@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/mammoth/-/mammoth-1.8.0.tgz#d8f1b0d3a0355fda129270346e9dc853f223028f" + integrity sha512-pJNfxSk9IEGVpau+tsZFz22ofjUsl2mnA5eT8PjPs2n0BP+rhVte4Nez6FdgEuxv3IGI3afiV46ImKqTGDVlbA== + dependencies: + "@xmldom/xmldom" "^0.8.6" + argparse "~1.0.3" + base64-js "^1.5.1" + bluebird "~3.4.0" + dingbat-to-unicode "^1.0.1" + jszip "^3.7.1" + lop "^0.4.1" + path-is-absolute "^1.0.0" + underscore "^1.13.1" + xmlbuilder "^10.0.0" + markdown-it@^14.0.0: version "14.1.0" resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-14.1.0.tgz#3c3c5992883c633db4714ccb4d7b5935d98b7d45" @@ -13002,6 +13146,13 @@ mini-svg-data-uri@^1.2.3: resolved "https://registry.yarnpkg.com/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz#8ab0aabcdf8c29ad5693ca595af19dd2ead09939" integrity sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg== +minimatch@9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.1.tgz#8a555f541cf976c622daf078bb28f29fb927c253" + integrity sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w== + dependencies: + brace-expansion "^2.0.1" + minimatch@9.0.3: version "9.0.3" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" @@ -13329,6 +13480,13 @@ nopt@^5.0.0: dependencies: abbrev "1" +nopt@^7.2.0: + version "7.2.1" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-7.2.1.tgz#1cac0eab9b8e97c9093338446eddd40b2c8ca1e7" + integrity sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w== + dependencies: + abbrev "^2.0.0" + normalize-package-data@^2.3.2: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" @@ -13641,6 +13799,11 @@ opener@^1.5.1: resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== +option@~0.2.1: + version "0.2.4" + resolved "https://registry.yarnpkg.com/option/-/option-0.2.4.tgz#fd475cdf98dcabb3cb397a3ba5284feb45edbfe4" + integrity sha512-pkEqbDyl8ou5cpq+VsnQbe/WlEy5qS7xPzMS1U55OCG9KPvwFD46zDbxQIj3egJSFc3D+XhYOPUzz49zQAVy7A== + optionator@^0.9.3: version "0.9.4" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" @@ -13795,6 +13958,11 @@ package-json-from-dist@^1.0.0: resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== +pako@~1.0.2: + version "1.0.11" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -13852,6 +14020,14 @@ parse5@^7.0.0, parse5@^7.1.2: dependencies: entities "^4.4.0" +parseley@^0.12.0: + version "0.12.1" + resolved "https://registry.yarnpkg.com/parseley/-/parseley-0.12.1.tgz#4afd561d50215ebe259e3e7a853e62f600683aef" + integrity sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw== + dependencies: + leac "^0.6.0" + peberminta "^0.9.0" + parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" @@ -13968,6 +14144,11 @@ pathval@^1.1.1: resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== +peberminta@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/peberminta/-/peberminta-0.9.0.tgz#8ec9bc0eb84b7d368126e71ce9033501dca2a352" + integrity sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ== + pend@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" @@ -14758,6 +14939,11 @@ prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.13.3, pros prosemirror-state "^1.0.0" prosemirror-transform "^1.1.0" +proto-list@~1.2.1: + version "1.2.4" + resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" + integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA== + protobufjs@^7.2.5, protobufjs@^7.3.0: version "7.4.0" resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.4.0.tgz#7efe324ce9b3b61c82aae5de810d287bc08a248a" @@ -15558,6 +15744,14 @@ rrweb-cssom@^0.6.0: resolved "https://registry.yarnpkg.com/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz#ed298055b97cbddcdeb278f904857629dec5e0e1" integrity sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw== +rtf-parser@^1.0.4: + version "1.3.3" + resolved "https://registry.yarnpkg.com/rtf-parser/-/rtf-parser-1.3.3.tgz#41a10484fd3837c85c9e83d8da2b16829db6f2a9" + integrity sha512-sz2eb4tcCFtwVfs5Ei/l3JnSQGqpDv+drFuNz/zwn2tA24cL2WTuk2VMo2bA4IcRgkn38juAOri2hB9nv85u2Q== + dependencies: + iconv-lite "^0.4.15" + readable-stream "^2.2.2" + rtl-css-js@^1.16.1: version "1.16.1" resolved "https://registry.yarnpkg.com/rtl-css-js/-/rtl-css-js-1.16.1.tgz#4b48b4354b0ff917a30488d95100fbf7219a3e80" @@ -15730,6 +15924,13 @@ secure-compare@3.0.1: resolved "https://registry.yarnpkg.com/secure-compare/-/secure-compare-3.0.1.tgz#f1a0329b308b221fae37b9974f3d578d0ca999e3" integrity sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw== +selderee@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/selderee/-/selderee-0.11.0.tgz#6af0c7983e073ad3e35787ffe20cefd9daf0ec8a" + integrity sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA== + dependencies: + parseley "^0.12.0" + semver-compare@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" @@ -15854,6 +16055,11 @@ set-harmonic-interval@^1.0.1: resolved "https://registry.yarnpkg.com/set-harmonic-interval/-/set-harmonic-interval-1.0.1.tgz#e1773705539cdfb80ce1c3d99e7f298bb3995249" integrity sha512-AhICkFV84tBP1aWqPwLZqFvAwqEoVA9kxNMniGEUvzOlm4vLmOFLiTT3UZ6bziJTy4bOVpzWGTfSCbmaayGx8g== +setimmediate@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== + setprototypeof@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" @@ -17234,6 +17440,11 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" +underscore@^1.13.1: + version "1.13.7" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.7.tgz#970e33963af9a7dda228f17ebe8399e5fbe63a10" + integrity sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g== + undici-types@~6.19.2: version "6.19.8" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" @@ -18005,6 +18216,11 @@ xmlbuilder@>=11.0.1, xmlbuilder@^15.1.1: resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5" integrity sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg== +xmlbuilder@^10.0.0: + version "10.1.1" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-10.1.1.tgz#8cae6688cc9b38d850b7c8d3c0a4161dcaf475b0" + integrity sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg== + xmlchars@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"