diff --git a/app/api/csv/arrangeThesauri.ts b/app/api/csv/arrangeThesauri.ts index f9882e7755..ee0bb890e6 100644 --- a/app/api/csv/arrangeThesauri.ts +++ b/app/api/csv/arrangeThesauri.ts @@ -21,15 +21,12 @@ import { LabelInfoBase } from './typeParsers/select'; import { headerWithLanguage } from './csvDefinitions'; class ArrangeThesauriError extends Error { - source: Error; - row: CSVRow; index: number; constructor(source: Error, row: CSVRow, index: number) { - super(source.message); - this.source = source; + super(source.message, { cause: source }); this.row = row; this.index = index; } @@ -163,9 +160,7 @@ const tryAddingLabel = ( const map = thesauriValueData[id]; if (isStandaloneGroup(map, labelInfo)) { throw new Error( - `The label "${ - labelInfo.label - }" at property "${name}" is a group label in line:\n${JSON.stringify(row)}` + `The label "${labelInfo.label}" at property "${name}" is a group label in line:\n${JSON.stringify(row)}` ); } const { childInfo, parentInfo } = pickParentChild(labelInfo); diff --git a/app/api/entities.v2/contracts/EntitiesDataSource.ts b/app/api/entities.v2/contracts/EntitiesDataSource.ts index 4a9195a18b..5f637a26e7 100644 --- a/app/api/entities.v2/contracts/EntitiesDataSource.ts +++ b/app/api/entities.v2/contracts/EntitiesDataSource.ts @@ -1,5 +1,5 @@ import { ResultSet } from 'api/common.v2/contracts/ResultSet'; -import { Entity, MetadataValue } from '../model/Entity'; +import { Entity, EntityMetadata, MetadataValue } from '../model/Entity'; type MarkAsChangedCriteria = { template: string } | { sharedId: string }; type MarkAsChangedData = { property: string } | { properties: string[] }; @@ -8,7 +8,11 @@ export type MarkAsChangedItems = MarkAsChangedCriteria & MarkAsChangedData; export interface EntitiesDataSource { updateObsoleteMetadataValues( id: Entity['_id'], - values: Record + values: Record + ): Promise; + updateMetadataValues( + id: Entity['_id'], + values: Record ): Promise; entitiesExist(sharedIds: string[]): Promise; getByIds(sharedIds: string[], language?: string): ResultSet; diff --git a/app/api/entities.v2/database/MongoEntitiesDataSource.ts b/app/api/entities.v2/database/MongoEntitiesDataSource.ts index 48498120fc..3a2de5c661 100644 --- a/app/api/entities.v2/database/MongoEntitiesDataSource.ts +++ b/app/api/entities.v2/database/MongoEntitiesDataSource.ts @@ -1,15 +1,16 @@ import { ResultSet } from 'api/common.v2/contracts/ResultSet'; import { MongoDataSource } from 'api/common.v2/database/MongoDataSource'; +import { MongoIdHandler } from 'api/common.v2/database/MongoIdGenerator'; import { MongoResultSet } from 'api/common.v2/database/MongoResultSet'; import { MongoTransactionManager } from 'api/common.v2/database/MongoTransactionManager'; -import { MongoIdHandler } from 'api/common.v2/database/MongoIdGenerator'; +import entities from 'api/entities/entities'; import { MongoSettingsDataSource } from 'api/settings.v2/database/MongoSettingsDataSource'; import { MongoTemplatesDataSource } from 'api/templates.v2/database/MongoTemplatesDataSource'; import { Db } from 'mongodb'; import { EntitiesDataSource } from '../contracts/EntitiesDataSource'; +import { Entity, EntityMetadata, MetadataValue } from '../model/Entity'; import { EntityMappers } from './EntityMapper'; import { EntityDBO, EntityJoinTemplate } from './schemas/EntityTypes'; -import { Entity, MetadataValue } from '../model/Entity'; export class MongoEntitiesDataSource extends MongoDataSource @@ -123,9 +124,30 @@ export class MongoEntitiesDataSource return new MongoResultSet(result, entity => entity.sharedId); } + // eslint-disable-next-line class-methods-use-this + async updateMetadataValues( + id: Entity['_id'], + values: Record + ) { + // This is using V1 so that it gets denormalized to speed up development + // this is a hack and should be changed as soon as we finish AT + const entityToModify = await entities.getById(id); + if (!entityToModify) { + throw new Error(`entity does not exists: ${id}`); + } + + Object.entries(values).forEach(([propertyName, metadataValues]) => { + entityToModify.metadata = entityToModify.metadata || {}; + // @ts-ignore + entityToModify.metadata[propertyName] = metadataValues; + }); + + await entities.save(entityToModify, { user: {}, language: entityToModify.language }); + } + async updateObsoleteMetadataValues( id: Entity['_id'], - values: Record + values: Record ): Promise { const stream = this.createBulkStream(); diff --git a/app/api/entities.v2/model/Entity.ts b/app/api/entities.v2/model/Entity.ts index 5b4749a427..6491c59c08 100644 --- a/app/api/entities.v2/model/Entity.ts +++ b/app/api/entities.v2/model/Entity.ts @@ -1,5 +1,7 @@ +type MetadataValue = unknown; + type BaseMetadataValue = { - value: unknown; + value: MetadataValue; label: string; }; @@ -8,9 +10,9 @@ type InheritedResultValue = BaseMetadataValue & { inheritedType: string; }; -type MetadataValue = BaseMetadataValue | InheritedResultValue; +type EntityMetadata = BaseMetadataValue | InheritedResultValue; -type Metadata = Record; +type Metadata = Record; export class Entity { readonly _id: string; @@ -45,4 +47,5 @@ export class Entity { this.obsoleteMetadata = obsoleteMetadata ?? []; } } -export type { Metadata, MetadataValue }; + +export type { Metadata, EntityMetadata, MetadataValue }; diff --git a/app/api/entities.v2/services/EntityRelationshipsUpdateService.ts b/app/api/entities.v2/services/EntityRelationshipsUpdateService.ts index ef040328c2..fccedb5c7f 100644 --- a/app/api/entities.v2/services/EntityRelationshipsUpdateService.ts +++ b/app/api/entities.v2/services/EntityRelationshipsUpdateService.ts @@ -3,7 +3,7 @@ import { Template } from 'api/templates.v2/model/Template'; import { RelationshipProperty } from 'api/templates.v2/model/RelationshipProperty'; import { RelationshipsDataSource } from 'api/relationships.v2/contracts/RelationshipsDataSource'; import { TemplatesDataSource } from 'api/templates.v2/contracts/TemplatesDataSource'; -import { Entity, MetadataValue } from '../model/Entity'; +import { Entity, EntityMetadata } from '../model/Entity'; import { EntitiesDataSource } from '../contracts/EntitiesDataSource'; export class EntityRelationshipsUpdateService { @@ -40,7 +40,7 @@ export class EntityRelationshipsUpdateService { private async transformToDenormalizedData( property: RelationshipProperty, queryResult: Entity[] - ): Promise { + ): Promise { return Promise.all( queryResult.map(async entity => ({ value: entity.sharedId, @@ -65,7 +65,7 @@ export class EntityRelationshipsUpdateService { await this.entitiesDataSource.getByIds(sharedIds).forEach(async entity => { template = await this.findTemplate(template, entity.template); - const metadataToUpdate: Record = {}; + const metadataToUpdate: Record = {}; await Promise.all( template.properties.map(async property => { diff --git a/app/api/externalIntegrations.v2/automaticTranslation/SaveEntityTranslations.ts b/app/api/externalIntegrations.v2/automaticTranslation/SaveEntityTranslations.ts new file mode 100644 index 0000000000..06671d0f44 --- /dev/null +++ b/app/api/externalIntegrations.v2/automaticTranslation/SaveEntityTranslations.ts @@ -0,0 +1,69 @@ +import { EntitiesDataSource } from 'api/entities.v2/contracts/EntitiesDataSource'; +import { TemplatesDataSource } from 'api/templates.v2/contracts/TemplatesDataSource'; +import { ATTranslationResultValidator } from './contracts/ATTranslationResultValidator'; +import { InvalidInputDataFormat } from './errors/generateATErrors'; +import { TranslationResult } from './types/TranslationResult'; + +export class SaveEntityTranslations { + static AITranslatedText = '(AI translated)'; + + private entitiesDS: EntitiesDataSource; + + private templatesDS: TemplatesDataSource; + + private validator: ATTranslationResultValidator; + + constructor( + templatesDS: TemplatesDataSource, + entitiesDS: EntitiesDataSource, + validator: ATTranslationResultValidator + ) { + this.entitiesDS = entitiesDS; + this.templatesDS = templatesDS; + this.validator = validator; + } + + async execute(translationResult: TranslationResult | unknown) { + if (!this.validator.validate(translationResult)) { + throw new InvalidInputDataFormat(this.validator.getErrors()[0]); + } + + const [, entitySharedId, propertyId] = translationResult.key; + + const property = await this.getProperty(entitySharedId, propertyId); + + const entities = this.entitiesDS.getByIds([entitySharedId]); + + await entities.forEach(async oneEntity => { + const translation = translationResult.translations.find( + t => t.language === oneEntity.language + ); + if (translation) { + await this.entitiesDS.updateMetadataValues(oneEntity._id, { + [property.name]: [ + { value: `${SaveEntityTranslations.AITranslatedText} ${translation.text}` }, + ], + }); + } + }); + } + + private async getProperty(entitySharedId: string, propertyId: string) { + const entity = await this.entitiesDS.getByIds([entitySharedId]).first(); + if (!entity) { + throw new Error('Entity does not exists'); + } + + const template = await this.templatesDS.getById(entity.template); + if (!template) { + throw new Error('Template does not exists'); + } + + const property = template.properties.find(p => p.id === propertyId); + + if (!property) { + throw new Error('Property does not exists'); + } + return property; + } +} diff --git a/app/api/externalIntegrations.v2/automaticTranslation/contracts/ATTranslationResultValidator.ts b/app/api/externalIntegrations.v2/automaticTranslation/contracts/ATTranslationResultValidator.ts new file mode 100644 index 0000000000..f368e257c3 --- /dev/null +++ b/app/api/externalIntegrations.v2/automaticTranslation/contracts/ATTranslationResultValidator.ts @@ -0,0 +1,6 @@ +import { TranslationResult } from '../types/TranslationResult'; + +export interface ATTranslationResultValidator { + getErrors(): string[]; + validate(data: unknown): data is TranslationResult; +} diff --git a/app/api/externalIntegrations.v2/automaticTranslation/infrastructure/AJVTranslationResultValidator.ts b/app/api/externalIntegrations.v2/automaticTranslation/infrastructure/AJVTranslationResultValidator.ts new file mode 100644 index 0000000000..f27daaff91 --- /dev/null +++ b/app/api/externalIntegrations.v2/automaticTranslation/infrastructure/AJVTranslationResultValidator.ts @@ -0,0 +1,40 @@ +import { Ajv } from 'ajv'; +import { JTDSchemaType } from 'ajv/dist/core'; +import { TranslationResult } from '../types/TranslationResult'; +import { ATTranslationResultValidator } from '../contracts/ATTranslationResultValidator'; + +const schema: JTDSchemaType = { + additionalProperties: false, + properties: { + key: { elements: { type: 'string' } }, + text: { type: 'string' }, + language_from: { type: 'string' }, + languages_to: { elements: { type: 'string' } }, + translations: { + elements: { + properties: { + text: { type: 'string' }, + language: { type: 'string' }, + success: { type: 'boolean' }, + error_message: { type: 'string' }, + }, + }, + }, + }, +}; + +export class AJVTranslationResultValidator implements ATTranslationResultValidator { + private errors: string[] = []; + + getErrors() { + return this.errors; + } + + validate(data: unknown) { + const ajv = new Ajv({ strict: false }); + const validate = ajv.compile(schema); + const result = validate(data); + this.errors = validate.errors ? validate.errors?.map(e => JSON.stringify(e.params)) : []; + return result; + } +} diff --git a/app/api/externalIntegrations.v2/automaticTranslation/specs/SaveEntityTranslations.spec.ts b/app/api/externalIntegrations.v2/automaticTranslation/specs/SaveEntityTranslations.spec.ts new file mode 100644 index 0000000000..fd7f7c8a29 --- /dev/null +++ b/app/api/externalIntegrations.v2/automaticTranslation/specs/SaveEntityTranslations.spec.ts @@ -0,0 +1,160 @@ +import { DefaultTransactionManager } from 'api/common.v2/database/data_source_defaults'; +import { DefaultEntitiesDataSource } from 'api/entities.v2/database/data_source_defaults'; +import { DefaultTemplatesDataSource } from 'api/templates.v2/database/data_source_defaults'; +import { getFixturesFactory } from 'api/utils/fixturesFactory'; +import { testingEnvironment } from 'api/utils/testingEnvironment'; +import testingDB, { DBFixture } from 'api/utils/testing_db'; +import { LanguageISO6391 } from 'shared/types/commonTypes'; +import { SaveEntityTranslations } from '../SaveEntityTranslations'; +import { InvalidInputDataFormat } from '../errors/generateATErrors'; +import { AJVTranslationResultValidator } from '../infrastructure/AJVTranslationResultValidator'; +import { TranslationResult } from '../types/TranslationResult'; + +const factory = getFixturesFactory(); + +beforeEach(async () => { + const fixtures = { + templates: [ + factory.template('template1', [ + { + _id: factory.id('propertyName'), + name: 'propertyName', + type: 'text', + label: 'Prop 1', + }, + ]), + ], + entities: [ + ...factory.entityInMultipleLanguages(['en', 'pt', 'es'], 'entity', 'template1', { + propertyName: [{ value: 'original text' }], + }), + ], + settings: [ + { + languages: [ + { label: 'en', key: 'en' as LanguageISO6391, default: true }, + { label: 'pt', key: 'pt' as LanguageISO6391 }, + { label: 'es', key: 'es' as LanguageISO6391 }, + ], + }, + ], + }; + await testingEnvironment.setUp(fixtures); +}); + +afterAll(async () => { + await testingEnvironment.tearDown(); +}); + +describe('GenerateAutomaticTranslationConfig', () => { + let saveEntityTranslations: SaveEntityTranslations; + + beforeEach(() => { + const transactionManager = DefaultTransactionManager(); + saveEntityTranslations = new SaveEntityTranslations( + DefaultTemplatesDataSource(transactionManager), + DefaultEntitiesDataSource(transactionManager), + new AJVTranslationResultValidator() + ); + }); + + it('should validate input has proper shape at runtime', async () => { + const invalidConfig = { invalid_prop: true }; + await expect(saveEntityTranslations.execute(invalidConfig)).rejects.toEqual( + new InvalidInputDataFormat('{"additionalProperty":"invalid_prop"}') + ); + }); + + it(`should save entity with translated text with prepended "${SaveEntityTranslations.AITranslatedText}"`, async () => { + const translationResult: TranslationResult = { + key: ['tenant', 'entity', factory.idString('propertyName')], + text: 'original text', + language_from: 'en', + languages_to: ['es', 'pt'], + translations: [ + { text: 'texto original', language: 'es', success: true, error_message: '' }, + { text: 'texto original (pt)', language: 'pt', success: true, error_message: '' }, + ], + }; + + await saveEntityTranslations.execute(translationResult); + + const entities = + (await testingDB.mongodb?.collection('entities').find({ sharedId: 'entity' }).toArray()) || + []; + + expect(entities.find(e => e.language === 'es')).toMatchObject({ + metadata: { + propertyName: [{ value: `${SaveEntityTranslations.AITranslatedText} texto original` }], + }, + }); + + expect(entities.find(e => e.language === 'pt')).toMatchObject({ + metadata: { + propertyName: [{ value: `${SaveEntityTranslations.AITranslatedText} texto original (pt)` }], + }, + }); + + expect(entities.find(e => e.language === 'en')).toMatchObject({ + metadata: { propertyName: [{ value: 'original text' }] }, + }); + }); + + it('should denormalize text property on related entities', async () => { + const fixtures: DBFixture = { + settings: [ + { + languages: [{ label: 'es', key: 'es' as LanguageISO6391, default: true }], + }, + ], + templates: [ + factory.template('templateA', [factory.inherit('relationship', 'templateB', 'text')]), + factory.template('templateB', [factory.property('text')]), + ], + entities: [ + factory.entity('A1', 'templateA', { + relationship: [factory.metadataValue('B1')], + }), + factory.entity('B1', 'templateB', {}, { title: 'B1title' }), + ], + }; + + await testingEnvironment.setUp(fixtures); + + const translationResult: TranslationResult = { + key: ['tenant', 'B1', factory.idString('text')], + text: 'texto original', + language_from: 'es', + languages_to: ['en'], + translations: [{ text: 'original text', language: 'en', success: true, error_message: '' }], + }; + + await saveEntityTranslations.execute(translationResult); + + const entities = (await testingDB.mongodb?.collection('entities').find().toArray()) || []; + + expect(entities).toMatchObject([ + { + sharedId: 'A1', + metadata: { + relationship: [ + { + value: 'B1', + label: 'B1title', + inheritedValue: [ + { value: `${SaveEntityTranslations.AITranslatedText} original text` }, + ], + }, + ], + }, + }, + { + title: 'B1title', + sharedId: 'B1', + metadata: { + text: [{ value: `${SaveEntityTranslations.AITranslatedText} original text` }], + }, + }, + ]); + }); +}); diff --git a/app/api/externalIntegrations.v2/automaticTranslation/types/TranslationResult.ts b/app/api/externalIntegrations.v2/automaticTranslation/types/TranslationResult.ts new file mode 100644 index 0000000000..a53b8b3b95 --- /dev/null +++ b/app/api/externalIntegrations.v2/automaticTranslation/types/TranslationResult.ts @@ -0,0 +1,12 @@ +export interface TranslationResult { + key: string[]; + text: string; + language_from: string; + languages_to: string[]; + translations: { + text: string; + language: string; + success: boolean; + error_message: string; + }[]; +} diff --git a/app/api/files/S3Storage.ts b/app/api/files/S3Storage.ts index 6038e9755e..a456ef3110 100644 --- a/app/api/files/S3Storage.ts +++ b/app/api/files/S3Storage.ts @@ -6,23 +6,30 @@ import { S3Client, _Object, } from '@aws-sdk/client-s3'; -import { NodeHttpHandler } from '@smithy/node-http-handler'; import { config } from 'api/config'; +class S3TimeoutError extends Error { + constructor(cause: Error) { + super(cause.message, { cause }); + } +} + +const catchS3Errors = async (cb: () => Promise): Promise => { + try { + return await cb(); + } catch (err) { + if (err.name === 'TimeoutError') { + throw new S3TimeoutError(err); + } + throw err; + } +}; + export class S3Storage { private client: S3Client; - constructor() { - this.client = new S3Client({ - requestHandler: new NodeHttpHandler({ - socketTimeout: 30000, - }), - apiVersion: 'latest', - region: 'placeholder-region', - endpoint: config.s3.endpoint, - credentials: config.s3.credentials, - forcePathStyle: true, - }); + constructor(s3Client: S3Client) { + this.client = s3Client; } static bucketName() { @@ -30,20 +37,22 @@ export class S3Storage { } async upload(key: string, body: Buffer) { - return this.client.send( - new PutObjectCommand({ Bucket: S3Storage.bucketName(), Key: key, Body: body }) + return catchS3Errors(async () => + this.client.send( + new PutObjectCommand({ Bucket: S3Storage.bucketName(), Key: key, Body: body }) + ) ); } async get(key: string) { - const response = await this.client.send( - new GetObjectCommand({ - Bucket: S3Storage.bucketName(), - Key: key, - }) + return catchS3Errors(async () => + this.client.send( + new GetObjectCommand({ + Bucket: S3Storage.bucketName(), + Key: key, + }) + ) ); - - return response; } async list(prefix?: string) { @@ -61,21 +70,26 @@ export class S3Storage { return response.NextContinuationToken; }; - let continuationToken = await requestNext(); - while (continuationToken) { - // eslint-disable-next-line no-await-in-loop - continuationToken = await requestNext(continuationToken); - } - - return objects; + return catchS3Errors(async () => { + let continuationToken = await requestNext(); + while (continuationToken) { + // eslint-disable-next-line no-await-in-loop + continuationToken = await requestNext(continuationToken); + } + return objects; + }); } async delete(key: string) { - await this.client.send( - new DeleteObjectCommand({ - Bucket: S3Storage.bucketName(), - Key: key, - }) + return catchS3Errors(async () => + this.client.send( + new DeleteObjectCommand({ + Bucket: S3Storage.bucketName(), + Key: key, + }) + ) ); } } + +export { S3TimeoutError }; diff --git a/app/api/files/specs/s3Storage.spec.ts b/app/api/files/specs/s3Storage.spec.ts new file mode 100644 index 0000000000..35c601fe97 --- /dev/null +++ b/app/api/files/specs/s3Storage.spec.ts @@ -0,0 +1,45 @@ +import { S3Storage, S3TimeoutError } from '../S3Storage'; + +let s3Storage: S3Storage; + +class S3TimeoutClient { + // eslint-disable-next-line class-methods-use-this + send() { + const error = new Error(); + error.name = 'TimeoutError'; + throw error; + } +} + +describe('s3Storage', () => { + beforeAll(async () => { + // @ts-ignore + s3Storage = new S3Storage(new S3TimeoutClient()); + }); + + describe('get', () => { + it('should throw S3TimeoutError on timeout', async () => { + await expect(s3Storage.get('dummy_key')).rejects.toBeInstanceOf(S3TimeoutError); + }); + }); + + describe('upload', () => { + it('should throw S3TimeoutError on timeout', async () => { + await expect( + s3Storage.upload('dummy_key', Buffer.from('dummy buffer', 'utf-8')) + ).rejects.toBeInstanceOf(S3TimeoutError); + }); + }); + + describe('delete', () => { + it('should throw S3TimeoutError on timeout', async () => { + await expect(s3Storage.delete('dummy_key')).rejects.toBeInstanceOf(S3TimeoutError); + }); + }); + + describe('list', () => { + it('should throw S3TimeoutError on timeout', async () => { + await expect(s3Storage.list()).rejects.toBeInstanceOf(S3TimeoutError); + }); + }); +}); diff --git a/app/api/files/storage.ts b/app/api/files/storage.ts index 2dfac22938..ba43d25b75 100644 --- a/app/api/files/storage.ts +++ b/app/api/files/storage.ts @@ -1,6 +1,7 @@ -import { NoSuchKey } from '@aws-sdk/client-s3'; +import { NoSuchKey, S3Client } from '@aws-sdk/client-s3'; import { config } from 'api/config'; import { tenants } from 'api/tenants'; +import { NodeHttpHandler } from '@smithy/node-http-handler'; // eslint-disable-next-line node/no-restricted-import import { createReadStream, createWriteStream } from 'fs'; // eslint-disable-next-line node/no-restricted-import @@ -9,6 +10,7 @@ import path from 'path'; import { FileType } from 'shared/types/fileType'; import { Readable } from 'stream'; import { pipeline } from 'stream/promises'; +import { FileNotFound } from './FileNotFound'; import { activityLogPath, attachmentsPath, @@ -18,14 +20,24 @@ import { uploadsPath, } from './filesystem'; import { S3Storage } from './S3Storage'; -import { FileNotFound } from './FileNotFound'; type FileTypes = NonNullable | 'activitylog' | 'segmentation'; let s3Instance: S3Storage; const s3 = () => { if (config.s3.endpoint && !s3Instance) { - s3Instance = new S3Storage(); + s3Instance = new S3Storage( + new S3Client({ + requestHandler: new NodeHttpHandler({ + socketTimeout: 30000, + }), + apiVersion: 'latest', + region: 'placeholder-region', + endpoint: config.s3.endpoint, + credentials: config.s3.credentials, + forcePathStyle: true, + }) + ); } return s3Instance; }; diff --git a/app/api/services/convertToPDF/specs/convertToPdfWorker.spec.ts b/app/api/services/convertToPDF/specs/convertToPdfWorker.spec.ts index 0b8fa8cb62..19b1d2ef08 100644 --- a/app/api/services/convertToPDF/specs/convertToPdfWorker.spec.ts +++ b/app/api/services/convertToPDF/specs/convertToPdfWorker.spec.ts @@ -22,13 +22,13 @@ describe('convertToPdfWorker', () => { const recreateRedisQueue = async () => { try { - await redisSMQ.deleteQueueAsync({ qname: 'convert-to-pdf_results' }); + await redisSMQ.deleteQueueAsync({ qname: 'development_convert-to-pdf_results' }); } catch (err) { if (err instanceof Error && err.name !== 'queueNotFound') { throw err; } } - await redisSMQ.createQueueAsync({ qname: 'convert-to-pdf_results' }); + await redisSMQ.createQueueAsync({ qname: 'development_convert-to-pdf_results' }); }; beforeAll(async () => { @@ -88,7 +88,7 @@ describe('convertToPdfWorker', () => { }; await redisSMQ.sendMessageAsync({ - qname: 'convert-to-pdf_results', + qname: 'development_convert-to-pdf_results', message: JSON.stringify(message), }); }); @@ -162,7 +162,7 @@ describe('convertToPdfWorker', () => { jest.spyOn(handleError, 'handleError').mockImplementationOnce(() => {}); await redisSMQ.sendMessageAsync({ - qname: 'convert-to-pdf_results', + qname: 'development_convert-to-pdf_results', message: JSON.stringify(message), }); diff --git a/app/api/services/tasksmanager/TaskManager.ts b/app/api/services/tasksmanager/TaskManager.ts index f9c57b9254..e6dcf9166a 100644 --- a/app/api/services/tasksmanager/TaskManager.ts +++ b/app/api/services/tasksmanager/TaskManager.ts @@ -52,8 +52,8 @@ export class TaskManager< constructor(service: Service) { this.service = service; - this.taskQueue = `${service.serviceName}_tasks`; - this.resultsQueue = `${service.serviceName}_results`; + this.taskQueue = `${config.ENVIRONMENT}_${service.serviceName}_tasks`; + this.resultsQueue = `${config.ENVIRONMENT}_${service.serviceName}_results`; const redisUrl = `redis://${config.redis.host}:${config.redis.port}`; this.redisClient = Redis.createClient(redisUrl); this.redisSMQ = new RedisSMQ({ client: this.redisClient }); diff --git a/app/api/services/tasksmanager/specs/ExternalDummyService.ts b/app/api/services/tasksmanager/specs/ExternalDummyService.ts index 58b6371fac..8243048b6b 100644 --- a/app/api/services/tasksmanager/specs/ExternalDummyService.ts +++ b/app/api/services/tasksmanager/specs/ExternalDummyService.ts @@ -122,16 +122,16 @@ export class ExternalDummyService { } async resetQueue() { - await this.deleteQueue(`${this.serviceName}_tasks`); - await this.deleteQueue(`${this.serviceName}_results`); + await this.deleteQueue(`development_${this.serviceName}_tasks`); + await this.deleteQueue(`development_${this.serviceName}_results`); - await this.createQueue(`${this.serviceName}_tasks`); - await this.createQueue(`${this.serviceName}_results`); + await this.createQueue(`development_${this.serviceName}_tasks`); + await this.createQueue(`development_${this.serviceName}_results`); } async readFirstTaskMessage() { const message: RedisSMQ.QueueMessage | {} = await this.rsmq.receiveMessageAsync({ - qname: `${this.serviceName}_tasks`, + qname: `development_${this.serviceName}_tasks`, }); const queueMessage = message as QueueMessage; @@ -140,7 +140,7 @@ export class ExternalDummyService { } await this.rsmq.deleteMessageAsync({ - qname: `${this.serviceName}_tasks`, + qname: `development_${this.serviceName}_tasks`, id: queueMessage.id, }); @@ -186,7 +186,7 @@ export class ExternalDummyService { async sendFinishedMessage(task: ResultsMessage) { try { await this.rsmq.sendMessageAsync({ - qname: `${this.serviceName}_results`, + qname: `development_${this.serviceName}_results`, message: JSON.stringify(task), }); } catch (err) { diff --git a/app/api/users/specs/users.spec.js b/app/api/users/specs/users.spec.js index b6e81ae9d8..45da5465a4 100644 --- a/app/api/users/specs/users.spec.js +++ b/app/api/users/specs/users.spec.js @@ -587,16 +587,13 @@ describe('Users', () => { }); describe('when the user does not exist with that email', () => { - it('should not create the entry in the database, should not send a mail, and return an error.', async () => { + it('should not create the entry in the database, should not send a mail, and return nothing', async () => { jest.spyOn(Date, 'now').mockReturnValue(1000); const key = unlockCode.generateUnlockCode(); let response; - try { - response = await users.recoverPassword('false@email.com'); - } catch (error) { - expect(error.code).toBe(403); - response = await passwordRecoveriesModel.get({ key }); - } + response = await users.recoverPassword('false@email.com'); + expect(response).toBe(undefined); + response = await passwordRecoveriesModel.get({ key }); expect(response.length).toBe(0); }); }); diff --git a/app/api/users/users.js b/app/api/users/users.js index f9f44d5827..523760c682 100644 --- a/app/api/users/users.js +++ b/app/api/users/users.js @@ -321,7 +321,7 @@ export default { }); } - return Promise.reject(createError('User not found', 403)); + return undefined; }); }, diff --git a/app/api/utils/handleError.js b/app/api/utils/handleError.js index 38a8259773..044121352a 100644 --- a/app/api/utils/handleError.js +++ b/app/api/utils/handleError.js @@ -1,10 +1,12 @@ -import { legacyLogger } from 'api/log'; import Ajv from 'ajv'; -import { createError } from 'api/utils/index'; -import { appContext } from 'api/utils/AppContext'; import { UnauthorizedError } from 'api/authorization.v2/errors/UnauthorizedError'; import { ValidationError } from 'api/common.v2/validation/ValidationError'; import { FileNotFound } from 'api/files/FileNotFound'; +import { S3TimeoutError } from 'api/files/S3Storage'; +import { legacyLogger } from 'api/log'; +import { appContext } from 'api/utils/AppContext'; +import { createError } from 'api/utils/index'; +import util from 'node:util'; const ajvPrettifier = error => { const errorMessage = [error.message]; @@ -59,7 +61,11 @@ const prettifyError = (error, { req = {}, uncaught = false } = {}) => { let result = error; if (error instanceof Error) { - result = { code: 500, message: error.stack, logLevel: 'error' }; + result = { code: 500, message: util.inspect(error), logLevel: 'error' }; + } + + if (error instanceof S3TimeoutError) { + result = { code: 408, message: util.inspect(error), logLevel: 'debug' }; } if (error instanceof Ajv.ValidationError) { diff --git a/app/api/utils/specs/handleError.spec.js b/app/api/utils/specs/handleError.spec.js index bd7774956d..840486b144 100644 --- a/app/api/utils/specs/handleError.spec.js +++ b/app/api/utils/specs/handleError.spec.js @@ -1,8 +1,10 @@ -import { createError } from 'api/utils'; import { legacyLogger } from 'api/log'; +import { createError } from 'api/utils'; import { errors as elasticErrors } from '@elastic/elasticsearch'; +import { S3TimeoutError } from 'api/files/S3Storage'; import { appContext } from 'api/utils/AppContext'; +import util from 'node:util'; import { handleError, prettifyError } from '../handleError'; const contextRequestId = '1234'; @@ -18,6 +20,17 @@ describe('handleError', () => { }); describe('errors by type', () => { + describe('and is instance of S3TimeoutError', () => { + it('should be a debug logLevel and a 408 http code', () => { + const errorInstance = new S3TimeoutError(new Error('timeout')); + const error = handleError(errorInstance); + expect(error).toMatchObject({ + code: 408, + logLevel: 'debug', + }); + expect(legacyLogger.debug.mock.calls[0][0]).toContain('timeout'); + }); + }); describe('when error is instance of Error', () => { it('should return the error with 500 code without the original error and error stack', () => { const errorInstance = new Error('error'); @@ -36,7 +49,7 @@ describe('handleError', () => { handleError(error); expect(legacyLogger.error).toHaveBeenCalledWith( - `requestId: ${contextRequestId} \n${error.stack} + `requestId: ${contextRequestId} \n${util.inspect(error)} original error: { "name": "ConnectionError", "meta": { diff --git a/app/react/App/styles/globals.css b/app/react/App/styles/globals.css index cdef6fd274..9c7c6c7cde 100644 --- a/app/react/App/styles/globals.css +++ b/app/react/App/styles/globals.css @@ -1,5 +1,115 @@ +*, +::before, +::after { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(99 102 241 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: ; +} + +::backdrop { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(99 102 241 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: ; +} + /* -! tailwindcss v3.4.10 | MIT License | https://tailwindcss.com +! tailwindcss v3.4.12 | MIT License | https://tailwindcss.com */ /* @@ -16,7 +126,7 @@ /* 2 */ border-style: solid; /* 2 */ - border-color: #E5E7EB; + border-color: #e5e7eb; /* 2 */ } @@ -44,9 +154,24 @@ html, -moz-tab-size: 4; /* 3 */ -o-tab-size: 4; - tab-size: 4; + tab-size: 4; /* 3 */ - font-family: Inter, ui-sans-serif, system-ui, -apple-system, system-ui, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji; + font-family: + Inter, + ui-sans-serif, + system-ui, + -apple-system, + system-ui, + Segoe UI, + Roboto, + Helvetica Neue, + Arial, + Noto Sans, + sans-serif, + Apple Color Emoji, + Segoe UI Emoji, + Segoe UI Symbol, + Noto Color Emoji; /* 4 */ font-feature-settings: normal; /* 5 */ @@ -89,7 +214,7 @@ Add the correct text decoration in Chrome, Edge, and Safari. abbr:where([title]) { -webkit-text-decoration: underline dotted; - text-decoration: underline dotted; + text-decoration: underline dotted; } /* @@ -135,7 +260,15 @@ code, kbd, samp, pre { - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace; + font-family: + ui-monospace, + SFMono-Regular, + Menlo, + Monaco, + Consolas, + Liberation Mono, + Courier New, + monospace; /* 1 */ font-feature-settings: normal; /* 2 */ @@ -378,10 +511,11 @@ textarea { 2. Set the default placeholder color to the user's configured gray 400 color. */ -input::-moz-placeholder, textarea::-moz-placeholder { +input::-moz-placeholder, +textarea::-moz-placeholder { opacity: 1; /* 1 */ - color: #9CA3AF; + color: #9ca3af; /* 2 */ } @@ -389,7 +523,7 @@ input::placeholder, textarea::placeholder { opacity: 1; /* 1 */ - color: #9CA3AF; + color: #9ca3af; /* 2 */ } @@ -398,7 +532,7 @@ Set the default cursor for buttons. */ button, -[role="button"] { +[role='button'] { cursor: pointer; } @@ -446,7 +580,8 @@ video { display: none; } -.tooltip-arrow,.tooltip-arrow:before { +.tooltip-arrow, +.tooltip-arrow:before { position: absolute; width: 8px; height: 8px; @@ -458,7 +593,7 @@ video { } .tooltip-arrow:before { - content: ""; + content: ''; visibility: visible; transform: rotate(45deg); } @@ -508,7 +643,8 @@ video { visibility: hidden; } -[data-popper-arrow],[data-popper-arrow]:before { +[data-popper-arrow], +[data-popper-arrow]:before { position: absolute; width: 8px; height: 8px; @@ -520,13 +656,13 @@ video { } [data-popper-arrow]:before { - content: ""; + content: ''; visibility: visible; transform: rotate(45deg); } [data-popper-arrow]:after { - content: ""; + content: ''; visibility: visible; transform: rotate(45deg); position: absolute; @@ -535,96 +671,110 @@ video { background: inherit; } -[role="tooltip"] > [data-popper-arrow]:before { +[role='tooltip'] > [data-popper-arrow]:before { border-style: solid; border-color: #e5e7eb; } -.dark [role="tooltip"] > [data-popper-arrow]:before { +.dark [role='tooltip'] > [data-popper-arrow]:before { border-style: solid; border-color: #4b5563; } -[role="tooltip"] > [data-popper-arrow]:after { +[role='tooltip'] > [data-popper-arrow]:after { border-style: solid; border-color: #e5e7eb; } -.dark [role="tooltip"] > [data-popper-arrow]:after { +.dark [role='tooltip'] > [data-popper-arrow]:after { border-style: solid; border-color: #4b5563; } -[data-popover][role="tooltip"][data-popper-placement^='top'] > [data-popper-arrow]:before { +[data-popover][role='tooltip'][data-popper-placement^='top'] > [data-popper-arrow]:before { border-bottom-width: 1px; border-right-width: 1px; } -[data-popover][role="tooltip"][data-popper-placement^='top'] > [data-popper-arrow]:after { +[data-popover][role='tooltip'][data-popper-placement^='top'] > [data-popper-arrow]:after { border-bottom-width: 1px; border-right-width: 1px; } -[data-popover][role="tooltip"][data-popper-placement^='right'] > [data-popper-arrow]:before { +[data-popover][role='tooltip'][data-popper-placement^='right'] > [data-popper-arrow]:before { border-bottom-width: 1px; border-left-width: 1px; } -[data-popover][role="tooltip"][data-popper-placement^='right'] > [data-popper-arrow]:after { +[data-popover][role='tooltip'][data-popper-placement^='right'] > [data-popper-arrow]:after { border-bottom-width: 1px; border-left-width: 1px; } -[data-popover][role="tooltip"][data-popper-placement^='bottom'] > [data-popper-arrow]:before { +[data-popover][role='tooltip'][data-popper-placement^='bottom'] > [data-popper-arrow]:before { border-top-width: 1px; border-left-width: 1px; } -[data-popover][role="tooltip"][data-popper-placement^='bottom'] > [data-popper-arrow]:after { +[data-popover][role='tooltip'][data-popper-placement^='bottom'] > [data-popper-arrow]:after { border-top-width: 1px; border-left-width: 1px; } -[data-popover][role="tooltip"][data-popper-placement^='left'] > [data-popper-arrow]:before { +[data-popover][role='tooltip'][data-popper-placement^='left'] > [data-popper-arrow]:before { border-top-width: 1px; border-right-width: 1px; } -[data-popover][role="tooltip"][data-popper-placement^='left'] > [data-popper-arrow]:after { +[data-popover][role='tooltip'][data-popper-placement^='left'] > [data-popper-arrow]:after { border-top-width: 1px; border-right-width: 1px; } -[data-popover][role="tooltip"][data-popper-placement^='top'] > [data-popper-arrow] { +[data-popover][role='tooltip'][data-popper-placement^='top'] > [data-popper-arrow] { bottom: -5px; } -[data-popover][role="tooltip"][data-popper-placement^='bottom'] > [data-popper-arrow] { +[data-popover][role='tooltip'][data-popper-placement^='bottom'] > [data-popper-arrow] { top: -5px; } -[data-popover][role="tooltip"][data-popper-placement^='left'] > [data-popper-arrow] { +[data-popover][role='tooltip'][data-popper-placement^='left'] > [data-popper-arrow] { right: -5px; } -[data-popover][role="tooltip"][data-popper-placement^='right'] > [data-popper-arrow] { +[data-popover][role='tooltip'][data-popper-placement^='right'] > [data-popper-arrow] { left: -5px; } -[role="tooltip"].invisible > [data-popper-arrow]:before { +[role='tooltip'].invisible > [data-popper-arrow]:before { visibility: hidden; } -[role="tooltip"].invisible > [data-popper-arrow]:after { +[role='tooltip'].invisible > [data-popper-arrow]:after { visibility: hidden; } -[type='text'],[type='email'],[type='url'],[type='password'],[type='number'],[type='date'],[type='datetime-local'],[type='month'],[type='search'],[type='tel'],[type='time'],[type='week'],[multiple],textarea,select { +[type='text'], +[type='email'], +[type='url'], +[type='password'], +[type='number'], +[type='date'], +[type='datetime-local'], +[type='month'], +[type='search'], +[type='tel'], +[type='time'], +[type='week'], +[multiple], +textarea, +select { -webkit-appearance: none; - -moz-appearance: none; - appearance: none; + -moz-appearance: none; + appearance: none; background-color: #fff; - border-color: #6B7280; + border-color: #6b7280; border-width: 1px; border-radius: 0px; padding-top: 0.5rem; @@ -636,26 +786,44 @@ video { --tw-shadow: 0 0 #0000; } -[type='text']:focus, [type='email']:focus, [type='url']:focus, [type='password']:focus, [type='number']:focus, [type='date']:focus, [type='datetime-local']:focus, [type='month']:focus, [type='search']:focus, [type='tel']:focus, [type='time']:focus, [type='week']:focus, [multiple]:focus, textarea:focus, select:focus { +[type='text']:focus, +[type='email']:focus, +[type='url']:focus, +[type='password']:focus, +[type='number']:focus, +[type='date']:focus, +[type='datetime-local']:focus, +[type='month']:focus, +[type='search']:focus, +[type='tel']:focus, +[type='time']:focus, +[type='week']:focus, +[multiple]:focus, +textarea:focus, +select:focus { outline: 2px solid transparent; outline-offset: 2px; - --tw-ring-inset: var(--tw-empty,/*!*/ /*!*/); + --tw-ring-inset: var(--tw-empty, /*!*/ /*!*/); --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; --tw-ring-color: #4f46e5; - --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); - --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) + var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) + var(--tw-ring-color); box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); border-color: #4f46e5; } -input::-moz-placeholder, textarea::-moz-placeholder { - color: #6B7280; +input::-moz-placeholder, +textarea::-moz-placeholder { + color: #6b7280; opacity: 1; } -input::placeholder,textarea::placeholder { - color: #6B7280; +input::placeholder, +textarea::placeholder { + color: #6b7280; opacity: 1; } @@ -674,10 +842,10 @@ select:not([size]) { background-size: 0.75em 0.75em; padding-right: 2.5rem; -webkit-print-color-adjust: exact; - print-color-adjust: exact; + print-color-adjust: exact; } -:is([dir=rtl]) select:not([size]) { +:is([dir='rtl']) select:not([size]) { background-position: left 0.75rem center; padding-right: 0.75rem; padding-left: 0; @@ -690,28 +858,29 @@ select:not([size]) { background-size: initial; padding-right: 0.75rem; -webkit-print-color-adjust: unset; - print-color-adjust: unset; + print-color-adjust: unset; } -[type='checkbox'],[type='radio'] { +[type='checkbox'], +[type='radio'] { -webkit-appearance: none; - -moz-appearance: none; - appearance: none; + -moz-appearance: none; + appearance: none; padding: 0; -webkit-print-color-adjust: exact; - print-color-adjust: exact; + print-color-adjust: exact; display: inline-block; vertical-align: middle; background-origin: border-box; -webkit-user-select: none; - -moz-user-select: none; - user-select: none; + -moz-user-select: none; + user-select: none; flex-shrink: 0; height: 1rem; width: 1rem; color: #4f46e5; background-color: #fff; - border-color: #6B7280; + border-color: #6b7280; border-width: 1px; --tw-shadow: 0 0 #0000; } @@ -724,19 +893,25 @@ select:not([size]) { border-radius: 100%; } -[type='checkbox']:focus,[type='radio']:focus { +[type='checkbox']:focus, +[type='radio']:focus { outline: 2px solid transparent; outline-offset: 2px; - --tw-ring-inset: var(--tw-empty,/*!*/ /*!*/); + --tw-ring-inset: var(--tw-empty, /*!*/ /*!*/); --tw-ring-offset-width: 2px; --tw-ring-offset-color: #fff; --tw-ring-color: #4f46e5; - --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); - --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) + var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) + var(--tw-ring-color); box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); } -[type='checkbox']:checked,[type='radio']:checked,.dark [type='checkbox']:checked,.dark [type='radio']:checked { +[type='checkbox']:checked, +[type='radio']:checked, +.dark [type='checkbox']:checked, +.dark [type='radio']:checked { border-color: transparent; background-color: currentColor; background-size: 0.55em 0.55em; @@ -749,7 +924,7 @@ select:not([size]) { background-repeat: no-repeat; background-size: 0.55em 0.55em; -webkit-print-color-adjust: exact; - print-color-adjust: exact; + print-color-adjust: exact; } [type='radio']:checked { @@ -770,10 +945,11 @@ select:not([size]) { background-repeat: no-repeat; background-size: 0.55em 0.55em; -webkit-print-color-adjust: exact; - print-color-adjust: exact; + print-color-adjust: exact; } -[type='checkbox']:indeterminate:hover,[type='checkbox']:indeterminate:focus { +[type='checkbox']:indeterminate:hover, +[type='checkbox']:indeterminate:focus { border-color: transparent; background-color: currentColor; } @@ -792,9 +968,9 @@ select:not([size]) { outline: 1px auto inherit; } -input[type=file]::file-selector-button { +input[type='file']::file-selector-button { color: white; - background: #1F2937; + background: #1f2937; border: 0; font-weight: 500; font-size: 0.875rem; @@ -807,25 +983,25 @@ input[type=file]::file-selector-button { margin-inline-end: 1rem; } -input[type=file]::file-selector-button:hover { +input[type='file']::file-selector-button:hover { background: #374151; } -:is([dir=rtl]) input[type=file]::file-selector-button { +:is([dir='rtl']) input[type='file']::file-selector-button { padding-right: 2rem; padding-left: 1rem; } -.dark input[type=file]::file-selector-button { +.dark input[type='file']::file-selector-button { color: white; - background: #4B5563; + background: #4b5563; } -.dark input[type=file]::file-selector-button:hover { - background: #6B7280; +.dark input[type='file']::file-selector-button:hover { + background: #6b7280; } -input[type="range"]::-webkit-slider-thumb { +input[type='range']::-webkit-slider-thumb { height: 1.25rem; width: 1.25rem; background: #4f46e5; @@ -837,25 +1013,27 @@ input[type="range"]::-webkit-slider-thumb { cursor: pointer; } -input[type="range"]:disabled::-webkit-slider-thumb { - background: #9CA3AF; +input[type='range']:disabled::-webkit-slider-thumb { + background: #9ca3af; } -.dark input[type="range"]:disabled::-webkit-slider-thumb { - background: #6B7280; +.dark input[type='range']:disabled::-webkit-slider-thumb { + background: #6b7280; } -input[type="range"]:focus::-webkit-slider-thumb { +input[type='range']:focus::-webkit-slider-thumb { outline: 2px solid transparent; outline-offset: 2px; - --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); - --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color); + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) + var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) + var(--tw-ring-color); box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); --tw-ring-opacity: 1px; --tw-ring-color: rgb(164 202 254 / var(--tw-ring-opacity)); } -input[type="range"]::-moz-range-thumb { +input[type='range']::-moz-range-thumb { height: 1.25rem; width: 1.25rem; background: #4f46e5; @@ -867,19 +1045,19 @@ input[type="range"]::-moz-range-thumb { cursor: pointer; } -input[type="range"]:disabled::-moz-range-thumb { - background: #9CA3AF; +input[type='range']:disabled::-moz-range-thumb { + background: #9ca3af; } -.dark input[type="range"]:disabled::-moz-range-thumb { - background: #6B7280; +.dark input[type='range']:disabled::-moz-range-thumb { + background: #6b7280; } -input[type="range"]::-moz-range-progress { +input[type='range']::-moz-range-progress { background: #6366f1; } -input[type="range"]::-ms-fill-lower { +input[type='range']::-ms-fill-lower { background: #6366f1; } @@ -887,7 +1065,9 @@ input[type="range"]::-ms-fill-lower { font-family: 'Inter', sans-serif !important; } -*, ::before, ::after { +*, +::before, +::after { --tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-translate-x: 0; @@ -1034,7 +1214,7 @@ input[type="range"]::-ms-fill-lower { max-width: 65ch; } -.format :where([class~="lead"]):not(:where([class~="not-format"] *)) { +.format :where([class~='lead']):not(:where([class~='not-format'] *)) { color: var(--tw-format-lead); font-size: 1.25em; line-height: 1.6; @@ -1042,110 +1222,110 @@ input[type="range"]::-ms-fill-lower { margin-bottom: 1.2em; } -.format :where(a):not(:where([class~="not-format"] *)) { +.format :where(a):not(:where([class~='not-format'] *)) { color: var(--tw-format-links); text-decoration: underline; font-weight: 500; } -.format :where(a):not(:where([class~="not-format"] *)):hover { +.format :where(a):not(:where([class~='not-format'] *)):hover { text-decoration: none; } -.format :where(strong):not(:where([class~="not-format"] *)) { +.format :where(strong):not(:where([class~='not-format'] *)) { color: var(--tw-format-bold); font-weight: 700; } -.format :where(a strong):not(:where([class~="not-format"] *)) { +.format :where(a strong):not(:where([class~='not-format'] *)) { color: inherit; } -.format :where(blockquote strong):not(:where([class~="not-format"] *)) { +.format :where(blockquote strong):not(:where([class~='not-format'] *)) { color: inherit; } -.format :where(thead th strong):not(:where([class~="not-format"] *)) { +.format :where(thead th strong):not(:where([class~='not-format'] *)) { color: inherit; } -.format :where(ol):not(:where([class~="not-format"] *)) { +.format :where(ol):not(:where([class~='not-format'] *)) { list-style-type: decimal; margin-top: 1.25em; margin-bottom: 1.25em; padding-left: 1.625em; } -.format :where(ol[type="A"]):not(:where([class~="not-format"] *)) { +.format :where(ol[type='A']):not(:where([class~='not-format'] *)) { list-style-type: upper-alpha; } -.format :where(ol[type="a"]):not(:where([class~="not-format"] *)) { +.format :where(ol[type='a']):not(:where([class~='not-format'] *)) { list-style-type: lower-alpha; } -.format :where(ol[type="A" s]):not(:where([class~="not-format"] *)) { +.format :where(ol[type='A' s]):not(:where([class~='not-format'] *)) { list-style-type: upper-alpha; } -.format :where(ol[type="a" s]):not(:where([class~="not-format"] *)) { +.format :where(ol[type='a' s]):not(:where([class~='not-format'] *)) { list-style-type: lower-alpha; } -.format :where(ol[type="I"]):not(:where([class~="not-format"] *)) { +.format :where(ol[type='I']):not(:where([class~='not-format'] *)) { list-style-type: upper-roman; } -.format :where(ol[type="i"]):not(:where([class~="not-format"] *)) { +.format :where(ol[type='i']):not(:where([class~='not-format'] *)) { list-style-type: lower-roman; } -.format :where(ol[type="I" s]):not(:where([class~="not-format"] *)) { +.format :where(ol[type='I' s]):not(:where([class~='not-format'] *)) { list-style-type: upper-roman; } -.format :where(ol[type="i" s]):not(:where([class~="not-format"] *)) { +.format :where(ol[type='i' s]):not(:where([class~='not-format'] *)) { list-style-type: lower-roman; } -.format :where(ol[type="1"]):not(:where([class~="not-format"] *)) { +.format :where(ol[type='1']):not(:where([class~='not-format'] *)) { list-style-type: decimal; } -.format :where(ul):not(:where([class~="not-format"] *)) { +.format :where(ul):not(:where([class~='not-format'] *)) { list-style-type: disc; margin-top: 1.25em; margin-bottom: 1.25em; padding-left: 1.625em; } -.format :where(ol > li):not(:where([class~="not-format"] *))::marker { +.format :where(ol > li):not(:where([class~='not-format'] *))::marker { font-weight: 400; color: var(--tw-format-counters); } -.format :where(ul > li):not(:where([class~="not-format"] *))::marker { +.format :where(ul > li):not(:where([class~='not-format'] *))::marker { color: var(--tw-format-bullets); } -.format :where(hr):not(:where([class~="not-format"] *)) { +.format :where(hr):not(:where([class~='not-format'] *)) { border-color: var(--tw-format-hr); border-top-width: 1px; margin-top: 3em; margin-bottom: 3em; } -.format :where(blockquote):not(:where([class~="not-format"] *)) { +.format :where(blockquote):not(:where([class~='not-format'] *)) { font-size: 1.1111111em; font-weight: 700; font-style: italic; color: var(--tw-format-quotes); - quotes: "\201C""\201D""\2018""\2019"; + quotes: '\201C' '\201D' '\2018' '\2019'; margin-bottom: 1.6em; } -.format :where(blockquote):not(:where([class~="not-format"] *))::before { - content: ""; +.format :where(blockquote):not(:where([class~='not-format'] *))::before { + content: ''; background-image: url("data:image/svg+xml,%0A%3Csvg width='32' height='24' viewBox='0 0 32 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M18.6893 24V14.1453C18.6893 6.54 23.664 1.38533 30.6667 -7.15256e-07L31.9933 2.868C28.7507 4.09066 26.6667 7.71867 26.6667 10.6667H32V24H18.6893ZM-9.53674e-07 24V14.1453C-9.53674e-07 6.54 4.99733 1.384 12 -7.15256e-07L13.328 2.868C10.084 4.09066 8 7.71867 8 10.6667L13.3107 10.6667V24H-9.53674e-07Z' fill='%239CA3AF'/%3E%3C/svg%3E%0A"); background-repeat: no-repeat; color: var(--tw-format-quotes); @@ -1155,15 +1335,15 @@ input[type="range"]::-ms-fill-lower { margin-top: 1.6em; } -.format :where(blockquote p:first-of-type):not(:where([class~="not-format"] *))::before { +.format :where(blockquote p:first-of-type):not(:where([class~='not-format'] *))::before { content: open-quote; } -.format :where(blockquote p:last-of-type):not(:where([class~="not-format"] *))::after { +.format :where(blockquote p:last-of-type):not(:where([class~='not-format'] *))::after { content: close-quote; } -.format :where(h1):not(:where([class~="not-format"] *)) { +.format :where(h1):not(:where([class~='not-format'] *)) { color: var(--tw-format-headings); font-weight: 800; font-size: 2.25em; @@ -1172,12 +1352,12 @@ input[type="range"]::-ms-fill-lower { line-height: 1.1111111; } -.format :where(h1 strong):not(:where([class~="not-format"] *)) { +.format :where(h1 strong):not(:where([class~='not-format'] *)) { font-weight: 900; color: inherit; } -.format :where(h2):not(:where([class~="not-format"] *)) { +.format :where(h2):not(:where([class~='not-format'] *)) { color: var(--tw-format-headings); font-weight: 700; font-size: 1.5em; @@ -1186,12 +1366,12 @@ input[type="range"]::-ms-fill-lower { line-height: 1.3333333; } -.format :where(h2 strong):not(:where([class~="not-format"] *)) { +.format :where(h2 strong):not(:where([class~='not-format'] *)) { font-weight: 800; color: inherit; } -.format :where(h3):not(:where([class~="not-format"] *)) { +.format :where(h3):not(:where([class~='not-format'] *)) { color: var(--tw-format-headings); font-weight: 700; font-size: 1.25em; @@ -1200,12 +1380,12 @@ input[type="range"]::-ms-fill-lower { line-height: 1.6; } -.format :where(h3 strong):not(:where([class~="not-format"] *)) { +.format :where(h3 strong):not(:where([class~='not-format'] *)) { font-weight: 800; color: inherit; } -.format :where(h4):not(:where([class~="not-format"] *)) { +.format :where(h4):not(:where([class~='not-format'] *)) { color: var(--tw-format-headings); font-weight: 600; margin-top: 0; @@ -1213,29 +1393,29 @@ input[type="range"]::-ms-fill-lower { line-height: 1.5; } -.format :where(h4 strong):not(:where([class~="not-format"] *)) { +.format :where(h4 strong):not(:where([class~='not-format'] *)) { font-weight: 700; color: inherit; } -.format :where(img):not(:where([class~="not-format"] *)) { +.format :where(img):not(:where([class~='not-format'] *)) { margin-top: 2em; margin-bottom: 2em; } -.format :where(figure > *):not(:where([class~="not-format"] *)) { +.format :where(figure > *):not(:where([class~='not-format'] *)) { margin-top: 0; margin-bottom: 0; } -.format :where(figcaption):not(:where([class~="not-format"] *)) { +.format :where(figcaption):not(:where([class~='not-format'] *)) { color: var(--tw-format-captions); font-size: 0.875em; line-height: 1.4285714; margin-top: 0.8571429em; } -.format :where(code):not(:where([class~="not-format"] *)) { +.format :where(code):not(:where([class~='not-format'] *)) { color: var(--tw-format-code); font-weight: 600; background-color: var(--tw-format-code-bg); @@ -1247,37 +1427,37 @@ input[type="range"]::-ms-fill-lower { font-size: 0.875em; } -.format :where(a code):not(:where([class~="not-format"] *)) { +.format :where(a code):not(:where([class~='not-format'] *)) { color: inherit; } -.format :where(h1 code):not(:where([class~="not-format"] *)) { +.format :where(h1 code):not(:where([class~='not-format'] *)) { color: inherit; } -.format :where(h2 code):not(:where([class~="not-format"] *)) { +.format :where(h2 code):not(:where([class~='not-format'] *)) { color: inherit; font-size: 0.875em; } -.format :where(h3 code):not(:where([class~="not-format"] *)) { +.format :where(h3 code):not(:where([class~='not-format'] *)) { color: inherit; font-size: 0.9em; } -.format :where(h4 code):not(:where([class~="not-format"] *)) { +.format :where(h4 code):not(:where([class~='not-format'] *)) { color: inherit; } -.format :where(blockquote code):not(:where([class~="not-format"] *)) { +.format :where(blockquote code):not(:where([class~='not-format'] *)) { color: inherit; } -.format :where(thead th code):not(:where([class~="not-format"] *)) { +.format :where(thead th code):not(:where([class~='not-format'] *)) { color: inherit; } -.format :where(pre):not(:where([class~="not-format"] *)) { +.format :where(pre):not(:where([class~='not-format'] *)) { color: var(--tw-format-pre-code); background-color: var(--tw-format-pre-bg); overflow-x: auto; @@ -1293,7 +1473,7 @@ input[type="range"]::-ms-fill-lower { padding-left: 1.1428571em; } -.format :where(pre code):not(:where([class~="not-format"] *)) { +.format :where(pre code):not(:where([class~='not-format'] *)) { background-color: transparent; border-width: 0; border-radius: 0; @@ -1305,15 +1485,15 @@ input[type="range"]::-ms-fill-lower { line-height: inherit; } -.format :where(pre code):not(:where([class~="not-format"] *))::before { +.format :where(pre code):not(:where([class~='not-format'] *))::before { content: none; } -.format :where(pre code):not(:where([class~="not-format"] *))::after { +.format :where(pre code):not(:where([class~='not-format'] *))::after { content: none; } -.format :where(table):not(:where([class~="not-format"] *)) { +.format :where(table):not(:where([class~='not-format'] *)) { width: 100%; table-layout: auto; text-align: left; @@ -1323,12 +1503,12 @@ input[type="range"]::-ms-fill-lower { line-height: 1.7142857; } -.format :where(thead):not(:where([class~="not-format"] *)) { +.format :where(thead):not(:where([class~='not-format'] *)) { background-color: var(--tw-format-th-bg); border-radius: 0.2777778em; } -.format :where(thead th):not(:where([class~="not-format"] *)) { +.format :where(thead th):not(:where([class~='not-format'] *)) { color: var(--tw-format-headings); font-weight: 600; vertical-align: bottom; @@ -1338,25 +1518,25 @@ input[type="range"]::-ms-fill-lower { padding-left: 0.5714286em; } -.format :where(tbody tr):not(:where([class~="not-format"] *)) { +.format :where(tbody tr):not(:where([class~='not-format'] *)) { border-bottom-width: 1px; border-bottom-color: var(--tw-format-td-borders); } -.format :where(tbody tr:last-child):not(:where([class~="not-format"] *)) { +.format :where(tbody tr:last-child):not(:where([class~='not-format'] *)) { border-bottom-width: 0; } -.format :where(tbody td):not(:where([class~="not-format"] *)) { +.format :where(tbody td):not(:where([class~='not-format'] *)) { vertical-align: baseline; } -.format :where(tfoot):not(:where([class~="not-format"] *)) { +.format :where(tfoot):not(:where([class~='not-format'] *)) { border-top-width: 1px; border-top-color: var(--tw-format-th-borders); } -.format :where(tfoot td):not(:where([class~="not-format"] *)) { +.format :where(tfoot td):not(:where([class~='not-format'] *)) { vertical-align: top; } @@ -1401,187 +1581,187 @@ input[type="range"]::-ms-fill-lower { line-height: 1.75; } -.format :where(p):not(:where([class~="not-format"] *)) { +.format :where(p):not(:where([class~='not-format'] *)) { margin-top: 1.25em; margin-bottom: 1.25em; } -.format :where(blockquote > p:first-child):not(:where([class~="not-format"] *)) { +.format :where(blockquote > p:first-child):not(:where([class~='not-format'] *)) { margin-top: 0; } -.format :where(video):not(:where([class~="not-format"] *)) { +.format :where(video):not(:where([class~='not-format'] *)) { margin-top: 2em; margin-bottom: 2em; } -.format :where(figure):not(:where([class~="not-format"] *)) { +.format :where(figure):not(:where([class~='not-format'] *)) { margin-top: 2em; margin-bottom: 2em; } -.format :where(li):not(:where([class~="not-format"] *)) { +.format :where(li):not(:where([class~='not-format'] *)) { margin-top: 0.5em; margin-bottom: 0.5em; } -.format :where(ol > li):not(:where([class~="not-format"] *)) { +.format :where(ol > li):not(:where([class~='not-format'] *)) { padding-left: 0.375em; } -.format :where(ul > li):not(:where([class~="not-format"] *)) { +.format :where(ul > li):not(:where([class~='not-format'] *)) { padding-left: 0.375em; } -.format :where(.format > ul > li p):not(:where([class~="not-format"] *)) { +.format :where(.format > ul > li p):not(:where([class~='not-format'] *)) { margin-top: 0.75em; margin-bottom: 0.75em; } -.format :where(.format > ul > li > *:first-child):not(:where([class~="not-format"] *)) { +.format :where(.format > ul > li > *:first-child):not(:where([class~='not-format'] *)) { margin-top: 1.25em; } -.format :where(.format > ul > li > *:last-child):not(:where([class~="not-format"] *)) { +.format :where(.format > ul > li > *:last-child):not(:where([class~='not-format'] *)) { margin-bottom: 1.25em; } -.format :where(.format > ol > li > *:first-child):not(:where([class~="not-format"] *)) { +.format :where(.format > ol > li > *:first-child):not(:where([class~='not-format'] *)) { margin-top: 1.25em; } -.format :where(.format > ol > li > *:last-child):not(:where([class~="not-format"] *)) { +.format :where(.format > ol > li > *:last-child):not(:where([class~='not-format'] *)) { margin-bottom: 1.25em; } -.format :where(ul ul, ul ol, ol ul, ol ol):not(:where([class~="not-format"] *)) { +.format :where(ul ul, ul ol, ol ul, ol ol):not(:where([class~='not-format'] *)) { margin-top: 0.75em; margin-bottom: 0.75em; } -.format :where(hr + *):not(:where([class~="not-format"] *)) { +.format :where(hr + *):not(:where([class~='not-format'] *)) { margin-top: 0; } -.format :where(h2 + *):not(:where([class~="not-format"] *)) { +.format :where(h2 + *):not(:where([class~='not-format'] *)) { margin-top: 0; } -.format :where(h3 + *):not(:where([class~="not-format"] *)) { +.format :where(h3 + *):not(:where([class~='not-format'] *)) { margin-top: 0; } -.format :where(h4 + *):not(:where([class~="not-format"] *)) { +.format :where(h4 + *):not(:where([class~='not-format'] *)) { margin-top: 0; } -.format :where(thead th:last-child):not(:where([class~="not-format"] *)) { +.format :where(thead th:last-child):not(:where([class~='not-format'] *)) { padding-right: 0; } -.format :where(tbody td, tfoot td):not(:where([class~="not-format"] *)) { +.format :where(tbody td, tfoot td):not(:where([class~='not-format'] *)) { padding-top: 0.5714286em; padding-right: 0.5714286em; padding-bottom: 0.5714286em; padding-left: 0.5714286em; } -.format :where(tbody td:last-child, tfoot td:last-child):not(:where([class~="not-format"] *)) { +.format :where(tbody td:last-child, tfoot td:last-child):not(:where([class~='not-format'] *)) { padding-right: 0; } -.format :where(.format > :first-child):not(:where([class~="not-format"] *)) { +.format :where(.format > :first-child):not(:where([class~='not-format'] *)) { margin-top: 0; } -.format :where(.format > :last-child):not(:where([class~="not-format"] *)) { +.format :where(.format > :last-child):not(:where([class~='not-format'] *)) { margin-bottom: 0; } -.format-sm :where(.format > ul > li p):not(:where([class~="not-format"] *)) { +.format-sm :where(.format > ul > li p):not(:where([class~='not-format'] *)) { margin-top: 0.5em; margin-bottom: 0.5em; } -.format-sm :where(.format > ul > li > *:first-child):not(:where([class~="not-format"] *)) { +.format-sm :where(.format > ul > li > *:first-child):not(:where([class~='not-format'] *)) { margin-top: 1em; } -.format-sm :where(.format > ul > li > *:last-child):not(:where([class~="not-format"] *)) { +.format-sm :where(.format > ul > li > *:last-child):not(:where([class~='not-format'] *)) { margin-bottom: 1em; } -.format-sm :where(.format > ol > li > *:first-child):not(:where([class~="not-format"] *)) { +.format-sm :where(.format > ol > li > *:first-child):not(:where([class~='not-format'] *)) { margin-top: 1em; } -.format-sm :where(.format > ol > li > *:last-child):not(:where([class~="not-format"] *)) { +.format-sm :where(.format > ol > li > *:last-child):not(:where([class~='not-format'] *)) { margin-bottom: 1em; } -.format-sm :where(.format > :first-child):not(:where([class~="not-format"] *)) { +.format-sm :where(.format > :first-child):not(:where([class~='not-format'] *)) { margin-top: 0; } -.format-sm :where(.format > :last-child):not(:where([class~="not-format"] *)) { +.format-sm :where(.format > :last-child):not(:where([class~='not-format'] *)) { margin-bottom: 0; } -.format-base :where(.format > ul > li p):not(:where([class~="not-format"] *)) { +.format-base :where(.format > ul > li p):not(:where([class~='not-format'] *)) { margin-top: 0.75em; margin-bottom: 0.75em; } -.format-base :where(.format > ul > li > *:first-child):not(:where([class~="not-format"] *)) { +.format-base :where(.format > ul > li > *:first-child):not(:where([class~='not-format'] *)) { margin-top: 1.25em; } -.format-base :where(.format > ul > li > *:last-child):not(:where([class~="not-format"] *)) { +.format-base :where(.format > ul > li > *:last-child):not(:where([class~='not-format'] *)) { margin-bottom: 1.25em; } -.format-base :where(.format > ol > li > *:first-child):not(:where([class~="not-format"] *)) { +.format-base :where(.format > ol > li > *:first-child):not(:where([class~='not-format'] *)) { margin-top: 1.25em; } -.format-base :where(.format > ol > li > *:last-child):not(:where([class~="not-format"] *)) { +.format-base :where(.format > ol > li > *:last-child):not(:where([class~='not-format'] *)) { margin-bottom: 1.25em; } -.format-base :where(.format > :first-child):not(:where([class~="not-format"] *)) { +.format-base :where(.format > :first-child):not(:where([class~='not-format'] *)) { margin-top: 0; } -.format-base :where(.format > :last-child):not(:where([class~="not-format"] *)) { +.format-base :where(.format > :last-child):not(:where([class~='not-format'] *)) { margin-bottom: 0; } -.format-lg :where(.format > ul > li p):not(:where([class~="not-format"] *)) { +.format-lg :where(.format > ul > li p):not(:where([class~='not-format'] *)) { margin-top: 0.8888889em; margin-bottom: 0.8888889em; } -.format-lg :where(.format > ul > li > *:first-child):not(:where([class~="not-format"] *)) { +.format-lg :where(.format > ul > li > *:first-child):not(:where([class~='not-format'] *)) { margin-top: 1.3333333em; } -.format-lg :where(.format > ul > li > *:last-child):not(:where([class~="not-format"] *)) { +.format-lg :where(.format > ul > li > *:last-child):not(:where([class~='not-format'] *)) { margin-bottom: 1.3333333em; } -.format-lg :where(.format > ol > li > *:first-child):not(:where([class~="not-format"] *)) { +.format-lg :where(.format > ol > li > *:first-child):not(:where([class~='not-format'] *)) { margin-top: 1.3333333em; } -.format-lg :where(.format > ol > li > *:last-child):not(:where([class~="not-format"] *)) { +.format-lg :where(.format > ol > li > *:last-child):not(:where([class~='not-format'] *)) { margin-bottom: 1.3333333em; } -.format-lg :where(.format > :first-child):not(:where([class~="not-format"] *)) { +.format-lg :where(.format > :first-child):not(:where([class~='not-format'] *)) { margin-top: 0; } -.format-lg :where(.format > :last-child):not(:where([class~="not-format"] *)) { +.format-lg :where(.format > :last-child):not(:where([class~='not-format'] *)) { margin-bottom: 0; } @@ -2261,36 +2441,50 @@ input[type="range"]::-ms-fill-lower { .-translate-x-\[-300px\] { --tw-translate-x: 300px; - transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) + skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) + scaleY(var(--tw-scale-y)); } .-translate-x-\[-500px\] { --tw-translate-x: 500px; - transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) + skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) + scaleY(var(--tw-scale-y)); } .-translate-x-\[300px\] { --tw-translate-x: -300px; - transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) + skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) + scaleY(var(--tw-scale-y)); } .-translate-x-\[500px\] { --tw-translate-x: -500px; - transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) + skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) + scaleY(var(--tw-scale-y)); } .translate-x-0 { --tw-translate-x: 0px; - transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) + skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) + scaleY(var(--tw-scale-y)); } .rotate-45 { --tw-rotate: 45deg; - transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) + skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) + scaleY(var(--tw-scale-y)); } .transform { - transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) + skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) + scaleY(var(--tw-scale-y)); } .cursor-default { @@ -2307,8 +2501,8 @@ input[type="range"]::-ms-fill-lower { .select-none { -webkit-user-select: none; - -moz-user-select: none; - user-select: none; + -moz-user-select: none; + user-select: none; } .resize { @@ -2947,7 +3141,7 @@ input[type="range"]::-ms-fill-lower { } .stroke-gray-300 { - stroke: #D1D5DB; + stroke: #d1d5db; } .stroke-1 { @@ -2960,7 +3154,7 @@ input[type="range"]::-ms-fill-lower { .object-scale-down { -o-object-fit: scale-down; - object-fit: scale-down; + object-fit: scale-down; } .p-0 { @@ -3180,11 +3374,34 @@ input[type="range"]::-ms-fill-lower { } .font-mono { - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace; + font-family: + ui-monospace, + SFMono-Regular, + Menlo, + Monaco, + Consolas, + Liberation Mono, + Courier New, + monospace; } .font-sans { - font-family: Inter, ui-sans-serif, system-ui, -apple-system, system-ui, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji; + font-family: + Inter, + ui-sans-serif, + system-ui, + -apple-system, + system-ui, + Segoe UI, + Roboto, + Helvetica Neue, + Arial, + Noto Sans, + sans-serif, + Apple Color Emoji, + Segoe UI Emoji, + Segoe UI Symbol, + Noto Color Emoji; } .text-3xl { @@ -3273,7 +3490,7 @@ input[type="range"]::-ms-fill-lower { } .leading-3 { - line-height: .75rem; + line-height: 0.75rem; } .leading-4 { @@ -3530,37 +3747,44 @@ input[type="range"]::-ms-fill-lower { .shadow { --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), + var(--tw-shadow); } .shadow-\[inset_0_-4px_\#3949AB\] { - --tw-shadow: inset 0 -4px #3949AB; + --tw-shadow: inset 0 -4px #3949ab; --tw-shadow-colored: inset 0 -4px var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), + var(--tw-shadow); } .shadow-\[inset_5px_0px_0px_-1px_\#3949AB\] { - --tw-shadow: inset 5px 0px 0px -1px #3949AB; + --tw-shadow: inset 5px 0px 0px -1px #3949ab; --tw-shadow-colored: inset 5px 0px 0px -1px var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), + var(--tw-shadow); } .shadow-lg { --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); - --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); + --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), + 0 4px 6px -4px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), + var(--tw-shadow); } .shadow-md { --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), + var(--tw-shadow); } .shadow-sm { --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), + var(--tw-shadow); } .outline { @@ -3568,33 +3792,63 @@ input[type="range"]::-ms-fill-lower { } .ring-0 { - --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); - --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color); + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) + var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) + var(--tw-ring-color); box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); } .blur { --tw-blur: blur(8px); - filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) + var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); } .drop-shadow-none { --tw-drop-shadow: drop-shadow(0 0 #0000); - filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) + var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); } .\!filter { - filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow) !important; + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) + var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow) !important; } .filter { - filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) + var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); } .transition { - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; + transition-property: + color, + background-color, + border-color, + text-decoration-color, + fill, + stroke, + opacity, + box-shadow, + transform, + filter, + -webkit-backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, + opacity, box-shadow, transform, filter, backdrop-filter; + transition-property: + color, + background-color, + border-color, + text-decoration-color, + fill, + stroke, + opacity, + box-shadow, + transform, + filter, + backdrop-filter, + -webkit-backdrop-filter; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 150ms; } @@ -3689,7 +3943,7 @@ input[type="range"]::-ms-fill-lower { } .visited\:text-primary-700:visited { - color: rgb(67 56 202 ); + color: rgb(67 56 202); } .hover\:cursor-default:hover { @@ -3863,14 +4117,18 @@ input[type="range"]::-ms-fill-lower { } .focus\:ring-2:focus { - --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); - --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) + var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) + var(--tw-ring-color); box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); } .focus\:ring-4:focus { - --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); - --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color); + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) + var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) + var(--tw-ring-color); box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); } @@ -4105,7 +4363,9 @@ input[type="range"]::-ms-fill-lower { .peer:checked ~ .peer-checked\:after\:translate-x-full::after { content: var(--tw-content); --tw-translate-x: 100%; - transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) + skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) + scaleY(var(--tw-scale-y)); } .peer:checked ~ .peer-checked\:after\:border-white::after { @@ -4287,12 +4547,16 @@ input[type="range"]::-ms-fill-lower { .md\:-translate-x-\[-768px\] { --tw-translate-x: 768px; - transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) + skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) + scaleY(var(--tw-scale-y)); } .md\:-translate-x-\[768px\] { --tw-translate-x: -768px; - transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) + skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) + scaleY(var(--tw-scale-y)); } .md\:grid-cols-2 { @@ -4340,12 +4604,16 @@ input[type="range"]::-ms-fill-lower { .lg\:-translate-x-1\/2 { --tw-translate-x: -50%; - transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) + skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) + scaleY(var(--tw-scale-y)); } .lg\:-translate-x-\[-50\%\] { --tw-translate-x: 50%; - transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) + skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) + scaleY(var(--tw-scale-y)); } .lg\:px-6 { @@ -4368,23 +4636,25 @@ input[type="range"]::-ms-fill-lower { } } -.rtl\:rotate-180:where([dir="rtl"], [dir="rtl"] *) { +.rtl\:rotate-180:where([dir='rtl'], [dir='rtl'] *) { --tw-rotate: 180deg; - transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) + skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) + scaleY(var(--tw-scale-y)); } -.rtl\:space-x-reverse:where([dir="rtl"], [dir="rtl"] *) > :not([hidden]) ~ :not([hidden]) { +.rtl\:space-x-reverse:where([dir='rtl'], [dir='rtl'] *) > :not([hidden]) ~ :not([hidden]) { --tw-space-x-reverse: 1; } -.\[\&\>div\>\*\:nth-child\(odd\)\]\:border-0>div>*:nth-child(odd) { +.\[\&\>div\>\*\:nth-child\(odd\)\]\:border-0 > div > *:nth-child(odd) { border-width: 0px; } -.\[\&\>div\>\*\:nth-child\(odd\)\]\:bg-transparent>div>*:nth-child(odd) { +.\[\&\>div\>\*\:nth-child\(odd\)\]\:bg-transparent > div > *:nth-child(odd) { background-color: transparent; } -.\[\&\>div\>\*\:nth-child\(odd\)\]\:pl-8>div>*:nth-child(odd) { +.\[\&\>div\>\*\:nth-child\(odd\)\]\:pl-8 > div > *:nth-child(odd) { padding-left: 2rem; } diff --git a/app/react/Markdown/MarkdownViewer.js b/app/react/Markdown/MarkdownViewer.js index 8561a3591e..bbf5ac85cb 100644 --- a/app/react/Markdown/MarkdownViewer.js +++ b/app/react/Markdown/MarkdownViewer.js @@ -1,5 +1,6 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; +import sanitizeHtml from 'sanitize-html'; import { risonDecodeOrIgnore } from 'app/utils'; import { Translate } from 'app/I18N'; import { MarkdownLink, SearchBox, MarkdownMedia, ItemList } from './components'; @@ -7,6 +8,7 @@ import CustomHookComponents from './CustomHooks'; import markdownToReact from './markdownToReact'; import { ValidatedElement } from './ValidatedElement'; +import { visualizationHtmlTags } from './utils'; class MarkdownViewer extends Component { static errorHtml(index, message) { @@ -107,8 +109,15 @@ class MarkdownViewer extends Component { return false; } + const sanitizedMarkdown = !this.props.sanitized + ? this.props.markdown + : sanitizeHtml(this.props.markdown, { + allowedTags: visualizationHtmlTags, + allowedAttributes: false, + }); + const ReactFromMarkdown = markdownToReact( - this.props.markdown, + sanitizedMarkdown, this.customComponent.bind(this), this.props.html ); @@ -120,7 +129,8 @@ class MarkdownViewer extends Component { return ValidatedElement( 'div', { className: 'markdown-viewer' }, - ...React.Children.toArray(ReactFromMarkdown) + React.Children.toArray(ReactFromMarkdown), + this.props.sanitized ); } } @@ -130,6 +140,7 @@ MarkdownViewer.defaultProps = { markdown: '', html: false, compact: false, + sanitized: true, }; MarkdownViewer.propTypes = { @@ -137,6 +148,7 @@ MarkdownViewer.propTypes = { lists: PropTypes.arrayOf(PropTypes.object), html: PropTypes.bool, compact: PropTypes.bool, + sanitized: PropTypes.bool, }; export default MarkdownViewer; diff --git a/app/react/Markdown/ValidatedElement.tsx b/app/react/Markdown/ValidatedElement.tsx index 3ba6a277d0..6a98886f62 100644 --- a/app/react/Markdown/ValidatedElement.tsx +++ b/app/react/Markdown/ValidatedElement.tsx @@ -1,14 +1,16 @@ import React from 'react'; -import { validHtmlTags } from './utils'; +import { extendedHtmlTags, visualizationHtmlTags } from './utils'; -const isValidTagName = (tagName: string): boolean => validHtmlTags.has(tagName); +const isValidTagName = (tagName: string, sanitized: boolean): boolean => + !sanitized ? extendedHtmlTags.includes(tagName) : visualizationHtmlTags.includes(tagName); const ValidatedElement = ( type: string | React.JSXElementConstructor, props: (React.Attributes & { children?: React.ReactNode }) | null, - ...children: React.ReactNode[] + children: React.ReactNode[], + sanitized = true ): React.ReactElement | null => { - if (typeof type === 'string' && !isValidTagName(type)) { + if (typeof type === 'string' && !isValidTagName(type, sanitized)) { return React.createElement('div', { className: 'error' }, `Invalid tag: ${type}`); } @@ -17,7 +19,12 @@ const ValidatedElement = ( return child.map(c => { const childProps = c.props as React.Attributes & { children?: React.ReactNode }; return React.isValidElement(c) - ? ValidatedElement(c.type, childProps, ...React.Children.toArray(childProps.children)) + ? ValidatedElement( + c.type, + childProps, + React.Children.toArray(childProps.children), + sanitized + ) : c; }); } @@ -25,7 +32,8 @@ const ValidatedElement = ( return ValidatedElement( child.type, child.props as React.Attributes & { children?: React.ReactNode }, - ...React.Children.toArray(child.props.children) + React.Children.toArray(child.props.children), + sanitized ); } return child; diff --git a/app/react/Markdown/specs/MarkdownViewer.spec.js b/app/react/Markdown/specs/MarkdownViewer.spec.js index 7484f7b619..6099cccb01 100644 --- a/app/react/Markdown/specs/MarkdownViewer.spec.js +++ b/app/react/Markdown/specs/MarkdownViewer.spec.js @@ -1,3 +1,4 @@ +/* eslint-disable no-template-curly-in-string */ /* eslint-disable max-statements */ import React, { Component } from 'react'; @@ -13,6 +14,7 @@ describe('MarkdownViewer', () => { beforeEach(() => { props = { markdown: '## MarkdownContent', + sanitized: false, }; }); @@ -213,4 +215,41 @@ describe('MarkdownViewer', () => { expect(component).toMatchSnapshot(); }); }); + + describe('limited markdown', () => { + it.each` + type | markdown + ${'forbidden tag'} | ${'
This was cleaned
'} + ${'forbidden tag'} | ${'
This was cleaned
'} + ${'malicious code'} | ${''} + `('should replace banned tags $type', ({ markdown }) => { + props = { + markdown, + html: true, + sanitized: true, + }; + render(); + + expect(component).toMatchSnapshot(); + }); + + it.each` + type | markdown + ${'custom tags'} | ${'label and \n
test
'} + ${'custom hook'} | ${''} + ${'placeholder'} | ${'$content'} + ${'standard tags'} | ${'
Title
value
'} + ${'interpolation'} | ${'${template.color}'} + ${'media'} | ${'{vimeo}(1234, options)'} + `('should keep allowed tags $type', ({ markdown }) => { + props = { + markdown, + html: true, + sanitized: true, + }; + render(); + + expect(component).toMatchSnapshot(); + }); + }); }); diff --git a/app/react/Markdown/specs/__snapshots__/MarkdownViewer.spec.js.snap b/app/react/Markdown/specs/__snapshots__/MarkdownViewer.spec.js.snap index 49a174f09e..79f670d9ca 100644 --- a/app/react/Markdown/specs/__snapshots__/MarkdownViewer.spec.js.snap +++ b/app/react/Markdown/specs/__snapshots__/MarkdownViewer.spec.js.snap @@ -1,5 +1,126 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`MarkdownViewer limited markdown should keep allowed tags custom hook 1`] = ` +
+

+ +

+ + +
+`; + +exports[`MarkdownViewer limited markdown should keep allowed tags custom tags 1`] = ` +
+

+ + label + + and +

+ + +
+ test +
+
+`; + +exports[`MarkdownViewer limited markdown should keep allowed tags interpolation 1`] = ` +
+

+ \${template.color} +

+ + +
+`; + +exports[`MarkdownViewer limited markdown should keep allowed tags media 1`] = ` +
+
+ +
+ + +
+`; + +exports[`MarkdownViewer limited markdown should keep allowed tags placeholder 1`] = ` +
+

+ + $content + +

+ + +
+`; + +exports[`MarkdownViewer limited markdown should keep allowed tags standard tags 1`] = ` +
+ + + + + + + +
+ Title +
+ value +
+
+`; + +exports[`MarkdownViewer limited markdown should replace banned tags forbidden tag 1`] = ` +
+
+ This was cleaned +
+
+`; + +exports[`MarkdownViewer limited markdown should replace banned tags forbidden tag 2`] = ` +
+
+ This was cleaned +
+
+`; + +exports[`MarkdownViewer limited markdown should replace banned tags malicious code 1`] = ` +
+ +
+`; + exports[`MarkdownViewer render should be able to render properly custom components not separated by \\n\\n 1`] = `
{ /* eslint-enable no-console */ }; -const validHtmlTags = new Set([ +const customVisualizationTags = [ + 'vimeo', + 'youtube', + 'media', + 'markdownlink', + 'markdownmedia', + 'counter', + 'entitylink', + 'itemlist', + 'repeat', + 'context', + 'map', + 'value', + 'cejilchart', + 'cejilchart001', + 'cejilchart002', + 'cejilhero', + 'placeholder', +]; +const customExtendedTags = [ + 'searchbox', + 'contactform', + 'paypaldonatelink', + 'publicform', + 'searchbox', + 'entityinfo', + 'query', + 'dataset', + 'entitydata', + 'entitysection', + 'piechart', + 'pie', + 'piechart', + 'pie', + 'barchart', + 'bar', + 'tooltip', + 'stackeddualbarchart', + 'librarychart', + 'librarycharts', + 'coloredbar', + 'extendedtooltip', +]; + +const extendedValidHtmlTags = [ + 'blink', + 'body', + 'button', + 'canvas', + 'embed', + 'element', + 'fieldset', + 'font', + 'foreignObject', + 'form', + 'iframe', + 'input', + 'image', + 'legend', + 'marker', + 'marquee', + 'object', + 'option', + 'select', + 'spacer', + 'style', + 'textarea', + 'title', + 'use', + 'var', + 'view', + 'wbr', +].concat(customExtendedTags); + +const visualizationHtmlTags = [ 'a', 'abbr', 'acronym', @@ -27,12 +101,8 @@ const validHtmlTags = new Set([ 'bdi', 'bdo', 'big', - 'blink', 'blockquote', - 'body', 'br', - 'button', - 'canvas', 'caption', 'center', 'circle', @@ -55,10 +125,8 @@ const validHtmlTags = new Set([ 'div', 'dl', 'dt', - 'element', 'ellipse', 'em', - 'embed', 'feBlend', 'feColorMatrix', 'feComponentTransfer', @@ -83,14 +151,10 @@ const validHtmlTags = new Set([ 'feSpotLight', 'feTile', 'feTurbulence', - 'fieldset', 'figcaption', 'figure', 'filter', - 'font', 'footer', - 'foreignObject', - 'form', 'g', 'h1', 'h2', @@ -104,22 +168,16 @@ const validHtmlTags = new Set([ 'hr', 'html', 'i', - 'iframe', - 'image', 'img', - 'input', 'ins', 'kbd', 'label', - 'legend', 'li', 'line', 'linearGradient', 'main', 'map', 'mark', - 'marker', - 'marquee', 'mask', 'menu', 'menuitem', @@ -127,10 +185,8 @@ const validHtmlTags = new Set([ 'meter', 'nav', 'nobr', - 'object', 'ol', 'optgroup', - 'option', 'output', 'p', 'path', @@ -148,15 +204,12 @@ const validHtmlTags = new Set([ 's', 'samp', 'section', - 'select', 'shadow', 'small', 'source', - 'spacer', 'span', 'strike', 'strong', - 'style', 'sub', 'summary', 'sup', @@ -167,23 +220,22 @@ const validHtmlTags = new Set([ 'tbody', 'td', 'template', - 'textarea', 'tfoot', 'th', 'thead', 'time', - 'title', 'tr', 'track', 'tspan', 'tt', 'u', 'ul', - 'use', - 'var', 'video', - 'view', 'wbr', -]); +].concat(customVisualizationTags); + +const extendedHtmlTags = visualizationHtmlTags + .concat(extendedValidHtmlTags) + .concat(customExtendedTags); -export { objectPath, logError, validHtmlTags }; +export { objectPath, logError, extendedHtmlTags, visualizationHtmlTags }; diff --git a/app/react/Metadata/components/specs/__snapshots__/Metadata.spec.js.snap b/app/react/Metadata/components/specs/__snapshots__/Metadata.spec.js.snap index b4ac122f4e..fd4f233999 100644 --- a/app/react/Metadata/components/specs/__snapshots__/Metadata.spec.js.snap +++ b/app/react/Metadata/components/specs/__snapshots__/Metadata.spec.js.snap @@ -66,6 +66,7 @@ exports[`Metadata should render a Markdown when the metadata is type mardown 1`] html={true} lists={Array []} markdown="some markdown text" + sanitized={true} /> @@ -113,6 +114,7 @@ exports[`Metadata should render a media field 1`] = ` html={false} lists={Array []} markdown="{media}(http://youtube.com/videoid)" + sanitized={true} /> diff --git a/app/react/Pages/components/PageViewer.js b/app/react/Pages/components/PageViewer.js index 01ad70f8b4..739b8ffc02 100644 --- a/app/react/Pages/components/PageViewer.js +++ b/app/react/Pages/components/PageViewer.js @@ -85,7 +85,12 @@ class PageViewer extends Component { {this.state.customPageError && this.renderErrorWarning()} - +
diff --git a/contents/ui-translations/ar.csv b/contents/ui-translations/ar.csv index f8c514d8dc..beee6ef5aa 100644 --- a/contents/ui-translations/ar.csv +++ b/contents/ui-translations/ar.csv @@ -397,7 +397,7 @@ Install browser extension (dynamic link),Install browser extension (dynamic link Install Language(s),Install Language(s) INSTALL the browser extension,INSTALL the browser extension "Instructions on how to achieve this will vary according to the app used, please refer to the app's documentation.","Instructions on how to achieve this will vary according to the app used, please refer to the app's documentation." -Instructions to reset password,If the email address supplied is correct +Instructions to reset password,"If the email address supplied is correct, we will send instructions to reset the password." Instructions to reset the password were sent to the user,Instructions to reset the password were sent to the user Invalid captcha,حروف التحقق غير صحيحة Invalid csv: all the values for a row must be either nested or non-nested,Invalid csv: all the values for a row must be either nested or non-nested diff --git a/contents/ui-translations/en.csv b/contents/ui-translations/en.csv index d67ee5fcd5..3c614ec83d 100644 --- a/contents/ui-translations/en.csv +++ b/contents/ui-translations/en.csv @@ -400,7 +400,7 @@ Install browser extension (dynamic link),Install browser extension (dynamic link Install Language(s),Install Language(s) INSTALL the browser extension,INSTALL the browser extension "Instructions on how to achieve this will vary according to the app used, please refer to the app's documentation.","Instructions on how to achieve this will vary according to the app used, please refer to the app's documentation." -Instructions to reset password,If the email address supplied is correct +Instructions to reset password,"If the email address supplied is correct, we will send instructions to reset the password." Instructions to reset the password were sent to the user,Instructions to reset the password were sent to the user Invalid captcha,Invalid captcha Invalid csv: all the values for a row must be either nested or non-nested,Invalid csv: all the values for a row must be either nested or non-nested diff --git a/contents/ui-translations/fr.csv b/contents/ui-translations/fr.csv index 09b362f5ef..cfab8ed70d 100644 --- a/contents/ui-translations/fr.csv +++ b/contents/ui-translations/fr.csv @@ -397,7 +397,7 @@ Install browser extension (dynamic link),Install browser extension (dynamic link Install Language(s),Install Language(s) INSTALL the browser extension,INSTALL the browser extension "Instructions on how to achieve this will vary according to the app used, please refer to the app's documentation.","Instructions on how to achieve this will vary according to the app used, please refer to the app's documentation." -Instructions to reset password,If the email address supplied is correct +Instructions to reset password,"If the email address supplied is correct, we will send instructions to reset the password." Instructions to reset the password were sent to the user,Instructions to reset the password were sent to the user Invalid captcha,Captcha invalide Invalid csv: all the values for a row must be either nested or non-nested,Invalid csv: all the values for a row must be either nested or non-nested diff --git a/contents/ui-translations/ko.csv b/contents/ui-translations/ko.csv index bc925a456d..2801cb186b 100644 --- a/contents/ui-translations/ko.csv +++ b/contents/ui-translations/ko.csv @@ -398,7 +398,7 @@ Install browser extension (dynamic link),Install browser extension (dynamic link Install Language(s),Install Language(s) INSTALL the browser extension,INSTALL the browser extension "Instructions on how to achieve this will vary according to the app used, please refer to the app's documentation.","Instructions on how to achieve this will vary according to the app used, please refer to the app's documentation." -Instructions to reset password,If the email address supplied is correct +Instructions to reset password,"If the email address supplied is correct, we will send instructions to reset the password." Instructions to reset the password were sent to the user,Instructions to reset the password were sent to the user Invalid captcha,잘못된 보안문자 Invalid csv: all the values for a row must be either nested or non-nested,Invalid csv: all the values for a row must be either nested or non-nested diff --git a/contents/ui-translations/my.csv b/contents/ui-translations/my.csv index 2cf8bd28b4..bf671dc1d3 100644 --- a/contents/ui-translations/my.csv +++ b/contents/ui-translations/my.csv @@ -398,7 +398,7 @@ Install browser extension (dynamic link),Install browser extension (dynamic link Install Language(s),Install Language(s) INSTALL the browser extension,INSTALL the browser extension "Instructions on how to achieve this will vary according to the app used, please refer to the app's documentation.","Instructions on how to achieve this will vary according to the app used, please refer to the app's documentation." -Instructions to reset password,If the email address supplied is correct +Instructions to reset password,"If the email address supplied is correct, we will send instructions to reset the password." Instructions to reset the password were sent to the user,Instructions to reset the password were sent to the user Invalid captcha,ကက်ပ်ချာ မှားနေသည် Invalid csv: all the values for a row must be either nested or non-nested,Invalid csv: all the values for a row must be either nested or non-nested diff --git a/contents/ui-translations/ru.csv b/contents/ui-translations/ru.csv index 06bd54b168..80e0929aff 100644 --- a/contents/ui-translations/ru.csv +++ b/contents/ui-translations/ru.csv @@ -395,7 +395,7 @@ Install browser extension (dynamic link),Install browser extension (dynamic link Install Language(s),Install Language(s) INSTALL the browser extension,INSTALL the browser extension "Instructions on how to achieve this will vary according to the app used, please refer to the app's documentation.","Instructions on how to achieve this will vary according to the app used, please refer to the app's documentation." -Instructions to reset password,If the email address supplied is correct +Instructions to reset password,"If the email address supplied is correct, we will send instructions to reset the password." Instructions to reset the password were sent to the user,Instructions to reset the password were sent to the user Invalid captcha,Неверная Captcha Invalid csv: all the values for a row must be either nested or non-nested,Invalid csv: all the values for a row must be either nested or non-nested diff --git a/contents/ui-translations/th.csv b/contents/ui-translations/th.csv index df41947229..bf3203ab54 100644 --- a/contents/ui-translations/th.csv +++ b/contents/ui-translations/th.csv @@ -398,7 +398,7 @@ Install browser extension (dynamic link),Install browser extension (dynamic link Install Language(s),Install Language(s) INSTALL the browser extension,INSTALL the browser extension "Instructions on how to achieve this will vary according to the app used, please refer to the app's documentation.","Instructions on how to achieve this will vary according to the app used, please refer to the app's documentation." -Instructions to reset password,If the email address supplied is correct +Instructions to reset password,"If the email address supplied is correct, we will send instructions to reset the password." Instructions to reset the password were sent to the user,Instructions to reset the password were sent to the user Invalid captcha,captcha ไม่ถูกต้อง Invalid csv: all the values for a row must be either nested or non-nested,Invalid csv: all the values for a row must be either nested or non-nested diff --git a/contents/ui-translations/tr.csv b/contents/ui-translations/tr.csv index dcf4bcff67..43c201bba0 100644 --- a/contents/ui-translations/tr.csv +++ b/contents/ui-translations/tr.csv @@ -398,7 +398,7 @@ Install browser extension (dynamic link),Install browser extension (dynamic link Install Language(s),Install Language(s) INSTALL the browser extension,INSTALL the browser extension "Instructions on how to achieve this will vary according to the app used, please refer to the app's documentation.","Instructions on how to achieve this will vary according to the app used, please refer to the app's documentation." -Instructions to reset password,If the email address supplied is correct +Instructions to reset password,"If the email address supplied is correct, we will send instructions to reset the password." Instructions to reset the password were sent to the user,Instructions to reset the password were sent to the user Invalid captcha,Geçersiz güvenlik kodu Invalid csv: all the values for a row must be either nested or non-nested,Invalid csv: all the values for a row must be either nested or non-nested diff --git a/package.json b/package.json index baac76b76a..70b497ae6e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "uwazi", - "version": "1.185.0-rc5", + "version": "1.186.0-rc3", "description": "Uwazi is a free, open-source solution for organising, analysing and publishing your documents.", "keywords": [ "react" @@ -104,14 +104,14 @@ "@fortawesome/free-regular-svg-icons": "^6.6.0", "@fortawesome/free-solid-svg-icons": "^5.15.4", "@fortawesome/react-fontawesome": "^0.2.2", - "@googlemaps/js-api-loader": "^1.16.8", + "@googlemaps/js-api-loader": "^1.16.6", "@headlessui/react": "1.7.17", "@heroicons/react": "^2.1.5", "@hookform/error-message": "^2.0.1", "@huridocs/react-text-selection-handler": "^0.3.0", "@loadable/component": "^5.16.4", "@popperjs/core": "^2.11.8", - "@remix-run/router": "^1.16.1", + "@remix-run/router": "1.19.2", "@sentry/node": "^7.114.0", "@sentry/react": "7.114.0", "@sentry/tracing": "^7.114.0", @@ -119,7 +119,7 @@ "@socket.io/redis-adapter": "7.2.0", "@socket.io/redis-emitter": "5.1.0", "@supercharge/promise-pool": "^3.2.0", - "@tanstack/react-table": "^8.19.3", + "@tanstack/react-table": "8.20.5", "@types/franc": "^5.0.1", "@types/lodash": "^4.14.170", "@types/mime-types": "^2.1.1", @@ -132,7 +132,7 @@ "ajv-keywords": "^5.1.0", "any-date-parser": "^1.5.4", "bcryptjs": "^2.4.3", - "big.js": "^6.2.2", + "big.js": "^6.2.1", "body-parser": "^1.20.2", "bootstrap": "3.4.1", "child-process-promise": "^2.2.1", @@ -148,10 +148,10 @@ "diacritics": "^1.3.0", "dotenv": "^16.4.5", "express": "^4.19.2", - "express-http-proxy": "^2.0.0", + "express-http-proxy": "2.1.1", "express-prom-bundle": "^7.0.0", "express-session": "1.18.0", - "filesize": "^10.1.4", + "filesize": "10.1.6", "flag-icon-css": "^4.1.7", "flowbite": "^2.3.0", "flowbite-datepicker": "^1.2.7", @@ -165,7 +165,7 @@ "immutable": "^3.7.6", "is-reachable": "^5.2.1", "isomorphic-fetch": "3.0.0", - "jotai": "^2.9.1", + "jotai": "2.9.3", "json-schema": "^0.4.0", "json-schema-to-typescript": "^13.1.2", "jvent": "1.0.2", @@ -173,7 +173,7 @@ "leaflet.gridlayer.googlemutant": "^0.14.1", "leaflet.markercluster": "^1.5.3", "lodash": "^4.17.21", - "luxon": "^3.4.4", + "luxon": "3.5.0", "mark.js": "^8.11.1", "markdown-it": "14.1.0", "markdown-it-container": "4.0.0", @@ -186,17 +186,17 @@ "mongoose": "8.1.2", "multer": "^1.4.5-lts.1", "node-uuid": "^1.4.7", - "nodemailer": "^6.9.14", + "nodemailer": "6.9.15", "nprogress": "^0.2.0", "otplib": "^11.0.1", "passport": "^0.7.0", "passport-local": "^1.0.0", - "pdfjs-dist": "^4.5.136", + "pdfjs-dist": "4.6.82", "postcss-loader": "^8.1.1", - "postcss-prefix-selector": "^1.16.1", + "postcss-prefix-selector": "^1.16.0", "prom-client": "^15.1.3", "prop-types": "^15.8.1", - "qrcode.react": "^3.1.0", + "qrcode.react": "^4.0.1", "qs": "^6.13.0", "react": "^18.3.1", "react-color": "^2.19.3", @@ -217,7 +217,7 @@ "react-redux": "5.0.6", "react-redux-form": "^1.16.14", "react-render-if-visible": "^2.1.1", - "react-router-dom": "6.23.1", + "react-router-dom": "6.26.2", "react-table": "^7.8.0", "react-table-sticky": "^1.1.3", "react-tabs": "^6.0.2", @@ -234,16 +234,17 @@ "rsmq": "^0.12.4", "rtlcss": "^4.3.0", "sanitize-filename": "^1.6.3", + "sanitize-html": "^2.13.0", "serialize-javascript": "^6.0.1", "sift": "^17.1.3", "socket.io": "4.7.5", "socket.io-client": "4.7.5", "socket.io-parser": "4.2.4", - "stopword": "^3.0.1", + "stopword": "3.1.1", "superagent": "10.1.0", "svg-captcha": "^1.4.0", "tiny-cookie": "^2.5.1", - "typescript": "5.5.4", + "typescript": "5.6.2", "underscore": "^1.13.7", "url-join": "^4.0.1", "world-countries": "5.0.0", @@ -254,7 +255,7 @@ }, "devDependencies": { "@4tw/cypress-drag-drop": "^2.2.5", - "@babel/cli": "7.24.8", + "@babel/cli": "7.25.6", "@babel/core": "7.25.2", "@babel/eslint-parser": "7.25.1", "@babel/helper-call-delegate": "^7.12.13", @@ -286,7 +287,7 @@ "@storybook/react": "^8.1.11", "@storybook/react-webpack5": "^8.1.11", "@storybook/test": "^8.1.11", - "@testing-library/jest-dom": "^6.4.8", + "@testing-library/jest-dom": "6.5.0", "@testing-library/react": "^15.0.7", "@testing-library/user-event": "^14.5.2", "@types/body-parser": "^1.19.2", @@ -296,7 +297,7 @@ "@types/enzyme": "3.10.12", "@types/enzyme-adapter-react-16": "1.0.2", "@types/expect-puppeteer": "4.4.7", - "@types/express": "4.17.13", + "@types/express": "4.17.21", "@types/express-session": "1.18.0", "@types/google.maps": "^3.48.2", "@types/immutable": "^3.8.7", @@ -343,7 +344,7 @@ "copy-webpack-plugin": "12.0.2", "css-loader": "^7.1.2", "css-minimizer-webpack-plugin": "^7.0.0", - "cypress": "13.13.2", + "cypress": "13.14.1", "cypress-axe": "^1.5.0", "cypress-plugin-snapshots": "^1.4.4", "cypress-real-events": "^1.13.0", @@ -353,12 +354,12 @@ "eslint-config-airbnb": "19.0.4", "eslint-plugin-cypress": "^3.5.0", "eslint-plugin-import": "v2.29.1", - "eslint-plugin-jasmine": "4.2.0", - "eslint-plugin-jest": "v28.8.0", - "eslint-plugin-jsx-a11y": "6.7.1", + "eslint-plugin-jasmine": "4.2.1", + "eslint-plugin-jest": "v28.8.2", + "eslint-plugin-jsx-a11y": "6.10.0", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "5.1.3", - "eslint-plugin-react": "v7.34.3", + "eslint-plugin-react": "v7.36.1", "eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-storybook": "^0.8.0", "fetch-mock": "^9.11.0", @@ -376,19 +377,19 @@ "node-polyfill-webpack-plugin": "^4.0.0", "nodemon": "^3.1.4", "plop": "^4.0.1", - "postcss": "^8.4.41", - "prettier": "3.2.5", + "postcss": "8.4.45", + "prettier": "3.3.3", "puppeteer": "^13.5.2", "react-dnd-test-backend": "15.1.1", "redux-mock-store": "^1.5.4", "rtlcss-webpack-plugin": "4.0.7", - "sass": "^1.77.8", + "sass": "1.78.0", "sass-loader": "16.0.1", "storybook": "^8.1.11", "stream-mock": "^2.0.5", "supertest": "7.0.0", "svg-inline-loader": "^0.8.2", - "tailwindcss": "^3.4.1", + "tailwindcss": "3.4.10", "terser-webpack-plugin": "^5.3.10", "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", @@ -397,7 +398,7 @@ "webpack": "^5.94.0", "webpack-bundle-analyzer": "^4.10.2", "webpack-cli": "5.1.4", - "webpack-dev-middleware": "7.3.0", + "webpack-dev-middleware": "7.4.2", "webpack-hot-middleware": "^2.26.1", "worker-loader": "^3.0.8" }, diff --git a/yarn.lock b/yarn.lock index 8eeac25661..0c3b9a4f23 100644 --- a/yarn.lock +++ b/yarn.lock @@ -623,10 +623,10 @@ "@smithy/types" "^3.3.0" tslib "^2.6.2" -"@babel/cli@7.24.8": - version "7.24.8" - resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.24.8.tgz#79eaa55a69c77cafbea3e87537fd1df5a5a2edf8" - integrity sha512-isdp+G6DpRyKc+3Gqxy2rjzgF7Zj9K0mzLNnxz+E/fgeag8qT3vVulX4gY9dGO1q0y+0lUv6V3a+uhUzMzrwXg== +"@babel/cli@7.25.6": + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.25.6.tgz#bc35561adc78ade43ac9c09a690768493ab9ed95" + integrity sha512-Z+Doemr4VtvSD2SNHTrkiFZ1LX+JI6tyRXAAOb4N9khIuPyoEPmTPJarPm8ljJV1D6bnMQjyHMWTT9NeKbQuXA== dependencies: "@jridgewell/trace-mapping" "^0.3.25" commander "^6.2.0" @@ -637,7 +637,7 @@ slash "^2.0.0" optionalDependencies: "@nicolo-ribaudo/chokidar-2" "2.1.8-no-fsevents.3" - chokidar "^3.4.0" + chokidar "^3.6.0" "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.24.7": version "7.24.7" @@ -1742,7 +1742,7 @@ resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.12.18", "@babel/runtime@^7.12.5", "@babel/runtime@^7.17.8", "@babel/runtime@^7.20.7", "@babel/runtime@^7.24.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.12.18", "@babel/runtime@^7.12.5", "@babel/runtime@^7.17.8", "@babel/runtime@^7.24.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.7.tgz#f4f0d5530e8dbdf59b3451b9b3e594b6ba082e12" integrity sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw== @@ -2287,10 +2287,12 @@ dependencies: prop-types "^15.8.1" -"@googlemaps/js-api-loader@^1.16.8": - version "1.16.8" - resolved "https://registry.yarnpkg.com/@googlemaps/js-api-loader/-/js-api-loader-1.16.8.tgz#1595a2af80ca07e551fc961d921a2437d1cb3643" - integrity sha512-CROqqwfKotdO6EBjZO/gQGVTbeDps5V7Mt9+8+5Q+jTg5CRMi3Ii/L9PmV3USROrt2uWxtGzJHORmByxyo9pSQ== +"@googlemaps/js-api-loader@^1.16.6": + version "1.16.6" + resolved "https://registry.yarnpkg.com/@googlemaps/js-api-loader/-/js-api-loader-1.16.6.tgz#c89970c94b55796d51746c092f0e52953994a171" + integrity sha512-V8p5W9DbPQx74jWUmyYJOerhiB4C+MHekaO0ZRmc6lrOYrvY7+syLhzOWpp55kqSPeNb+qbC2h8i69aLIX6krQ== + dependencies: + fast-deep-equal "^3.1.3" "@hapi/hoek@^9.0.0": version "9.3.0" @@ -3243,7 +3245,7 @@ resolved "https://registry.yarnpkg.com/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz#d1b4befa423f692fa4abf1c79209702e7d8ae4b4" integrity sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA== -"@remix-run/router@1.19.2", "@remix-run/router@^1.19.2": +"@remix-run/router@1.19.2": version "1.19.2" resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.19.2.tgz#0c896535473291cb41f152c180bedd5680a3b273" integrity sha512-baiMx18+IMuD1yyvOGaHM9QrVUPGGG0jC+z+IPHnRJWUAUvaKuWKyE8gjDj2rzv3sz9zOGoRSPgeBVHRhZnBlA== @@ -4646,7 +4648,7 @@ dependencies: defer-to-connect "^2.0.0" -"@tanstack/react-table@^8.20.5": +"@tanstack/react-table@8.20.5": version "8.20.5" resolved "https://registry.yarnpkg.com/@tanstack/react-table/-/react-table-8.20.5.tgz#19987d101e1ea25ef5406dce4352cab3932449d8" integrity sha512-WEHopKw3znbUZ61s9i0+i9g8drmDo6asTWbrQh8Us63DAk/M0FkmIqERew6P71HI75ksZ2Pxyuf4vvKh9rAkiA== @@ -4686,7 +4688,7 @@ lodash "^4.17.21" redent "^3.0.0" -"@testing-library/jest-dom@^6.5.0": +"@testing-library/jest-dom@6.5.0": version "6.5.0" resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.5.0.tgz#50484da3f80fb222a853479f618a9ce5c47bfe54" integrity sha512-xGGHpBXYSHUUr6XsKBfs85TWlYKpTc37cSBBVrXcib2MkHLboWlkClhWF37JKlDb9KEq3dHs+f2xR7XJEWGBxA== @@ -6166,19 +6168,26 @@ aria-hidden@^1.1.1: dependencies: tslib "^2.0.0" -aria-query@5.3.0, aria-query@^5.0.0, aria-query@^5.1.3: +aria-query@5.3.0, aria-query@^5.0.0: version "5.3.0" resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.0.tgz#650c569e41ad90b51b3d7df5e5eed1c7549c103e" integrity sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A== dependencies: dequal "^2.0.3" +aria-query@~5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.1.3.tgz#19db27cd101152773631396f7a95a3b58c22c35e" + integrity sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ== + dependencies: + deep-equal "^2.0.5" + arr-union@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz" integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= -array-buffer-byte-length@^1.0.1: +array-buffer-byte-length@^1.0.0, array-buffer-byte-length@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f" integrity sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg== @@ -6266,7 +6275,7 @@ array.prototype.flat@^1.2.3, array.prototype.flat@^1.3.1, array.prototype.flat@^ es-abstract "^1.22.1" es-shim-unscopables "^1.0.0" -array.prototype.flatmap@^1.3.1, array.prototype.flatmap@^1.3.2: +array.prototype.flatmap@^1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz#c9a7c6831db8e719d6ce639190146c24bbd3e527" integrity sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ== @@ -6363,10 +6372,10 @@ assets-webpack-plugin@7.1.1: escape-string-regexp "^4.0.0" lodash "^4.17.21" -ast-types-flow@^0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" - integrity sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag== +ast-types-flow@^0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.8.tgz#0a85e1c92695769ac13a428bb653e7538bea27d6" + integrity sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ== ast-types@^0.16.1: version "0.16.1" @@ -6417,10 +6426,10 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.12.0.tgz#ce1c9d143389679e253b314241ea9aa5cec980d3" integrity sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg== -axe-core@^4.6.2: - version "4.8.3" - resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.8.3.tgz#205df863dd9917d5979e9435dab4d47692759051" - integrity sha512-d5ZQHPSPkF9Tw+yfyDcRoUOc4g/8UloJJe5J8m4L5+c7AtDdjDLRxew/knnI4CxvtdxEUVgWz4x3OIQUIFiMfw== +axe-core@^4.10.0: + version "4.10.0" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.10.0.tgz#d9e56ab0147278272739a000880196cdfe113b59" + integrity sha512-Mr2ZakwQ7XUAjp7pAwQWRhhK8mQQ6JAaNWSjmjxil0R8BPioMtQsTLOolGYkji1rcL++3dCqZA3zWqpT+9Ew6g== axios@^0.25.0: version "0.25.0" @@ -6429,12 +6438,10 @@ axios@^0.25.0: dependencies: follow-redirects "^1.14.7" -axobject-query@^3.1.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.2.1.tgz#39c378a6e3b06ca679f29138151e45b2b32da62a" - integrity sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg== - dependencies: - dequal "^2.0.3" +axobject-query@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-4.1.0.tgz#28768c76d0e3cff21bc62a9e2d0b6ac30042a1ee" + integrity sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ== babel-core@^7.0.0-bridge.0: version "7.0.0-bridge.0" @@ -6611,10 +6618,10 @@ big.js@^5.2.2: resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== -big.js@^6.2.2: - version "6.2.2" - resolved "https://registry.yarnpkg.com/big.js/-/big.js-6.2.2.tgz#be3bb9ac834558b53b099deef2a1d06ac6368e1a" - integrity sha512-y/ie+Faknx7sZA5MfGA2xKlu0GDv8RWrXGsmlteyJQ2lvoKv9GBK/fpRMc2qlSoBAgNxrixICFCBefIq8WCQpQ== +big.js@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-6.2.1.tgz#7205ce763efb17c2e41f26f121c420c6a7c2744f" + integrity sha512-bCtHMwL9LeDIozFn+oNhhFoq+yQ3BNdnsLSASUxLciOb1vgvpHsIO1dsENiGMgbb4SkP5TrzWzRiLddn8ahVOQ== binary-extensions@^2.0.0: version "2.1.0" @@ -6665,10 +6672,10 @@ bn.js@^5.0.0, bn.js@^5.2.1: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== -body-parser@1.20.3: - version "1.20.3" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6" - integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g== +body-parser@1.20.2, body-parser@^1.20.2: + version "1.20.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" + integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== dependencies: bytes "3.1.2" content-type "~1.0.5" @@ -6678,15 +6685,15 @@ body-parser@1.20.3: http-errors "2.0.0" iconv-lite "0.4.24" on-finished "2.4.1" - qs "6.13.0" + qs "6.11.0" raw-body "2.5.2" type-is "~1.6.18" unpipe "1.0.0" -body-parser@^1.20.2: - version "1.20.2" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" - integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== +body-parser@1.20.3: + version "1.20.3" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6" + integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g== dependencies: bytes "3.1.2" content-type "~1.0.5" @@ -6696,7 +6703,7 @@ body-parser@^1.20.2: http-errors "2.0.0" iconv-lite "0.4.24" on-finished "2.4.1" - qs "6.11.0" + qs "6.13.0" raw-body "2.5.2" type-is "~1.6.18" unpipe "1.0.0" @@ -7109,7 +7116,7 @@ child-process-promise@^2.2.1: node-version "^1.0.0" promise-polyfill "^6.0.1" -"chokidar@>=3.0.0 <4.0.0", chokidar@^3.4.0, chokidar@^3.5.2, chokidar@^3.5.3: +"chokidar@>=3.0.0 <4.0.0", chokidar@^3.5.2, chokidar@^3.5.3, chokidar@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== @@ -7969,10 +7976,10 @@ cypress-real-events@^1.13.0: resolved "https://registry.yarnpkg.com/cypress-real-events/-/cypress-real-events-1.13.0.tgz#6b7cd32dcac172db1493608f97a2576c7d0bd5af" integrity sha512-LoejtK+dyZ1jaT8wGT5oASTPfsNV8/ClRp99ruN60oPj8cBJYod80iJDyNwfPAu4GCxTXOhhAv9FO65Hpwt6Hg== -cypress@13.14.2: - version "13.14.2" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-13.14.2.tgz#4237eb7b26de2baeaa1f01e585f965d88fca7f39" - integrity sha512-lsiQrN17vHMB2fnvxIrKLAjOr9bPwsNbPZNrWf99s4u+DVmCY6U+w7O3GGG9FvP4EUVYaDu+guWeNLiUzBrqvA== +cypress@13.14.1: + version "13.14.1" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-13.14.1.tgz#05875bbbf6333e858a92aed33ba67d7ddf8370d7" + integrity sha512-Wo+byPmjps66hACEH5udhXINEiN3qS3jWNGRzJOjrRJF3D0+YrcP2LVB1T7oYaVQM/S+eanqEvBWYc8cf7Vcbg== dependencies: "@cypress/request" "^3.0.1" "@cypress/xvfb" "^1.2.4" @@ -8243,6 +8250,30 @@ deep-eql@^4.1.3: dependencies: type-detect "^4.0.0" +deep-equal@^2.0.5: + version "2.2.3" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.2.3.tgz#af89dafb23a396c7da3e862abc0be27cf51d56e1" + integrity sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA== + dependencies: + array-buffer-byte-length "^1.0.0" + call-bind "^1.0.5" + es-get-iterator "^1.1.3" + get-intrinsic "^1.2.2" + is-arguments "^1.1.1" + is-array-buffer "^3.0.2" + is-date-object "^1.0.5" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.2" + isarray "^2.0.5" + object-is "^1.1.5" + object-keys "^1.1.1" + object.assign "^4.1.4" + regexp.prototype.flags "^1.5.1" + side-channel "^1.0.4" + which-boxed-primitive "^1.0.2" + which-collection "^1.0.1" + which-typed-array "^1.1.13" + deep-is@^0.1.3: version "0.1.3" resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz" @@ -9026,6 +9057,21 @@ es-errors@^1.2.1, es-errors@^1.3.0: resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== +es-get-iterator@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.3.tgz#3ef87523c5d464d41084b2c3c9c214f1199763d6" + integrity sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.3" + has-symbols "^1.0.3" + is-arguments "^1.1.1" + is-map "^2.0.2" + is-set "^2.0.2" + is-string "^1.0.7" + isarray "^2.0.5" + stop-iteration-iterator "^1.0.0" + es-iterator-helpers@^1.0.19: version "1.0.19" resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.0.19.tgz#117003d0e5fec237b4b5c08aded722e0c6d50ca8" @@ -9307,39 +9353,39 @@ eslint-plugin-import@v2.29.1: semver "^6.3.1" tsconfig-paths "^3.15.0" -eslint-plugin-jasmine@4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-jasmine/-/eslint-plugin-jasmine-4.2.0.tgz#ed0fe988b6e3b123905a7bf68d77239649fd018c" - integrity sha512-zSCsnP4gMqBSt8jApExP0ja43nAI1fpAD5kY+knrIJylBxC/LEth25PkqcKJqW32GjesjsiA1SSSR3Z5qIranA== +eslint-plugin-jasmine@4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-jasmine/-/eslint-plugin-jasmine-4.2.1.tgz#80925dc6b24ee263eb834bedb09ff4abf3740b3d" + integrity sha512-Vwecc66rjMgz2e9UtGScsUdo6D+SbfgPA4Kf0zdAl4+5IQMRL0mXd8973MaZuYYF89XpRjQEGl5TNmg2Bv+KcQ== -eslint-plugin-jest@v28.8.0: - version "28.8.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-28.8.0.tgz#54f597b5a3295ad04ec946baa245ad02b9b2bca0" - integrity sha512-Tubj1hooFxCl52G4qQu0edzV/+EZzPUeN8p2NnW5uu4fbDs+Yo7+qDVDc4/oG3FbCqEBmu/OC3LSsyiU22oghw== +eslint-plugin-jest@v28.8.2: + version "28.8.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-28.8.2.tgz#7f8307179c5cf8d51101b3aa002be168daadecbc" + integrity sha512-mC3OyklHmS5i7wYU1rGId9EnxRI8TVlnFG56AE+8U9iRy6zwaNygZR+DsdZuCL0gRG0wVeyzq+uWcPt6yJrrMA== dependencies: "@typescript-eslint/utils" "^6.0.0 || ^7.0.0 || ^8.0.0" -eslint-plugin-jsx-a11y@6.7.1: - version "6.7.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.7.1.tgz#fca5e02d115f48c9a597a6894d5bcec2f7a76976" - integrity sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA== +eslint-plugin-jsx-a11y@6.10.0: + version "6.10.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.0.tgz#36fb9dead91cafd085ddbe3829602fb10ef28339" + integrity sha512-ySOHvXX8eSN6zz8Bywacm7CvGNhUtdjvqfQDVe6020TUK34Cywkw7m0KsCCk1Qtm9G1FayfTN1/7mMYnYO2Bhg== dependencies: - "@babel/runtime" "^7.20.7" - aria-query "^5.1.3" - array-includes "^3.1.6" - array.prototype.flatmap "^1.3.1" - ast-types-flow "^0.0.7" - axe-core "^4.6.2" - axobject-query "^3.1.1" + aria-query "~5.1.3" + array-includes "^3.1.8" + array.prototype.flatmap "^1.3.2" + ast-types-flow "^0.0.8" + axe-core "^4.10.0" + axobject-query "^4.1.0" damerau-levenshtein "^1.0.8" emoji-regex "^9.2.2" - has "^1.0.3" - jsx-ast-utils "^3.3.3" - language-tags "=1.0.5" + es-iterator-helpers "^1.0.19" + hasown "^2.0.2" + jsx-ast-utils "^3.3.5" + language-tags "^1.0.9" minimatch "^3.1.2" - object.entries "^1.1.6" - object.fromentries "^2.0.6" - semver "^6.3.0" + object.fromentries "^2.0.8" + safe-regex-test "^1.0.3" + string.prototype.includes "^2.0.0" eslint-plugin-node@^11.1.0: version "11.1.0" @@ -9659,7 +9705,7 @@ expect@^29.0.0, expect@^29.7.0: jest-message-util "^29.7.0" jest-util "^29.7.0" -express-http-proxy@^2.1.1: +express-http-proxy@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/express-http-proxy/-/express-http-proxy-2.1.1.tgz#90bd7eaee5166be968157b035eb6b499d2af2bf4" integrity sha512-4aRQRqDQU7qNPV5av0/hLcyc0guB9UP71nCYrQEYml7YphTo8tmWf3nDZWdTJMMjFikyz9xKXaURor7Chygdwg== @@ -9692,7 +9738,44 @@ express-session@1.18.0: safe-buffer "5.2.1" uid-safe "~2.1.5" -express@^4.17.3, express@^4.18.2, express@^4.20.0: +express@^4.17.3, express@^4.18.2: + version "4.19.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465" + integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q== + dependencies: + accepts "~1.3.8" + array-flatten "1.1.1" + body-parser "1.20.2" + content-disposition "0.5.4" + content-type "~1.0.4" + cookie "0.6.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "2.0.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.2.0" + fresh "0.5.2" + http-errors "2.0.0" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "2.4.1" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.7" + qs "6.11.0" + range-parser "~1.2.1" + safe-buffer "5.2.1" + send "0.18.0" + serve-static "1.15.0" + setprototypeof "1.2.0" + statuses "2.0.1" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +express@^4.19.2: version "4.21.0" resolved "https://registry.yarnpkg.com/express/-/express-4.21.0.tgz#d57cb706d49623d4ac27833f1cbc466b668eb915" integrity sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng== @@ -9924,16 +10007,16 @@ filelist@^1.0.4: dependencies: minimatch "^5.0.1" +filesize@10.1.6: + version "10.1.6" + resolved "https://registry.yarnpkg.com/filesize/-/filesize-10.1.6.tgz#31194da825ac58689c0bce3948f33ce83aabd361" + integrity sha512-sJslQKU2uM33qH5nqewAwVB2QgR6w1aMNsYUp3aN5rMRyXEwJGmZvaWzeJFNTOXWlHQyBFCWrdj3fV/fsTOX8w== + filesize@^10.0.12: version "10.1.4" resolved "https://registry.yarnpkg.com/filesize/-/filesize-10.1.4.tgz#184f256063a201f08b6e6b3cc47d21b60f5b8d89" integrity sha512-ryBwPIIeErmxgPnm6cbESAzXjuEFubs+yKYLBZvg3CaiNcmkJChoOGcBSrZ6IwkMwPABwPpVXE6IlNdGJJrvEg== -filesize@^10.1.6: - version "10.1.6" - resolved "https://registry.yarnpkg.com/filesize/-/filesize-10.1.6.tgz#31194da825ac58689c0bce3948f33ce83aabd361" - integrity sha512-sJslQKU2uM33qH5nqewAwVB2QgR6w1aMNsYUp3aN5rMRyXEwJGmZvaWzeJFNTOXWlHQyBFCWrdj3fV/fsTOX8w== - fill-range@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" @@ -9941,6 +10024,19 @@ fill-range@^7.1.1: dependencies: to-regex-range "^5.0.1" +finalhandler@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" + integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "2.4.1" + parseurl "~1.3.3" + statuses "2.0.1" + unpipe "~1.0.0" + finalhandler@1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.1.tgz#0c575f1d1d324ddd1da35ad7ece3df7d19088019" @@ -10375,7 +10471,7 @@ get-func-name@^2.0.1, get-func-name@^2.0.2: resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== -get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: +get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== @@ -10983,6 +11079,16 @@ htmlparser2@^6.1.0: domutils "^2.5.2" entities "^2.0.0" +htmlparser2@^8.0.0: + version "8.0.2" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz#f002151705b383e62433b5cf466f5b716edaec21" + integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.3" + domutils "^3.0.1" + entities "^4.4.0" + htmlparser2@^9.0, htmlparser2@^9.1.0: version "9.1.0" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-9.1.0.tgz#cdb498d8a75a51f739b61d3f718136c369bc8c23" @@ -11216,7 +11322,7 @@ inquirer@^9.2.10: strip-ansi "^6.0.1" wrap-ansi "^6.2.0" -internal-slot@^1.0.7: +internal-slot@^1.0.4, internal-slot@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.7.tgz#c06dcca3ed874249881007b0a5523b172a190802" integrity sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g== @@ -11260,7 +11366,7 @@ is-absolute@^1.0.0: is-relative "^1.0.0" is-windows "^1.0.1" -is-arguments@^1.0.4: +is-arguments@^1.0.4, is-arguments@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== @@ -11268,7 +11374,7 @@ is-arguments@^1.0.4: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-array-buffer@^3.0.4: +is-array-buffer@^3.0.2, is-array-buffer@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98" integrity sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw== @@ -11426,7 +11532,7 @@ is-interactive@^2.0.0: resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-2.0.0.tgz#40c57614593826da1100ade6059778d597f16e90" integrity sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ== -is-map@^2.0.1: +is-map@^2.0.1, is-map@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== @@ -11544,7 +11650,7 @@ is-relative@^1.0.0: dependencies: is-unc-path "^1.0.0" -is-set@^2.0.1: +is-set@^2.0.1, is-set@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d" integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== @@ -12323,7 +12429,7 @@ joi@^17.6.0: "@sideway/formula" "^3.0.0" "@sideway/pinpoint" "^2.0.0" -jotai@^2.9.3: +jotai@2.9.3: version "2.9.3" resolved "https://registry.yarnpkg.com/jotai/-/jotai-2.9.3.tgz#abcae49a737cd50e3144a6c9eb39840db077c727" integrity sha512-IqMWKoXuEzWSShjd9UhalNsRGbdju5G2FrqNLQJT+Ih6p41VNYe2sav5hnwQx4HJr25jq9wRqvGSWGviGG6Gjw== @@ -12530,7 +12636,7 @@ jsprim@^2.0.2: json-schema "0.4.0" verror "1.10.0" -"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.3: +"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.5: version "3.3.5" resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz#4766bd05a8e2a11af222becd19e15575e52a853a" integrity sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ== @@ -12588,17 +12694,17 @@ kruptein@^3.0.0: dependencies: asn1.js "^5.4.1" -language-subtag-registry@~0.3.2: - version "0.3.22" - resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz#2e1500861b2e457eba7e7ae86877cbd08fa1fd1d" - integrity sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w== +language-subtag-registry@^0.3.20: + version "0.3.23" + resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz#23529e04d9e3b74679d70142df3fd2eb6ec572e7" + integrity sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ== -language-tags@=1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.5.tgz#d321dbc4da30ba8bf3024e040fa5c14661f9193a" - integrity sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ== +language-tags@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.9.tgz#1ffdcd0ec0fafb4b1be7f8b11f306ad0f9c08777" + integrity sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA== dependencies: - language-subtag-registry "~0.3.2" + language-subtag-registry "^0.3.20" lazy-ass@^1.6.0: version "1.6.0" @@ -12956,7 +13062,7 @@ lru-queue@^0.1.0: dependencies: es5-ext "~0.10.2" -luxon@^3.5.0: +luxon@3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.5.0.tgz#6b6f65c5cd1d61d1fd19dbf07ee87a50bf4b8e20" integrity sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ== @@ -13128,6 +13234,10 @@ merge-deep@^3.0.3: clone-deep "^0.2.4" kind-of "^3.0.2" +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz" + merge-descriptors@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5" @@ -13610,7 +13720,7 @@ node-version@^1.0.0: version "1.2.0" resolved "https://registry.npmjs.org/node-version/-/node-version-1.2.0.tgz" -nodemailer@^6.9.14: +nodemailer@6.9.15: version "6.9.15" resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.9.15.tgz#57b79dc522be27e0e47ac16cc860aa0673e62e04" integrity sha512-AHf04ySLC6CIfuRtRiEYtGEXgRfa6INgWGluDhnxTZhHSKvrBu7lc1VVchQ0d8nPc4cFaZoPq8vkyNoZr0TpGQ== @@ -13767,7 +13877,7 @@ object.defaults@^1.1.0: for-own "^1.0.0" isobject "^3.0.0" -object.entries@^1.1.1, object.entries@^1.1.5, object.entries@^1.1.6, object.entries@^1.1.8: +object.entries@^1.1.1, object.entries@^1.1.5, object.entries@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.8.tgz#bffe6f282e01f4d17807204a24f8edd823599c41" integrity sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ== @@ -13776,7 +13886,7 @@ object.entries@^1.1.1, object.entries@^1.1.5, object.entries@^1.1.6, object.entr define-properties "^1.2.1" es-object-atoms "^1.0.0" -object.fromentries@^2.0.6, object.fromentries@^2.0.7, object.fromentries@^2.0.8: +object.fromentries@^2.0.7, object.fromentries@^2.0.8: version "2.0.8" resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65" integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ== @@ -14126,6 +14236,11 @@ parse-passwd@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz" +parse-srcset@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/parse-srcset/-/parse-srcset-1.0.2.tgz#f2bd221f6cc970a938d88556abc589caaaa2bde1" + integrity sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q== + parse5@^3.0.1: version "3.0.3" resolved "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz" @@ -14252,6 +14367,10 @@ path-to-regexp@0.1.10: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz#67e9108c5c0551b9e5326064387de4763c4d5f8b" integrity sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w== +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz" + path-to-regexp@^2.2.1: version "2.4.0" resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.4.0.tgz" @@ -14295,7 +14414,7 @@ pbkdf2@^3.0.3: safe-buffer "^5.0.1" sha.js "^2.4.8" -pdfjs-dist@^4.6.82: +pdfjs-dist@4.6.82: version "4.6.82" resolved "https://registry.yarnpkg.com/pdfjs-dist/-/pdfjs-dist-4.6.82.tgz#4f171289f1810f324cd17c58bc59bd5bd0e0db7e" integrity sha512-BUOryeRFwvbLe0lOU6NhkJNuVQUp06WxlJVVCsxdmJ4y5cU3O3s3/0DunVdK1PMm7v2MUw52qKYaidhDH1Z9+w== @@ -14332,11 +14451,6 @@ picocolors@^1.0.0, picocolors@^1.0.1: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== -picocolors@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.0.tgz#5358b76a78cde483ba5cef6a9dc9671440b27d59" - integrity sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw== - picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.0, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" @@ -14687,10 +14801,10 @@ postcss-ordered-values@^7.0.0: cssnano-utils "^5.0.0" postcss-value-parser "^4.2.0" -postcss-prefix-selector@^1.16.1: - version "1.16.1" - resolved "https://registry.yarnpkg.com/postcss-prefix-selector/-/postcss-prefix-selector-1.16.1.tgz#87a77523838b79c0e8aec29f173234b2987cdc04" - integrity sha512-Umxu+FvKMwlY6TyDzGFoSUnzW+NOfMBLyC1tAkIjgX+Z/qGspJeRjVC903D7mx7TuBpJlwti2ibXtWuA7fKMeQ== +postcss-prefix-selector@^1.16.0: + version "1.16.0" + resolved "https://registry.yarnpkg.com/postcss-prefix-selector/-/postcss-prefix-selector-1.16.0.tgz#ad5b56f9a73a2c090ca7161049632c9d89bcb404" + integrity sha512-rdVMIi7Q4B0XbXqNUEI+Z4E+pueiu/CS5E6vRCQommzdQ/sgsS4dK42U7GX8oJR+TJOtT+Qv3GkNo6iijUMp3Q== postcss-reduce-initial@^7.0.0: version "7.0.0" @@ -14735,6 +14849,15 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.1.0, postcss-value-parser@^ resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== +postcss@8.4.45: + version "8.4.45" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.45.tgz#538d13d89a16ef71edbf75d895284ae06b79e603" + integrity sha512-7KTLTdzdZZYscUc65XmjFiB73vBhBfbPztCYdUNvlaso9PrzjzcmjqBPR0lNGkcVlcO4BjiO5rK/qNz+XAen1Q== + dependencies: + nanoid "^3.3.7" + picocolors "^1.0.1" + source-map-js "^1.2.0" + postcss@^8.3.11, postcss@^8.4.21, postcss@^8.4.23, postcss@^8.4.33, postcss@^8.4.38: version "8.4.41" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.41.tgz#d6104d3ba272d882fe18fc07d15dc2da62fa2681" @@ -14744,15 +14867,6 @@ postcss@^8.3.11, postcss@^8.4.21, postcss@^8.4.23, postcss@^8.4.33, postcss@^8.4 picocolors "^1.0.1" source-map-js "^1.2.0" -postcss@^8.4.45: - version "8.4.47" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.47.tgz#5bf6c9a010f3e724c503bf03ef7947dcb0fea365" - integrity sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ== - dependencies: - nanoid "^3.3.7" - picocolors "^1.1.0" - source-map-js "^1.2.1" - prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" @@ -15712,7 +15826,7 @@ regenerator-transform@^0.15.2: dependencies: "@babel/runtime" "^7.8.4" -regexp.prototype.flags@^1.5.2: +regexp.prototype.flags@^1.5.1, regexp.prototype.flags@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz#138f644a3350f981a858c44f6bb1a61ff59be334" integrity sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw== @@ -16055,6 +16169,18 @@ sanitize-filename@^1.6.1, sanitize-filename@^1.6.3: dependencies: truncate-utf8-bytes "^1.0.0" +sanitize-html@^2.13.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.13.0.tgz#71aedcdb777897985a4ea1877bf4f895a1170dae" + integrity sha512-Xff91Z+4Mz5QiNSLdLWwjgBDm5b1RU6xBT0+12rapjiaR7SwfRdjw8f+6Rir2MXKLrDicRFHdb51hGOAxmsUIA== + dependencies: + deepmerge "^4.2.2" + escape-string-regexp "^4.0.0" + htmlparser2 "^8.0.0" + is-plain-object "^5.0.0" + parse-srcset "^1.0.2" + postcss "^8.3.11" + sass-loader@16.0.1: version "16.0.1" resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-16.0.1.tgz#57049c1787076e923b21a1dccc612546ecaf4295" @@ -16062,7 +16188,7 @@ sass-loader@16.0.1: dependencies: neo-async "^2.6.2" -sass@^1.78.0: +sass@1.78.0: version "1.78.0" resolved "https://registry.yarnpkg.com/sass/-/sass-1.78.0.tgz#cef369b2f9dc21ea1d2cf22c979f52365da60841" integrity sha512-AaIqGSrjo5lA2Yg7RvFZrlXDBCp3nV4XP73GrLGvdRWWwk+8H3l0SDvq/5bA4eF+0RFPLuWUk3E+P1U/YqnpsQ== @@ -16128,6 +16254,25 @@ semver@^7.3.5, semver@^7.3.7, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0: resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== +send@0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" + integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "2.0.0" + mime "1.6.0" + ms "2.1.3" + on-finished "2.4.1" + range-parser "~1.2.1" + statuses "2.0.1" + send@0.19.0: version "0.19.0" resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8" @@ -16163,6 +16308,16 @@ serialize-javascript@^6.0.1, serialize-javascript@^6.0.2: dependencies: randombytes "^2.1.0" +serve-static@1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" + integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.18.0" + serve-static@1.16.2: version "1.16.2" resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.2.tgz#b6a5343da47f6bdd2673848bf45754941e803296" @@ -16441,11 +16596,6 @@ socket.io@^2.2.0: resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== -source-map-js@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" - integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== - source-map-support@0.5.13: version "0.5.13" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" @@ -16559,7 +16709,14 @@ stdin-discarder@^0.2.1: resolved "https://registry.yarnpkg.com/stdin-discarder/-/stdin-discarder-0.2.2.tgz#390037f44c4ae1a1ae535c5fe38dc3aba8d997be" integrity sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ== -stopword@^3.0.1: +stop-iteration-iterator@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz#6a60be0b4ee757d1ed5254858ec66b10c49285e4" + integrity sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ== + dependencies: + internal-slot "^1.0.4" + +stopword@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/stopword/-/stopword-3.1.1.tgz#ce3cf748cafd962904902dd050f6f16ca42d3ec0" integrity sha512-TzJdIuzqJNo6IaFvrF3fYqu08uJ/0VMsdABl6d6+dt6daD7QeHJnMt9sPqhVIxEmNaaeE8+eandVPJv9RhAL5Q== @@ -16622,16 +16779,7 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -16658,6 +16806,14 @@ string-width@^7.0.0: get-east-asian-width "^1.0.0" strip-ansi "^7.1.0" +string.prototype.includes@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/string.prototype.includes/-/string.prototype.includes-2.0.0.tgz#8986d57aee66d5460c144620a6d873778ad7289f" + integrity sha512-E34CkBgyeqNDcrbU76cDjL5JLcVrtSdYq0MEh/B10r17pRP4ciHLwTgnuLV8Ay6cgEMLkcBkFCKyFZ43YldYzg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + string.prototype.matchall@^4.0.11: version "4.0.11" resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz#1092a72c59268d2abaad76582dccc687c0297e0a" @@ -16726,14 +16882,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -16939,10 +17088,10 @@ tailwind-merge@2.3.0: dependencies: "@babel/runtime" "^7.24.1" -tailwindcss@^3.4.10: - version "3.4.11" - resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.11.tgz#4d6df41acc05a1d0291b1319490db8df375ab709" - integrity sha512-qhEuBcLemjSJk5ajccN9xJFtM/h0AVCPaA6C92jNP+M2J8kX+eMJHI7R2HFKUvvAsMpcfLILMCFYSeDwpMmlUg== +tailwindcss@3.4.10: + version "3.4.10" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.10.tgz#70442d9aeb78758d1f911af29af8255ecdb8ffef" + integrity sha512-KWZkVPm7yJRhdu4SRSl9d4AK2wM3a50UsvgHZO7xY77NQr2V+fIrEuoDGQcbvswWvFGbS2f6e+jC/6WJm1Dl0w== dependencies: "@alloc/quick-lru" "^5.2.0" arg "^5.0.2" @@ -17971,10 +18120,10 @@ webpack-cli@5.1.4: rechoir "^0.8.0" webpack-merge "^5.7.3" -webpack-dev-middleware@7.3.0: - version "7.3.0" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-7.3.0.tgz#5975ea41271083dc5678886b99d4c058382fb311" - integrity sha512-xD2qnNew+F6KwOGZR7kWdbIou/ud7cVqLEXeK1q0nHcNsX/u7ul/fSdlOTX4ntSL5FNFy7ZJJXbf0piF591JYw== +webpack-dev-middleware@7.4.2: + version "7.4.2" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-7.4.2.tgz#40e265a3d3d26795585cff8207630d3a8ff05877" + integrity sha512-xOO8n6eggxnwYpy1NlzUKpvrjfJTvae5/D6WOK0S2LSo7vjmo5gCM1DbLUmFqrMTJP+W/0YZNctm7jasWvLuBA== dependencies: colorette "^2.0.10" memfs "^4.6.0" @@ -18138,7 +18287,7 @@ which-collection@^1.0.1: is-weakmap "^2.0.1" is-weakset "^2.0.1" -which-typed-array@^1.1.14, which-typed-array@^1.1.15, which-typed-array@^1.1.2, which-typed-array@^1.1.9: +which-typed-array@^1.1.13, which-typed-array@^1.1.14, which-typed-array@^1.1.15, which-typed-array@^1.1.2, which-typed-array@^1.1.9: version "1.1.15" resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d" integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA== @@ -18192,7 +18341,7 @@ world-countries@5.0.0: resolved "https://registry.yarnpkg.com/world-countries/-/world-countries-5.0.0.tgz#6f75ebcce3d5224d84e9117eaf0d75a7726b6501" integrity sha512-wAfOT9Y5i/xnxNOdKJKXdOCw9Q3yQLahBUeuRol+s+o20F6h2a4tLEbJ1lBCYwEQ30Sf9Meqeipk1gib3YwF5w== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -18210,15 +18359,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"