From 2fa4e3757c514f532953f9ec8c316754c0247499 Mon Sep 17 00:00:00 2001 From: 178Pelado Date: Tue, 4 Feb 2025 09:01:04 -0300 Subject: [PATCH 01/58] =?UTF-8?q?primera=20versi=C3=B3n=20extractor=20y=20?= =?UTF-8?q?filtros?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dynamic-button-dropdown.component.html | 11 + .../dynamic-button-dropdown.component.scss | 37 + .../form/dynamic-button-dropdown.component.ts | 56 ++ .../submission/form/pdf-viewer.component.html | 90 ++ .../submission/form/pdf-viewer.component.scss | 111 +++ .../submission/form/pdf-viewer.component.ts | 806 ++++++++++++++++++ .../form/submission-form.component.html | 63 +- .../form/submission-form.component.scss | 19 + .../form/submission-form.component.ts | 48 +- .../submission-upload-files.component.ts | 5 + ...hemed-submission-upload-files.component.ts | 5 + zfilterTransformer/filterTransformer.html | 100 +++ zfilterTransformer/filterTransformer.js | 132 +++ 13 files changed, 1452 insertions(+), 31 deletions(-) create mode 100644 src/app/submission/form/dynamic-button-dropdown.component.html create mode 100644 src/app/submission/form/dynamic-button-dropdown.component.scss create mode 100644 src/app/submission/form/dynamic-button-dropdown.component.ts create mode 100644 src/app/submission/form/pdf-viewer.component.html create mode 100644 src/app/submission/form/pdf-viewer.component.scss create mode 100644 src/app/submission/form/pdf-viewer.component.ts create mode 100644 zfilterTransformer/filterTransformer.html create mode 100644 zfilterTransformer/filterTransformer.js diff --git a/src/app/submission/form/dynamic-button-dropdown.component.html b/src/app/submission/form/dynamic-button-dropdown.component.html new file mode 100644 index 00000000000..313038bb214 --- /dev/null +++ b/src/app/submission/form/dynamic-button-dropdown.component.html @@ -0,0 +1,11 @@ + diff --git a/src/app/submission/form/dynamic-button-dropdown.component.scss b/src/app/submission/form/dynamic-button-dropdown.component.scss new file mode 100644 index 00000000000..bc155bc4df8 --- /dev/null +++ b/src/app/submission/form/dynamic-button-dropdown.component.scss @@ -0,0 +1,37 @@ +.dropdown-container { + position: relative; + display: inline-block; + + .btn { + display: flex; + align-items: center; + justify-content: center; + } + + .dropdown-menu { + position: absolute; + top: 100%; + left: 0; + background: white; + border: 1px solid #ccc; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + display: none; + z-index: 1000; + padding: 8px; + + .dropdown-item { + padding: 8px; + cursor: pointer; + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + box-sizing: border-box; + + &:hover { + background-color: #f0f0f0; + } + } + } +} diff --git a/src/app/submission/form/dynamic-button-dropdown.component.ts b/src/app/submission/form/dynamic-button-dropdown.component.ts new file mode 100644 index 00000000000..a083964ddaa --- /dev/null +++ b/src/app/submission/form/dynamic-button-dropdown.component.ts @@ -0,0 +1,56 @@ +import { Component, ElementRef, Input, Renderer2, ViewChild, AfterViewInit, OnDestroy, Output, EventEmitter } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +@Component({ + selector: 'app-dynamic-button-dropdown', + templateUrl: './dynamic-button-dropdown.component.html', + styleUrls: ['./dynamic-button-dropdown.component.scss'], + standalone: true, + imports: [CommonModule] +}) +export class DynamicButtonDropdownComponent implements AfterViewInit, OnDestroy { + @Output() filterApplied = new EventEmitter(); + @ViewChild('dropdown') dropdown!: ElementRef; + + options = [ + { text: 'upperCase', filter: 'upperCase', icon: 'fa-solid fa-arrow-up-a-z' }, // Mayúsculas + { text: 'lowerCase', filter: 'lowerCase', icon: 'fa-solid fa-arrow-down-a-z' }, // Minúsculas + { text: 'Remove titles', filter: 'removeTitles', icon: 'fa-solid fa-bars' }, // Quitar títulos (Ej: Lic., Mg.) + { text: 'Remove double spaces', filter: 'removeDoubleSpaces', icon: 'fa-solid fa-bars' }, // Eliminar espacios dobles + { text: 'Remove references', filter: 'removeReferences', icon: 'fa-solid fa-bars' }, // Quitar referencias (Ej: números, asteriscos) + { text: 'Remove spaces between letters', filter: 'removeSpacesBetweenLetters', icon: 'fa-solid fa-bars' } // Corregir espacios entre letras + ]; + + constructor(private renderer: Renderer2) {} + + ngAfterViewInit() { + // Asegúrate de que el dropdown esté oculto inicialmente + this.renderer.setStyle(this.dropdown.nativeElement, 'display', 'none'); + + // Agregar un EventListener para cerrar el dropdown al hacer clic fuera de él + document.addEventListener('click', this.handleClickOutside.bind(this)); + } + + ngOnDestroy() { + // Eliminar el EventListener cuando el componente se destruya + document.removeEventListener('click', this.handleClickOutside.bind(this)); + } + + toggleDropdown(event: Event) { + event.preventDefault(); // Evita que el botón reciba foco y cambie estilos + event.stopPropagation(); // Evita que el evento se propague y cierre el dropdown inmediatamente + const display = this.dropdown.nativeElement.style.display; + this.dropdown.nativeElement.style.display = display === 'none' ? 'flex' : 'none'; + } + + handleClickOutside(event: Event) { + if (this.dropdown && !this.dropdown.nativeElement.contains(event.target as Node)) { + this.dropdown.nativeElement.style.display = 'none'; + } + } + + applyFilter(filter: string) { + this.filterApplied.emit(filter); + this.dropdown.nativeElement.style.display = 'none'; // Cerrar el dropdown al seleccionar una opción + } +} \ No newline at end of file diff --git a/src/app/submission/form/pdf-viewer.component.html b/src/app/submission/form/pdf-viewer.component.html new file mode 100644 index 00000000000..3787cd188b6 --- /dev/null +++ b/src/app/submission/form/pdf-viewer.component.html @@ -0,0 +1,90 @@ + + +
+ + + + + +
+ +
+ + +
+ + +
+ + +
+ +
+
+ +
+ +
+ +
+ + +
+ + + + + +
+
+
+ + +
+ + +
+
+
\ No newline at end of file diff --git a/src/app/submission/form/pdf-viewer.component.scss b/src/app/submission/form/pdf-viewer.component.scss new file mode 100644 index 00000000000..9c093b9b0a5 --- /dev/null +++ b/src/app/submission/form/pdf-viewer.component.scss @@ -0,0 +1,111 @@ +.selection-menu { + border: 1px solid #ddd; + border-radius: 5px; + padding: 10px; + background-color: #f9f9f9; + + .section { + margin-bottom: 10px; + + label { + display: block; + font-weight: bold; + margin-bottom: 5px; + } + + textarea { + width: 100%; + max-height: 10vh; // PRUEBA BAJA RESOLUCIÓN + resize: vertical; // Permite ajustar manualmente + padding: 10px; + box-sizing: border-box; + font-size: 14px; + } + } + + .row { + display: flex; + justify-content: space-between; // Opcional: distribuye el espacio + gap: 15px; // Espaciado entre los dos elementos + margin-left: 0; + margin-right: 0; + + .field { + flex: 1; // Ajusta cada campo para ocupar espacio igual + display: flex; + flex-direction: column; + + label { + margin-bottom: 5px; // Espaciado entre el label y el select + } + + select { + width: 100%; // Asegura que cada select ocupe todo su contenedor + height: 100%; + padding: 5px; + } + } + } + + .action-buttons { + display: flex; + justify-content: space-between; + gap: 10px; + + .btn { + flex: 1; + padding: 10px 15px; + font-size: 14px; + border: none; + border-radius: 5px; + cursor: pointer; + + &.btn-primary { + background-color: #007bff; + color: #fff; + } + + &.btn-secondary { + background-color: #6c757d; + color: #fff; + } + + .square { + width: 40px; + height: 40px; + display: flex; + justify-content: center; + align-items: center; + background-color: #f0f0f0; + border: 1px solid #ddd; + border-radius: 5px; + cursor: pointer; + font-size: 16px; + } + + .square:hover { + background-color: #e0e0e0; + } + } + + .dropdown { + .dropdown-menu { + display: none; /* Ocultar el menú cuando no está activo */ + } + + .dropdown-menu.show { + display: flex; /* Mostrar el menú como flexbox cuando está activo */ + } + } + } + + // Prueba inputs divididos + .inputs-grid { + display: grid; + grid-template-rows: repeat(5, auto); /* Define un máximo de 5 filas */ + grid-auto-flow: column; /* Llena las columnas verticalmente */ + gap: 5px; + font-size: .75rem; + } + // Fin prueba inputs divididos +} \ No newline at end of file diff --git a/src/app/submission/form/pdf-viewer.component.ts b/src/app/submission/form/pdf-viewer.component.ts new file mode 100644 index 00000000000..af38f2fd1fa --- /dev/null +++ b/src/app/submission/form/pdf-viewer.component.ts @@ -0,0 +1,806 @@ +import { Component, Input, ViewChild, ChangeDetectorRef } from '@angular/core'; +import { NgIf, NgFor, NgStyle } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { PdfJsViewerModule } from "ng2-pdfjs-viewer"; + +import { filterTransformer } from '../../../../zfilterTransformer/filterTransformer.js'; +import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; + +import { AfterViewInit } from '@angular/core'; + +import { DynamicButtonDropdownComponent } from './dynamic-button-dropdown.component'; +import { ViewContainerRef, ComponentFactoryResolver } from '@angular/core'; + +@Component({ + selector: 'app-pdf-viewer', + templateUrl: './pdf-viewer.component.html', + styleUrls: ['./pdf-viewer.component.scss'], + standalone: true, + imports: [ + NgIf, + NgFor, + NgStyle, + FormsModule, + PdfJsViewerModule, + NgbDropdownModule, + DynamicButtonDropdownComponent, + ], +}) +// export class PdfViewerComponent { +export class PdfViewerComponent implements AfterViewInit { + + @Input() pdfUrl: string; + @ViewChild('pdfViewerOnDemand') pdfViewerOnDemand; + iframe; + container; + pdfIsLoading = true; + + constructor(private changeDetectorRef: ChangeDetectorRef, private viewContainerRef: ViewContainerRef, private componentFactoryResolver: ComponentFactoryResolver) { } + + public pagesLoadedEvent(): void { + this.iframe = this.pdfViewerOnDemand.iframe.nativeElement; + this.container = this.iframe.contentDocument.body; // Contenedor del PDF + const pdfApp = this.iframe.contentWindow?.PDFViewerApplication; + pdfApp.appConfig.viewerContainer.onmouseup = this.onTextSelected.bind(this); + } + + selectedText: string = ''; + isTextSelected: boolean = false; + selectedTextarea: string = ''; + replaceText: boolean = false; + metadataOptions = []; + repeatableMetadata: string[] = [ + 'sedici_identifier_isbn', + 'sedici_identifier_issn', + 'sedici_identifier_other', + 'sedici_identifier_uri', + 'sedici_creator_person', + 'sedici_creator_corporate', + 'sedici_creator_interprete', + 'sedici_contributor_colaborator', + 'sedici_contributor_translator', + 'sedici_contributor_editor', + 'sedici_contributor_compiler', + 'sedici_contributor_director', + 'sedici_contributor_codirector', + 'sedici_contributor_juror', + 'sedici_contributor_inscriber', + 'dc.title.alternative', + 'dc_format', + 'dc_format_extent', + 'dc_subject', + 'sedici_subject_materias', + 'sedici_subject_ford', + // 'sedici_description_note', // VER de sacar, porque puede ser tan largo que se agregue todo en la misma nota + // 'dc_description_abstract', // VER de sacar, porque puede ser tan largo que se agregue todo en el mismo resumen + 'sedici_institucionDesarrollo', + 'mods_originInfo_place', + 'mods_location', + 'sedici_relation_isRelatedWith', + 'dcterms_audience', + 'dc_coverage_spatial', + 'dc_coverage_temporal', + 'dc_description_filiation' + ]; + + repeatableAndExtensibleMetadata: string[] = [ + 'sedici_description_note', + 'dc_description_abstract', + ]; + + // Este método se ejecuta al soltar el mouse + onTextSelected(event) { + const textareas = Array.from(document.querySelectorAll('textarea')); + const inputs = Array.from(document.querySelectorAll('input')); + const elements = [...textareas, ...inputs]; + + const excludedIds = new Set([ + // Se cargan a mano + 'selected-text', + 'dc_type', + 'sedici_subtype', + 'dc_language', + 'sedici_description_fulltext', + 'sedici_description_peerReview', + + // Se carga la fecha completa a partir del campo del año + 'dc_date_issued_month', + 'dc_date_issued_day', + 'dc_date_created_month', + 'dc_date_created_day', + 'sedici_date_exposure_month', + 'sedici_date_exposure_day', + ]); + const uniqueIdParts = new Set(); + + this.metadataOptions = elements + .filter(element => window.getComputedStyle(element).visibility === 'visible') // Filtrar elementos visibles + .filter(element => element.id) // Filtrar elementos con id + .filter(element => { + // Excluir elementos específicos y aquellos que coinciden con el patrón (Bitstreams subidos, FileUploader y otros) + return !excludedIds.has(element.id) && !element.id.match(/^primaryBitstream\d+$/) && !element.id.match(/^inputFileUploader-ds-drag-and-drop-uploader\d+$/) && !element.id.match(/^SL_locer\d+$/) && !element.id.match(/^SL_BBL_locer\d+$/); + }) + .filter(element => { + const match = element.id.match(/(dc|sedici|mods|thesis).*/); // Extraer la parte común del id + const idPart = match ? match[0] : element.id; + if (uniqueIdParts.has(idPart)) { + return false; // Si el idPart ya está en el Set, filtrar el elemento (para que no haya repetidos) + } else { + uniqueIdParts.add(idPart); // Agregar el idPart al Set + return true; // Mantener el elemento + } + }) + .map(element => { + let name; + switch (element.id) { + case 'dc_date_issued_year': + name = 'Fecha de publicación'; + break; + case 'dc_date_created_year': + name = 'Fecha de creación'; + break; + case 'sedici_date_exposure_year': + name = 'Fecha de presentación'; + break; + default: + name = element.placeholder || element.name || element.id; + } + return { name, value: element.id }; + }); + + const selection = event.view.getSelection(); + if (selection && selection.toString().length > 0) { + this.selectedText = selection.toString(); + this.isTextSelected = true; + const range = selection.getRangeAt(0); + const rect = range.getBoundingClientRect(); // Coordenadas del texto seleccionado + this.createButtons(rect); // Crear botones de acceso rápido + } else { + // Prueba estilo + this.selectedText = ''; + // Fin prueba estilo + + // Prueba split y filters + this.showDynamicInputs = false; + // Fin prueba split y filters + + this.isTextSelected = false; + this.removeButtons(this.container); // Borro los botones de acceso rápido + } + this.changeDetectorRef.detectChanges(); + } + + createButtons(rect: DOMRect): void { + this.removeButtons(this.container); // Eliminar botones existentes para evitar duplicados + + // Crear un contenedor para los botones + const buttonGroup = document.createElement('div'); + buttonGroup.classList.add('button-group'); + buttonGroup.style.position = 'absolute'; + + const centerX = rect.left + (rect.width / 2); // Calcular la posición centrada horizontalmente + + // Ajustar la posición del contenedor de botones + buttonGroup.style.left = `${centerX}px`; + buttonGroup.style.top = `${rect.bottom}px`; + buttonGroup.style.transform = 'translateX(-50%)'; // Centrar el contenedor en el eje X + buttonGroup.style.zIndex = '1000'; + buttonGroup.style.display = 'flex'; // Estilo para alinear los botones horizontalmente + buttonGroup.style.gap = '0px'; // Sin separación entre botones + + // Configuración de botones con sus acciones + const buttonsConfig = [ + { + label: 'T', + action: () => { + this.selectedTextarea = 'dc_title'; + const element = document.getElementById(this.selectedTextarea) as HTMLTextAreaElement | HTMLInputElement; + this.setMetadataValue(element, this.selectedText, false); + this.removeButtons(this.container); + }, + color: '#00ff00' // Verde + }, + { + label: 'A', + action: () => { + this.selectedTextarea = 'sedici_creator_person'; + const author = filterTransformer.transformPerson(this.selectedText); + this.processRepeatableMetadata([author]); + this.removeButtons(this.container); + }, + color: '#0000ff' // Azul + }, + { + label: 'As', + action: () => { + this.selectedTextarea = 'sedici_creator_person'; + const authors = filterTransformer.transformPersons(this.selectedText); + this.processRepeatableMetadata(authors); + this.removeButtons(this.container); + }, + color: '#0000ff' // Azul + }, + { + label: 'PC', + action: () => { + this.selectedTextarea = 'dc_subject'; + const keyword = filterTransformer.transformKeyword(this.selectedText); + this.processRepeatableMetadata([keyword]); + this.removeButtons(this.container); + }, + color: '#ff0000' // Rojo + }, + { + label: 'PCs', + action: () => { + this.selectedTextarea = 'dc_subject'; + const keywords = filterTransformer.transformKeywords(this.selectedText); + this.processRepeatableMetadata(keywords); + this.removeButtons(this.container); + }, + color: '#ff0000' // Rojo + } + ]; + + // Crear botones dinámicamente según la configuración + buttonsConfig.forEach(config => { + const button = document.createElement('button'); + button.classList.add('selection-button'); // Clase para identificar fácilmente + button.innerText = config.label; + button.style.backgroundColor = config.color; // Color definido en la configuración + button.style.color = '#fff'; + button.style.border = 'none'; + button.style.padding = '5px 10px'; + button.style.cursor = 'pointer'; + button.style.flexGrow = '1'; // Asegura que los botones se alineen perfectamente + + button.addEventListener('click', config.action); // Agregar evento clic al botón + + buttonGroup.appendChild(button); // Agregar el botón al contenedor + }); + + this.container.appendChild(buttonGroup); // Agregar el grupo de botones al contenedor del visor + } + + // Método para eliminar todos los botones + removeButtons(container: HTMLElement): void { + const buttonGroup = container.querySelector('.button-group'); + if (buttonGroup) { + container.removeChild(buttonGroup); + } + } + + copyToTextarea() { + if (this.selectedTextarea) { + const idPart = this.extractIdPart(); + let element = document.getElementById(this.selectedTextarea) as HTMLTextAreaElement | HTMLInputElement; + if (this.showDynamicInputs) { + this.retrieveInputs(); + } else if (idPart.includes('date')) { + const date = this.splitDate(this.selectedText); + const metadataYear = document.getElementById(this.selectedTextarea) as HTMLTextAreaElement | HTMLInputElement; + const metadataMonth = document.getElementById(this.selectedTextarea.replace(/_year$/, '_month')) as HTMLTextAreaElement | HTMLInputElement; + const metadataDay = document.getElementById(this.selectedTextarea.replace(/_year$/, '_day')) as HTMLTextAreaElement | HTMLInputElement; + if (date.year != '') { this.setMetadataValue(metadataYear, date.year, true); } + if (date.month != '') { this.setMetadataValue(metadataMonth, date.month, true); } + if (date.day != '') { this.setMetadataValue(metadataDay, date.day, true); } + } else if (idPart === 'sedici_relation_journalVolumeAndIssue'){ + const journalVolumeAndIssue = this.splitVolumeIssueYear(this.selectedText); + if (journalVolumeAndIssue.volume) { + if(journalVolumeAndIssue.issue) { + this.setMetadataValue(element, `vol. ${journalVolumeAndIssue.volume}, no. ${journalVolumeAndIssue.issue}`, true); + } + else { + this.setMetadataValue(element, `vol. ${journalVolumeAndIssue.volume}`, true); + } + } else if (journalVolumeAndIssue.year) { + if(journalVolumeAndIssue.issue) { + this.setMetadataValue(element, `año ${journalVolumeAndIssue.year}, no. ${journalVolumeAndIssue.issue}`, true); + } + else { + this.setMetadataValue(element, `año ${journalVolumeAndIssue.year}`, true); + } + } else if (journalVolumeAndIssue.issue) { + this.setMetadataValue(element, `no. ${journalVolumeAndIssue.issue}`, true); + } else { + alert('No se encontraron año, volumen o número'); + } + } else if (this.isRepeatableMetadataName(idPart) || (this.isRepeatableAndExtensibleMetadataName(idPart) && this.replaceText)) { + this.processRepeatableMetadata([this.selectedText]); + } else if (this.isRepeatableAndExtensibleMetadataName(idPart) && !this.replaceText) { + const elementsWithSameId = Array.from(document.querySelectorAll(`[id*="${idPart}"]`)) + .filter(element => window.getComputedStyle(element).visibility === 'visible') // Filtrar elementos visibles + element = elementsWithSameId[elementsWithSameId.length - 1] as HTMLTextAreaElement | HTMLInputElement; // Se agrega en el último campo + this.setMetadataValue(element, this.selectedText, false); + } else { + this.setMetadataValue(element, this.selectedText); + } + this.clearSelection(); + } else { + alert('Selecciona una caja de texto.'); + } + } + + // Método para extraer el id del metadato seleccionado + extractIdPart(): string { + const match = this.selectedTextarea.match(/(dc|sedici|mods|thesis).*/); // Extraer la parte común del id (distintos inicios de metadatos) + const idPart = match ? match[0] : this.selectedTextarea; + return idPart; + } + + // Método para verificar si un nombre de metadato está en la lista + isRepeatableMetadataName(name: string): boolean { + return this.repeatableMetadata.includes(name); + } + + // Método para verificar si un nombre de metadato está en la lista + isRepeatableAndExtensibleMetadataName(name: string): boolean { + return this.repeatableAndExtensibleMetadata.includes(name); + } + + // Método para agregar automáticamente un nuevo campo de metadato específico usando el id del contenedor + async addMetadataField(selectedTextarea: string = this.selectedTextarea) { + const selectedInput = document.getElementById(selectedTextarea); // Encuentra el input seleccionado usando su id + + if (selectedInput) { + let metadataContainer = selectedInput.closest('div[id$="_array"]'); // Recorre el DOM hacia arriba hasta encontrar el contenedor con el id deseado + + if (metadataContainer) { + const addButton = metadataContainer.querySelector('.ds-form-add-more.btn.btn-link'); // Encuentra el botón "Añadir más" dentro del contenedor del metadato + + if (addButton) { + (addButton as any).click(); // Simula un clic en el botón para agregar el nuevo campo + } else { + console.error(`Botón "Añadir más" no encontrado en el contenedor con id ${metadataContainer.id}.`); + } + } else { + console.error('Contenedor no encontrado.'); + } + } else { + console.error('Input seleccionado no encontrado.'); + } + + await new Promise(resolve => setTimeout(resolve, 50)); // Espera un tiempo para asegurarse de que el nuevo campo se haya creado + } + + // Método para procesar la lista de palabras clave + async processRepeatableMetadata(keywordArray: string[], idPart: string = this.extractIdPart(), selectedTextarea: string = this.selectedTextarea) { + for (const keyword of keywordArray) { + // Función para copiar el texto en el textarea o input seleccionado + const copyTextToElement = (element: HTMLTextAreaElement | HTMLInputElement, text: string) => { + this.setMetadataValue(element, text, false); + }; + + const elementsWithSameId = Array.from(document.querySelectorAll(`[id*="${idPart}"]`)) + .filter(element => window.getComputedStyle(element).visibility === 'visible') // Filtrar elementos visibles + + let index = 0; // Definir la variable index + let element = elementsWithSameId[index] as HTMLTextAreaElement | HTMLInputElement; + + // Si el elemento ya tiene un valor, busca el siguiente elemento vacío + while (element && element.value) { + element = elementsWithSameId[++index] as HTMLTextAreaElement | HTMLInputElement; + } + + // Si no hay más elementos, crea uno nuevo + if (!element) { + await this.addMetadataField(selectedTextarea); + const newElementsWithSameId = Array.from(document.querySelectorAll(`[id*="${idPart}"]`)) + .filter(element => window.getComputedStyle(element).visibility === 'visible') // Filtrar elementos visibles + element = newElementsWithSameId[newElementsWithSameId.length - 1] as HTMLTextAreaElement | HTMLInputElement; + copyTextToElement(element, keyword); + + // VER DE UNIFICAR + const parent = element.closest('.col'); + if (parent) { + const factory = this.componentFactoryResolver.resolveComponentFactory(DynamicButtonDropdownComponent); + const componentRef = this.viewContainerRef.createComponent(factory); + componentRef.instance.filterApplied.subscribe((filter: string) => { + this.applyFilterToSubmissionField = true; + const newValue = this.applyFilter(filter, element.value); + this.setMetadataValue(element, newValue, true); + }); + parent.insertAdjacentElement('afterend', componentRef.location.nativeElement); + } + // FIN VER DE UNIFICAR + + } else { + copyTextToElement(element, keyword); // Si el elemento está vacío, copia el texto en él + } + } + } + + // Marca el campo como modificado y después de unos segundos elimina el estilo + modifiedFieldStyle(element: HTMLTextAreaElement | HTMLInputElement) { + element.style.border = '2px solid green'; + setTimeout(() => { + element.style.border = ''; // Elimina el estilo después de unos segundos + }, 5000); // 5000 milisegundos = 5 segundos + } + + // Limpia la selección de texto y oculta el menú + clearSelection() { + this.selectedText = ''; + this.isTextSelected = false; + this.selectedTextarea = ''; + this.removeButtons(this.container); + } + + splitDate(date: string) { + let day = ''; + let month = ''; + let year = ''; + + // Meses en español para convertirlos a números + const monthNames: { [key: string]: string } = { + enero: '1', febrero: '2', marzo: '3', abril: '4', mayo: '5', junio: '6', julio: '7', agosto: '8', septiembre: '9', octubre: '10', noviembre: '11', diciembre: '12' + }; + + // Expresiones regulares para los distintos formatos + const formats: { + regex: RegExp; + handler: (m: RegExpMatchArray) => Partial<{ day: string; month: string; year: string }>; + }[] = [ + { regex: /^(\d{4})$/, handler: (m) => ({ year: m[1] }) }, // AAAA + { regex: /^(\d{1,2})\/(\d{4})$/, handler: (m) => ({ month: m[1], year: m[2] }) }, // MM/AAAA + { regex: /^(\d{1,2})\/(\d{1,2})\/(\d{4})$/, handler: (m) => ({ day: m[1], month: m[2], year: m[3] }) }, // DD/MM/AAAA + { regex: /^(\d{4})\/(\d{1,2})$/, handler: (m) => ({ year: m[1], month: m[2] }) }, // AAAA/MM + { regex: /^(\d{4})\/(\d{1,2})\/(\d{1,2})$/, handler: (m) => ({ year: m[1], month: m[2], day: m[3] }) }, // AAAA/MM/DD + { regex: /^([a-zA-Zñ]+)\s+de\s+(\d{4})$/, handler: (m) => ({ month: monthNames[m[1].toLowerCase()], year: m[2] }) }, // M de AAAA + { regex: /^([a-zA-Zñ]+)\s+(\d{4})$/, handler: (m) => ({ month: monthNames[m[1].toLowerCase()], year: m[2] }) }, // M AAAA + { regex: /^([a-zA-Zñ]+)-([a-zA-Zñ]+)\s+(\d{4})$/, handler: (m) => ({ month: monthNames[m[2].toLowerCase()], year: m[3] }) }, // M-M AAAA + { regex: /^(\d{1,2})\s+de\s+([a-zA-Zñ]+)\s+de\s+(\d{4})$/, handler: (m) => ({ day: m[1], month: monthNames[m[2].toLowerCase()], year: m[3] }) }, // D de M de AAAA + { regex: /^(\d{1,2})\s+([a-zA-Zñ]+)\s+de\s+(\d{4})$/, handler: (m) => ({ day: m[1], month: monthNames[m[2].toLowerCase()], year: m[3] }) }, // D M de AAAA + { regex: /^(\d{1,2})\s+de\s+([a-zA-Zñ]+)\s+(\d{4})$/, handler: (m) => ({ day: m[1], month: monthNames[m[2].toLowerCase()], year: m[3] }) }, // D de M AAAA + { regex: /^(\d{1,2})\s+al\s+(\d{1,2})\s+de\s+([a-zA-Zñ]+)\s+de\s+(\d{4})$/, handler: (m) => ({ month: monthNames[m[3].toLowerCase()], year: m[4] }) }, // D1 al D2 de M de AAAA + { regex: /^(\d{1,2})-(\d{1,2})\s+de\s+([a-zA-Zñ]+)\s+de\s+(\d{4})$/, handler: (m) => ({ month: monthNames[m[3].toLowerCase()], year: m[4] }) }, // D1-D2 de M de AAAA + ]; + + for (const format of formats) { + const match = date.match(format.regex); + if (match) { + const result = format.handler(match); + day = result.day || ''; + month = result.month || ''; + year = result.year || ''; + break; + } + } + + return { day, month, year }; + } + + splitVolumeIssueYear(data: string) { + const issuePatterns = [ + { + regex: /(?:\bN[º°.\s]*\s*(\d+)|\((?:N[º°.\s]*?)?\s*(\d+)\)|N\.(\d+)|(?:Número|número)\s*(\d+)|\((\d+)\))/i, + fields: ['issue'], + }, + ]; + + const yearPatterns = [ + { + regex: /(?:Año|año)?\s*(\d{4})/i, + fields: ['year'], + }, + ]; + + const volumePatterns = [ + { + regex: /(?:[Vv](?:ol(?:\.|umen)?)?\.?\s*(\d+)|\b\w+,\s*(\d+))/i, + fields: ['volume'], + }, + ]; + + const extraPatterns = [ + { + regex: /(\d+)\.(\d+)/, + fields: ['issue', 'volume'], + }, + { + regex: /(\d+)\s?\((\d+)\)/, + fields: ['volume', 'issue'], + }, + ]; + + let result: { year?: string; volume?: string; issue?: string } = {}; + + for (const pattern of issuePatterns) { + const match = data.match(pattern.regex); + if (match) { + pattern.fields.forEach((field) => { + const capturedValue = match.slice(1).find((value) => value !== undefined); + if (capturedValue && result[field] === undefined) { + result[field] = parseInt(capturedValue, 10).toString(); + } + }); + break; + } + } + + for (const pattern of yearPatterns) { + const match = data.match(pattern.regex); + if (match) { + pattern.fields.forEach((field) => { + const capturedValue = match.slice(1).find((value) => value !== undefined); + if (capturedValue && result[field] === undefined) { + result[field] = capturedValue; + } + }); + break; + } + } + + for (const pattern of volumePatterns) { + const match = data.match(pattern.regex); + if (match) { + pattern.fields.forEach((field) => { + const capturedValue = match.slice(1).find((value) => value !== undefined); + if (capturedValue && result[field] === undefined) { + result[field] = capturedValue; + } + }); + break; + } + } + + if (!result.volume || !result.issue) { + for (const pattern of extraPatterns) { + const match = data.match(pattern.regex); + if (match) { + pattern.fields.forEach((field, index) => { + const capturedValue = match.slice(index + 1).find((value) => value !== undefined); + if (capturedValue && result[field] === undefined) { + result[field] = capturedValue; + } + }); + break; + } + } + } + + return result; + } + + // Método para establecer el valor de un campo de metadato + setMetadataValue(element: HTMLTextAreaElement | HTMLInputElement, value: string, replaceText: boolean = this.replaceText) { + if (element && (value !== '')) { + element.focus(); + + this.modifiedFieldStyle(element); // Marca el campo como modificado + + if (!element.value || replaceText) { + element.value = value; + } else { + element.value = element.value + '\n' + value; + } + + // Crear y disparar un evento de entrada y de cambio + const inputEvent = new Event('input', { bubbles: true }); + element.dispatchEvent(inputEvent); + const changeEvent = new Event('change', { bubbles: true }); + element.dispatchEvent(changeEvent); + + this.changeDetectorRef.detectChanges(); // Forzar la detección de cambios en Angular + } else { + alert(`Elemento con ID ${element.id} no encontrado. O valor no válido.`); + } + } + + + // Prueba botones desplegables transformer + isDropdownOpen = false; + + toggleDropdown() { + this.isDropdownOpen = !this.isDropdownOpen; + } + // Fin prueba botones desplegables transformer + + + // Prueba split inputs y filters + showDynamicInputs: boolean = false; + + createInputs(parts: string[]) { + const container = document.getElementById('inputsContainer'); + container.innerHTML = ''; // Limpiar el contenedor antes de agregar nuevos inputs + + parts.forEach((part, index) => { + const inputGroup = document.createElement('div'); + inputGroup.classList.add('dynamic-input-group'); + + const input = document.createElement('input'); + input.type = 'text'; + input.value = part; + input.id = `input-${index}`; + input.classList.add('dynamic-input'); + + const removeButton = document.createElement('button'); + removeButton.innerText = 'x'; + removeButton.classList.add('dynamic-remove-button'); + removeButton.addEventListener('click', () => { + container.removeChild(inputGroup); + const inputs = document.querySelectorAll('.dynamic-input'); + if (inputs.length === 0) { + removeAllButton.remove(); + this.showDynamicInputs = false; // Ocultar inputs dinámicos + // No se borra el texto seleccionado para dejar que el usuario pueda modificar la lista y recuperar los inputs de otra manera + } + }); + + inputGroup.appendChild(input); + inputGroup.appendChild(removeButton); + container.appendChild(inputGroup); + }); + + const removeAllButton = document.createElement('button'); + removeAllButton.innerText = 'Eliminar todos'; + removeAllButton.classList.add('dynamic-remove-all-button'); + removeAllButton.addEventListener('click', () => { + const inputs = document.querySelectorAll('.dynamic-input-group'); + inputs.forEach((inputGroup) => { + container.removeChild(inputGroup); + }); + removeAllButton.remove(); + this.showDynamicInputs = false; + }); + + // container.appendChild(removeAllButton); + container.insertAdjacentElement('afterend', removeAllButton); + } + + // VER si llamarlo de algún lado, o eliminarlo si no se usa + clearDynamicInputs() { + this.showDynamicInputs = false; // Ocultar inputs dinámicos + const container = document.getElementById('inputsContainer'); + container.innerHTML = ''; // Limpiar el contenedor de inputs dinámicos + } + + applyFilter(filter: string, text: string = this.selectedText) { + if (this.showDynamicInputs) { + const inputs = document.querySelectorAll('.dynamic-input'); + inputs.forEach((input) => { + let e = input as HTMLTextAreaElement | HTMLInputElement; + e.value = this.applyFilterToText(e.value, filter); + if (e.value === '') { + e.parentElement.remove(); // Eliminar el input si está vacío (iría para todos los remove) + } + }); + } else if (this.applyFilterToSubmissionField) { + this.applyFilterToSubmissionField = false; + return this.applyFilterToText(text, filter); + } else { + this.selectedText = this.applyFilterToText(text, filter); + } + } + + // AGREGAR implementaciones faltantes + applyFilterToText(text: string, filter: string): string { + switch (filter) { + case 'camelCase': + return filterTransformer.toCamelCase(text); + case 'upperCase': + return filterTransformer.toUpperCase(text); + case 'lowerCase': + return filterTransformer.toLowerCase(text); + case 'splitByDelimiter': + const parts = filterTransformer.splitByDelimiter(text); + this.showDynamicInputs = true; // Mostrar inputs dinámicos + this.changeDetectorRef.detectChanges(); // Forzar la detección de cambios + this.createInputs(parts); + return text; // El selectedText no se modifica + case 'removeDoubleSpaces': + return filterTransformer.removeDoubleSpaces(text); + case 'removeSpacesBetweenLetters': + return filterTransformer.removeSpacesBetweenLetters(text); + case 'removeSpacesAtStartAndEnd': + return filterTransformer.removeSpacesAtStartAndEnd(text); + case 'removeLineBreaks': + return filterTransformer.removeLineBreaks(text); + case 'removeTitles': + return filterTransformer.removeTitles(text); + case 'removeReferences': + return filterTransformer.removeReferences(text); + default: + return text; + } + } + + async retrieveInputs() { + const inputs = document.querySelectorAll('.dynamic-input'); + const idPart = this.extractIdPart(); + const selectedTextarea = this.selectedTextarea; + for (const [index, input] of Array.from(inputs).entries()) { + let e = input as HTMLTextAreaElement | HTMLInputElement; + await this.processRepeatableMetadata([e.value], idPart, selectedTextarea); + } + this.showDynamicInputs = false; + this.selectedText = ''; + this.isTextSelected = false; + } + // Fin prueba split inputs y filters + + + + // Prueba botones filter inputs formulario submission + applyFilterToSubmissionField: boolean = false; + + ngAfterViewInit() { + this.addButtonsToInputs(); + } + + addButtonsToInputs() { + const textareas = Array.from(document.querySelectorAll('textarea')); + const inputss = Array.from(document.querySelectorAll('input')); + const elements = [...textareas, ...inputss]; + + const excludedIds = new Set([ + // Se cargan a mano + 'selected-text', + 'dc_type', + 'sedici_subtype', + 'dc_language', + 'sedici_description_fulltext', + 'sedici_description_peerReview', + + // No usan el transformer para editarse + 'dc_date_issued_year', + 'dc_date_issued_month', + 'dc_date_issued_day', + 'dc_date_created_year', + 'dc_date_created_month', + 'dc_date_created_day', + 'sedici_date_exposure_year', + 'sedici_date_exposure_month', + 'sedici_date_exposure_day', + ]); + const uniqueIdParts = new Set(); + + this.metadataOptions = elements + .filter(element => element.id) // Filtrar elementos con id + .filter(element => { + // Excluir elementos específicos y aquellos que coinciden con el patrón (Bitstreams subidos, FileUploader y otros) + return !excludedIds.has(element.id) && !element.id.match(/^primaryBitstream\d+$/) && !element.id.match(/^inputFileUploader-ds-drag-and-drop-uploader\d+$/) && !element.id.match(/^SL_locer\d+$/) && !element.id.match(/^SL_BBL_locer\d+$/); + }) + .filter(element => { + const match = element.id.match(/(dc|sedici|mods|thesis).*/); // Extraer la parte común del id + const idPart = match ? match[0] : element.id; + if (uniqueIdParts.has(idPart)) { + return false; // Si el idPart ya está en el Set, filtrar el elemento (para que no haya repetidos) + } else { + uniqueIdParts.add(idPart); // Agregar el idPart al Set + return true; // Mantener el elemento + } + }) + .map(element => { + const name = element.placeholder || element.name || element.id; + return { name, value: element.id }; + }) + + const escapeSelector = (id) => { + return id.replace(/([!"#$%&'()*+,.\/:;<=>?@[\\\]^`{|}~])/g, '\\$1'); + }; + + const inputs = document.querySelectorAll(this.metadataOptions.map(option => `[id="${escapeSelector(option.value)}"]`).join(', ')); + console.log('metadataOptions', this.metadataOptions); + console.log('inputs', inputs); + inputs.forEach(input => { + // VER DE UNIFICAR2 + if (!input.id) return; + + const parent = input.closest('.col'); + if (!parent) return; + + const element = input as HTMLTextAreaElement | HTMLInputElement; + const factory = this.componentFactoryResolver.resolveComponentFactory(DynamicButtonDropdownComponent); + const componentRef = this.viewContainerRef.createComponent(factory); + componentRef.instance.filterApplied.subscribe((filter: string) => { + this.applyFilterToSubmissionField = true; + const newValue = this.applyFilter(filter, element.value); + this.setMetadataValue(element, newValue, true); + }); + parent.insertAdjacentElement('afterend', componentRef.location.nativeElement); + // FIN VER DE UNIFICAR2 + }); + } + // Fin prueba botones filter inputs formulario submission +} \ No newline at end of file diff --git a/src/app/submission/form/submission-form.component.html b/src/app/submission/form/submission-form.component.html index ed648751cb0..26cad446139 100644 --- a/src/app/submission/form/submission-form.component.html +++ b/src/app/submission/form/submission-form.component.html @@ -1,16 +1,16 @@ -
- @if ((isLoading$ | async) !== true) { -
- @if ((uploadEnabled$ | async)) { -
- -
-
- } +
+
+
+
+ +
+
+
- @if (!isSectionHidden) { + - } +
-
+
+ [submissionId]="submissionId">
- } -
- @if (isLoading$ | async) { - - } - @for (object of $any(submissionSections | async); track object) { - - - } +
+ + + + + +
- @if ((isLoading$ | async) !== true) { - + diff --git a/src/app/submission/form/submission-form.component.scss b/src/app/submission/form/submission-form.component.scss index 772849791ca..91096e08146 100644 --- a/src/app/submission/form/submission-form.component.scss +++ b/src/app/submission/form/submission-form.component.scss @@ -37,3 +37,22 @@ text-decoration: $link-hover-decoration; } } + +.selection-menu-bottom { + background-color: #f9f9f9; + border: 1px solid #ccc; + padding: 10px; + margin: 20px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + z-index: 1000; + + select, button { + margin: 5px 0; + } +} + +canvas { + border: 1px solid black; + width: 100%; + height: auto; +} diff --git a/src/app/submission/form/submission-form.component.ts b/src/app/submission/form/submission-form.component.ts index 25699b23fef..0b8680321b2 100644 --- a/src/app/submission/form/submission-form.component.ts +++ b/src/app/submission/form/submission-form.component.ts @@ -1,4 +1,7 @@ -import { CommonModule } from '@angular/common'; +import { + CommonModule, + NgFor, +} from '@angular/common'; import { ChangeDetectorRef, Component, @@ -14,6 +17,7 @@ import { of, Subscription, } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; import { distinctUntilChanged, filter, @@ -50,6 +54,9 @@ import { ThemedSubmissionFormFooterComponent } from './footer/themed-submission- import { SubmissionFormSectionAddComponent } from './section-add/submission-form-section-add.component'; import { ThemedSubmissionUploadFilesComponent } from './submission-upload-files/themed-submission-upload-files.component'; +import { FormsModule } from '@angular/forms'; + +import { PdfViewerComponent } from './pdf-viewer.component'; /** * This component represents the submission form. */ @@ -66,6 +73,9 @@ import { ThemedSubmissionUploadFilesComponent } from './submission-upload-files/ ThemedSubmissionSectionContainerComponent, ThemedSubmissionUploadFilesComponent, TranslatePipe, + FormsModule, + NgFor, + PdfViewerComponent, ], }) export class SubmissionFormComponent implements OnChanges, OnDestroy { @@ -156,6 +166,8 @@ export class SubmissionFormComponent implements OnChanges, OnDestroy { */ protected subs: Subscription[] = []; + public pdfBlobUrl: string | null = null; + /** * Initialize instance variables * @@ -166,6 +178,7 @@ export class SubmissionFormComponent implements OnChanges, OnDestroy { * @param {SectionsService} sectionsService */ constructor( + private http: HttpClient, private authService: AuthService, private changeDetectorRef: ChangeDetectorRef, private halService: HALEndpointService, @@ -227,6 +240,7 @@ export class SubmissionFormComponent implements OnChanges, OnDestroy { // start auto save this.submissionService.startAutoSave(this.submissionId); + this.loadSectionData(); } } @@ -310,4 +324,36 @@ export class SubmissionFormComponent implements OnChanges, OnDestroy { sections.filter((section: SectionDataObject) => !isEqual(section.sectionType,SectionsType.Collection))), ); } + + AAA(responsePath: any) { + const files = responsePath.sections.upload.files; + const pdfFiles = files.filter(file => file.format.extensions.includes("pdf")); // Filtro los archivos PDF + const lastFileLoad = files[files.length - 1]; + + // Lo cargo sólo si es el primer archivo PDF + if (pdfFiles.length === 1 && lastFileLoad.format.extensions.includes("pdf")) { + const fileUrl = lastFileLoad.url; + this.http.get(fileUrl, { responseType: 'blob' }).subscribe((blob: Blob) => { + this.pdfBlobUrl = URL.createObjectURL(blob); + }); + } + } + + loadSectionData() { + this.submissionService.getSubmissionSections(this.submissionId).subscribe((sections: SectionDataObject[]) => { + const uploadSection = sections.find(section => section.sectionType === 'upload'); + if (uploadSection && uploadSection.data && (uploadSection.data as any).files && (uploadSection.data as any).files.length > 0) { + const files = (uploadSection.data as any).files; + const primaryUUID = (uploadSection.data as any).primary; + const pdfFiles = files.filter(file => file.format.extensions.includes("pdf")); // Filtro los archivos PDF + const pdfFile = pdfFiles.find(file => file.uuid === primaryUUID) || pdfFiles[0]; // Filtro el archivo PDF primario o el primero de la lista + if (pdfFile) { + const fileUrl = pdfFile.url; + this.http.get(fileUrl, { responseType: 'blob' }).subscribe((blob: Blob) => { + this.pdfBlobUrl = URL.createObjectURL(blob); + }); + } + } + }); + } } diff --git a/src/app/submission/form/submission-upload-files/submission-upload-files.component.ts b/src/app/submission/form/submission-upload-files/submission-upload-files.component.ts index e1d933810e2..e6ee2239e7d 100644 --- a/src/app/submission/form/submission-upload-files/submission-upload-files.component.ts +++ b/src/app/submission/form/submission-upload-files/submission-upload-files.component.ts @@ -4,6 +4,8 @@ import { Input, OnChanges, OnDestroy, + EventEmitter, + Output, } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { @@ -62,6 +64,8 @@ export class SubmissionUploadFilesComponent implements OnChanges, OnDestroy { */ @Input() uploadFilesOptions: UploaderOptions; + @Output() AAA = new EventEmitter(); + /** * A boolean representing if is possible to active drop zone over the document page * @type {boolean} @@ -170,6 +174,7 @@ export class SubmissionUploadFilesComponent implements OnChanges, OnDestroy { } }), ); + this.AAA.emit(workspaceitem); } /** diff --git a/src/app/submission/form/submission-upload-files/themed-submission-upload-files.component.ts b/src/app/submission/form/submission-upload-files/themed-submission-upload-files.component.ts index d26a402ba60..35e5f2a59a3 100644 --- a/src/app/submission/form/submission-upload-files/themed-submission-upload-files.component.ts +++ b/src/app/submission/form/submission-upload-files/themed-submission-upload-files.component.ts @@ -1,6 +1,8 @@ import { Component, Input, + Output, + EventEmitter, } from '@angular/core'; import { ThemedComponent } from '../../../shared/theme-support/themed.component'; @@ -22,10 +24,13 @@ export class ThemedSubmissionUploadFilesComponent extends ThemedComponent(); + protected inAndOutputNames: (keyof SubmissionUploadFilesComponent & keyof this)[] = [ 'collectionId', 'submissionId', 'uploadFilesOptions', + 'AAA', ]; protected getComponentName(): string { diff --git a/zfilterTransformer/filterTransformer.html b/zfilterTransformer/filterTransformer.html new file mode 100644 index 00000000000..e7480a6d9c7 --- /dev/null +++ b/zfilterTransformer/filterTransformer.html @@ -0,0 +1,100 @@ + + + + + + Text Normalizer + + + +

Text Normalizer

+
+ + +
+
+ + + + + + + + + + + + +
+
+

Result:

+

+
+ + diff --git a/zfilterTransformer/filterTransformer.js b/zfilterTransformer/filterTransformer.js new file mode 100644 index 00000000000..fcbecc496c9 --- /dev/null +++ b/zfilterTransformer/filterTransformer.js @@ -0,0 +1,132 @@ +// filterTransformer.js +export const filterTransformer = { + // Convierte el texto a camelCase + toCamelCase: (text) => { + return text + .toLowerCase() + .replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => + index === 0 ? word.toLowerCase() : word.toUpperCase() + ) + .replace(/\s+/g, ''); + }, + + // Convierte el texto a mayúsculas + toUpperCase: (text) => { + return text.toUpperCase(); + }, + + // Convierte el texto a minúsculas + toLowerCase: (text) => { + return text.toLowerCase(); + }, + + // Divide el texto por uno o varios delimitadores + splitByDelimiter: (text, delimiters = [';', '/', '-', '–', ',', '.'] ) => { // La coma y el punto van al final porque pueden ser parte del texto sin tener que dividirlo + // Manejar el caso específico de dividir por la coma ';' y la palabra ' y ' + // VER CASO 'Iván Moreno y Fabianesi' + if (text.includes(';') && text.includes(' y ')) { + const parts = text.split(/;| y /).map(item => item.trim()).filter(item => item !== ''); + return parts; + } + + // Manejar el caso específico de dividir por la coma ',' y la palabra ' y ' + if (text.includes(',') && text.includes(' y ')) { + const parts = text.split(/,| y /).map(item => item.trim()).filter(item => item !== ''); + return parts; + } + + // Manejar el caso específico de dividir por el guión '-' y el guión largo '–' + if (text.includes('-') && text.includes('–')) { + const parts = text.split(/-|–/).map(item => item.trim()).filter(item => item !== ''); + return parts; + } + + let parts = [text]; + for (const delimiter of delimiters) { + const regex = new RegExp(`\\${delimiter}`, 'g'); + const newParts = parts.flatMap(part => part.split(regex).map(item => item.trim()).filter(item => item !== '')); + if (newParts.length > 1) { + parts = newParts; + break; // Detener el bucle si se ha realizado una división exitosa + } + } + return parts; + }, + + + // INICIO gestión de espacios y saltos de línea + + // Elimina los espacios dobles + removeDoubleSpaces: (text) => { + return text.replace(/\s{2,}/g, ' '); + }, + + // Sacar espacios de donde no van (letras separadas por un espacio) + removeSpacesBetweenLetters: (text) => { + return text.replace(/\s+/g, ''); + }, + + // Elimina los espacios al principio y al final del texto + removeSpacesAtStartAndEnd: (text) => { + return text.trim(); + }, + + // Elimina los saltos de línea + removeLineBreaks: (text) => { + return text.replace(/(\r\n|\n|\r)/gm, ''); + }, + + // FIN gestión de espacios y saltos de línea + + + // INICIO representación de personas + + // Filtro para eliminar títulos o grados de las personas + removeTitles: (text) => { + return text.replace(/\b(Dr\.|Dra\.|Prof\.|Bec\.|Lic\.|Ing\.|Mg\.|Mag\.|Sc\.|Soc\.|Arq\.)\s*/g, ''); + }, + + // Filtro para eliminar referencias asociadas de las personas + removeReferences: (text) => { + // return text.replace(/(\d+|\*+|\([a-zA-Z0-9]+\)|(? { + text = filterTransformer.removeTitles(text); + text = filterTransformer.removeReferences(text); + // AGREGAR otros filtros. EJ: espaciado doble + return text; + }, + + transformPersons: (text) => { + let persons = filterTransformer.splitByDelimiter(text); + persons = persons.map(element => { + element = filterTransformer.transformPerson(element); + return element; + }).filter(element => element !== ''); // Filtrar elementos vacíos + return persons; + }, + // FIN acceso rápido personas + + + // INICIO acceso rápido palabras claves + transformKeyword: (text) => { + // AGREGAR otros filtros. EJ: espaciado doble + return text; + }, + + transformKeywords: (text) => { + let keywords = filterTransformer.splitByDelimiter(text); + keywords = keywords.map(element => { + element = filterTransformer.transformKeyword(element); + return element; + }).filter(element => element !== ''); // Filtrar elementos vacíos + return keywords; + }, + // FIN acceso rápido palabras claves +}; From 3a5a062cacc2d13f82077b289f00644840c07eb6 Mon Sep 17 00:00:00 2001 From: 178Pelado Date: Tue, 11 Feb 2025 12:46:44 -0300 Subject: [PATCH 02/58] =?UTF-8?q?creo=20y=20modifico=20filtros.=20separo?= =?UTF-8?q?=20componente=20botones=20acceso=20r=C3=A1pido.=20modificacione?= =?UTF-8?q?s=20al=20dropdown=20de=20filtros?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../form/dynamic-button-dropdown.component.ts | 4 +- .../submission/form/pdf-viewer.component.ts | 331 ++++++++++-------- .../form/shortcuts-buttons.component.html | 9 + .../form/shortcuts-buttons.component.scss | 17 + .../form/shortcuts-buttons.component.ts | 47 +++ .../form/section-form-operations.service.ts | 2 +- zfilterTransformer/filterTransformer.js | 42 ++- 7 files changed, 297 insertions(+), 155 deletions(-) create mode 100644 src/app/submission/form/shortcuts-buttons.component.html create mode 100644 src/app/submission/form/shortcuts-buttons.component.scss create mode 100644 src/app/submission/form/shortcuts-buttons.component.ts diff --git a/src/app/submission/form/dynamic-button-dropdown.component.ts b/src/app/submission/form/dynamic-button-dropdown.component.ts index a083964ddaa..be6c027ae33 100644 --- a/src/app/submission/form/dynamic-button-dropdown.component.ts +++ b/src/app/submission/form/dynamic-button-dropdown.component.ts @@ -1,4 +1,4 @@ -import { Component, ElementRef, Input, Renderer2, ViewChild, AfterViewInit, OnDestroy, Output, EventEmitter } from '@angular/core'; +import { Component, ElementRef, Renderer2, ViewChild, AfterViewInit, OnDestroy, Output, EventEmitter } from '@angular/core'; import { CommonModule } from '@angular/common'; @Component({ @@ -15,6 +15,8 @@ export class DynamicButtonDropdownComponent implements AfterViewInit, OnDestroy options = [ { text: 'upperCase', filter: 'upperCase', icon: 'fa-solid fa-arrow-up-a-z' }, // Mayúsculas { text: 'lowerCase', filter: 'lowerCase', icon: 'fa-solid fa-arrow-down-a-z' }, // Minúsculas + { text: 'Reorder person name', filter: 'reorderPerson', icon: 'fa-solid fa-right-left' }, // Reordenar nombre/apellido + { text: 'Capitalize', filter: 'capitalize', icon: 'fa-solid fa-bars' }, // Mayúscuya inicial { text: 'Remove titles', filter: 'removeTitles', icon: 'fa-solid fa-bars' }, // Quitar títulos (Ej: Lic., Mg.) { text: 'Remove double spaces', filter: 'removeDoubleSpaces', icon: 'fa-solid fa-bars' }, // Eliminar espacios dobles { text: 'Remove references', filter: 'removeReferences', icon: 'fa-solid fa-bars' }, // Quitar referencias (Ej: números, asteriscos) diff --git a/src/app/submission/form/pdf-viewer.component.ts b/src/app/submission/form/pdf-viewer.component.ts index af38f2fd1fa..2c89a387ef6 100644 --- a/src/app/submission/form/pdf-viewer.component.ts +++ b/src/app/submission/form/pdf-viewer.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, ViewChild, ChangeDetectorRef } from '@angular/core'; +import { Component, Input, ViewChild, ChangeDetectorRef, NgZone, ComponentRef } from '@angular/core'; import { NgIf, NgFor, NgStyle } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { PdfJsViewerModule } from "ng2-pdfjs-viewer"; @@ -8,8 +8,12 @@ import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; import { AfterViewInit } from '@angular/core'; -import { DynamicButtonDropdownComponent } from './dynamic-button-dropdown.component'; import { ViewContainerRef, ComponentFactoryResolver } from '@angular/core'; +import { DynamicButtonDropdownComponent } from './dynamic-button-dropdown.component'; +import { ShortcutsButtonsComponent } from './shortcuts-buttons.component'; + +import { SectionFormOperationsService } from '../sections/form/section-form-operations.service'; +import { JsonPatchOperationPathCombiner } from '../../core/json-patch/builder/json-patch-operation-path-combiner' @Component({ selector: 'app-pdf-viewer', @@ -24,6 +28,7 @@ import { ViewContainerRef, ComponentFactoryResolver } from '@angular/core'; PdfJsViewerModule, NgbDropdownModule, DynamicButtonDropdownComponent, + ShortcutsButtonsComponent, ], }) // export class PdfViewerComponent { @@ -34,8 +39,15 @@ export class PdfViewerComponent implements AfterViewInit { iframe; container; pdfIsLoading = true; + private buttonComponents: ComponentRef[] = []; - constructor(private changeDetectorRef: ChangeDetectorRef, private viewContainerRef: ViewContainerRef, private componentFactoryResolver: ComponentFactoryResolver) { } + constructor( + private changeDetectorRef: ChangeDetectorRef, + private viewContainerRef: ViewContainerRef, + private componentFactoryResolver: ComponentFactoryResolver, + private ngZone: NgZone, + private formOperationsService: SectionFormOperationsService + ) { } public pagesLoadedEvent(): void { this.iframe = this.pdfViewerOnDemand.iframe.nativeElement; @@ -65,7 +77,7 @@ export class PdfViewerComponent implements AfterViewInit { 'sedici_contributor_codirector', 'sedici_contributor_juror', 'sedici_contributor_inscriber', - 'dc.title.alternative', + 'dc_title_alternative', 'dc_format', 'dc_format_extent', 'dc_subject', @@ -165,115 +177,67 @@ export class PdfViewerComponent implements AfterViewInit { // Fin prueba split y filters this.isTextSelected = false; - this.removeButtons(this.container); // Borro los botones de acceso rápido + this.removeButtons(); // Borro los botones de acceso rápido } this.changeDetectorRef.detectChanges(); } - createButtons(rect: DOMRect): void { - this.removeButtons(this.container); // Eliminar botones existentes para evitar duplicados + createButtons(rect: DOMRect): void { + this.ngZone.run(() => { // Forzamos que Angular detecte cambios dentro de su zona + this.removeButtons(); // Eliminar botones existentes para evitar duplicados - // Crear un contenedor para los botones - const buttonGroup = document.createElement('div'); - buttonGroup.classList.add('button-group'); - buttonGroup.style.position = 'absolute'; - - const centerX = rect.left + (rect.width / 2); // Calcular la posición centrada horizontalmente - - // Ajustar la posición del contenedor de botones - buttonGroup.style.left = `${centerX}px`; - buttonGroup.style.top = `${rect.bottom}px`; - buttonGroup.style.transform = 'translateX(-50%)'; // Centrar el contenedor en el eje X - buttonGroup.style.zIndex = '1000'; - buttonGroup.style.display = 'flex'; // Estilo para alinear los botones horizontalmente - buttonGroup.style.gap = '0px'; // Sin separación entre botones - - // Configuración de botones con sus acciones - const buttonsConfig = [ - { - label: 'T', - action: () => { - this.selectedTextarea = 'dc_title'; - const element = document.getElementById(this.selectedTextarea) as HTMLTextAreaElement | HTMLInputElement; - this.setMetadataValue(element, this.selectedText, false); - this.removeButtons(this.container); - }, - color: '#00ff00' // Verde - }, - { - label: 'A', - action: () => { - this.selectedTextarea = 'sedici_creator_person'; - const author = filterTransformer.transformPerson(this.selectedText); - this.processRepeatableMetadata([author]); - this.removeButtons(this.container); - }, - color: '#0000ff' // Azul - }, - { - label: 'As', - action: () => { - this.selectedTextarea = 'sedici_creator_person'; - const authors = filterTransformer.transformPersons(this.selectedText); - this.processRepeatableMetadata(authors); - this.removeButtons(this.container); - }, - color: '#0000ff' // Azul - }, - { - label: 'PC', - action: () => { - this.selectedTextarea = 'dc_subject'; - const keyword = filterTransformer.transformKeyword(this.selectedText); - this.processRepeatableMetadata([keyword]); - this.removeButtons(this.container); - }, - color: '#ff0000' // Rojo - }, - { - label: 'PCs', - action: () => { - this.selectedTextarea = 'dc_subject'; - const keywords = filterTransformer.transformKeywords(this.selectedText); - this.processRepeatableMetadata(keywords); - this.removeButtons(this.container); - }, - color: '#ff0000' // Rojo - } - ]; - - // Crear botones dinámicamente según la configuración - buttonsConfig.forEach(config => { - const button = document.createElement('button'); - button.classList.add('selection-button'); // Clase para identificar fácilmente - button.innerText = config.label; - button.style.backgroundColor = config.color; // Color definido en la configuración - button.style.color = '#fff'; - button.style.border = 'none'; - button.style.padding = '5px 10px'; - button.style.cursor = 'pointer'; - button.style.flexGrow = '1'; // Asegura que los botones se alineen perfectamente - - button.addEventListener('click', config.action); // Agregar evento clic al botón + const factory = this.componentFactoryResolver.resolveComponentFactory(ShortcutsButtonsComponent); + const componentRef = this.viewContainerRef.createComponent(factory); + + componentRef.instance.rect = rect; + componentRef.instance.buttonClicked.subscribe((event: { idElement: string, multiple: boolean }) => { + this.handleButtonClicked(event.idElement, event.multiple); + }); - buttonGroup.appendChild(button); // Agregar el botón al contenedor + this.buttonComponents.push(componentRef); + this.changeDetectorRef.detectChanges(); // Ahora sí debería forzar el render correctamente }); - - this.container.appendChild(buttonGroup); // Agregar el grupo de botones al contenedor del visor } - - // Método para eliminar todos los botones - removeButtons(container: HTMLElement): void { - const buttonGroup = container.querySelector('.button-group'); - if (buttonGroup) { - container.removeChild(buttonGroup); + + handleButtonClicked(idElement: string, multiple: boolean) { + this.selectedTextarea = idElement; + const element = document.getElementById(this.selectedTextarea) as HTMLTextAreaElement | HTMLInputElement; + if (multiple) { + if (this.selectedTextarea === 'sedici_creator_person') { + const authors = filterTransformer.transformPersons(this.selectedText); + this.processRepeatableMetadata(authors); + } else { + const keywords = filterTransformer.transformKeywords(this.selectedText); + this.processRepeatableMetadata(keywords); + } + } else { + if (this.selectedTextarea === 'sedici_creator_person') { + const author = filterTransformer.transformPerson(this.selectedText); + this.processRepeatableMetadata([author]); + } else if (this.selectedTextarea === 'dc_subject') { + const keyword = filterTransformer.transformKeyword(this.selectedText); + this.processRepeatableMetadata([keyword]); + } else { + this.setMetadataValue(element, this.selectedText, true); + } } + this.removeButtons(); } + removeButtons(): void { + this.buttonComponents.forEach(componentRef => componentRef.destroy()); // Eliminamos solo los botones + this.buttonComponents = []; // Limpiamos el array de referencias + } + copyToTextarea() { if (this.selectedTextarea) { const idPart = this.extractIdPart(); - let element = document.getElementById(this.selectedTextarea) as HTMLTextAreaElement | HTMLInputElement; + let elements = document.querySelectorAll(`[id*="${this.selectedTextarea}"]`); + let element = Array.from(elements).find(el => + window.getComputedStyle(el).visibility === 'visible' && + el.getAttribute('id').startsWith('label') === false + ) as HTMLTextAreaElement | HTMLInputElement; + if (this.showDynamicInputs) { this.retrieveInputs(); } else if (idPart.includes('date')) { @@ -281,9 +245,15 @@ export class PdfViewerComponent implements AfterViewInit { const metadataYear = document.getElementById(this.selectedTextarea) as HTMLTextAreaElement | HTMLInputElement; const metadataMonth = document.getElementById(this.selectedTextarea.replace(/_year$/, '_month')) as HTMLTextAreaElement | HTMLInputElement; const metadataDay = document.getElementById(this.selectedTextarea.replace(/_year$/, '_day')) as HTMLTextAreaElement | HTMLInputElement; - if (date.year != '') { this.setMetadataValue(metadataYear, date.year, true); } - if (date.month != '') { this.setMetadataValue(metadataMonth, date.month, true); } - if (date.day != '') { this.setMetadataValue(metadataDay, date.day, true); } + if (date.year != '') { + this.setMetadataValue(metadataYear, date.year, true); + if (date.month != '') { + this.setMetadataValue(metadataMonth, date.month, true); + if (date.day != '') { + this.setMetadataValue(metadataDay, date.day, true); + } + } + } } else if (idPart === 'sedici_relation_journalVolumeAndIssue'){ const journalVolumeAndIssue = this.splitVolumeIssueYear(this.selectedText); if (journalVolumeAndIssue.volume) { @@ -372,8 +342,11 @@ export class PdfViewerComponent implements AfterViewInit { }; const elementsWithSameId = Array.from(document.querySelectorAll(`[id*="${idPart}"]`)) - .filter(element => window.getComputedStyle(element).visibility === 'visible') // Filtrar elementos visibles - + .filter(element => { + const id = element.getAttribute('id'); + return id === idPart || id.endsWith(idPart); + }) + .filter(element => window.getComputedStyle(element).visibility === 'visible') // Filtrar elementos visibles let index = 0; // Definir la variable index let element = elementsWithSameId[index] as HTMLTextAreaElement | HTMLInputElement; @@ -386,24 +359,13 @@ export class PdfViewerComponent implements AfterViewInit { if (!element) { await this.addMetadataField(selectedTextarea); const newElementsWithSameId = Array.from(document.querySelectorAll(`[id*="${idPart}"]`)) + .filter(element => { + const id = element.getAttribute('id'); + return id === idPart || id.endsWith(idPart); + }) .filter(element => window.getComputedStyle(element).visibility === 'visible') // Filtrar elementos visibles element = newElementsWithSameId[newElementsWithSameId.length - 1] as HTMLTextAreaElement | HTMLInputElement; copyTextToElement(element, keyword); - - // VER DE UNIFICAR - const parent = element.closest('.col'); - if (parent) { - const factory = this.componentFactoryResolver.resolveComponentFactory(DynamicButtonDropdownComponent); - const componentRef = this.viewContainerRef.createComponent(factory); - componentRef.instance.filterApplied.subscribe((filter: string) => { - this.applyFilterToSubmissionField = true; - const newValue = this.applyFilter(filter, element.value); - this.setMetadataValue(element, newValue, true); - }); - parent.insertAdjacentElement('afterend', componentRef.location.nativeElement); - } - // FIN VER DE UNIFICAR - } else { copyTextToElement(element, keyword); // Si el elemento está vacío, copia el texto en él } @@ -423,7 +385,7 @@ export class PdfViewerComponent implements AfterViewInit { this.selectedText = ''; this.isTextSelected = false; this.selectedTextarea = ''; - this.removeButtons(this.container); + this.removeButtons(); } splitDate(date: string) { @@ -433,9 +395,11 @@ export class PdfViewerComponent implements AfterViewInit { // Meses en español para convertirlos a números const monthNames: { [key: string]: string } = { - enero: '1', febrero: '2', marzo: '3', abril: '4', mayo: '5', junio: '6', julio: '7', agosto: '8', septiembre: '9', octubre: '10', noviembre: '11', diciembre: '12' + enero: '1', febrero: '2', marzo: '3', abril: '4', mayo: '5', junio: '6', julio: '7', agosto: '8', septiembre: '9', octubre: '10', noviembre: '11', diciembre: '12', + january: '1', february: '2', march: '3', april: '4', may: '5', june: '6', july: '7', august: '8', september: '9', october: '10', november: '11', december: '12' }; + // FALTAN FORMATOS FECHAS EN INGLÉS (ej: April 7, 2017) // Expresiones regulares para los distintos formatos const formats: { regex: RegExp; @@ -448,9 +412,11 @@ export class PdfViewerComponent implements AfterViewInit { { regex: /^(\d{4})\/(\d{1,2})\/(\d{1,2})$/, handler: (m) => ({ year: m[1], month: m[2], day: m[3] }) }, // AAAA/MM/DD { regex: /^([a-zA-Zñ]+)\s+de\s+(\d{4})$/, handler: (m) => ({ month: monthNames[m[1].toLowerCase()], year: m[2] }) }, // M de AAAA { regex: /^([a-zA-Zñ]+)\s+(\d{4})$/, handler: (m) => ({ month: monthNames[m[1].toLowerCase()], year: m[2] }) }, // M AAAA + { regex: /^(\d{4})\s+([a-zA-Zñ]+)$/, handler: (m) => ({ month: monthNames[m[2].toLowerCase()], year: m[1] }) }, // AAAA M { regex: /^([a-zA-Zñ]+)-([a-zA-Zñ]+)\s+(\d{4})$/, handler: (m) => ({ month: monthNames[m[2].toLowerCase()], year: m[3] }) }, // M-M AAAA { regex: /^(\d{1,2})\s+de\s+([a-zA-Zñ]+)\s+de\s+(\d{4})$/, handler: (m) => ({ day: m[1], month: monthNames[m[2].toLowerCase()], year: m[3] }) }, // D de M de AAAA { regex: /^(\d{1,2})\s+([a-zA-Zñ]+)\s+de\s+(\d{4})$/, handler: (m) => ({ day: m[1], month: monthNames[m[2].toLowerCase()], year: m[3] }) }, // D M de AAAA + { regex: /^(\d{1,2})\s+([a-zA-Zñ]+)\s+(\d{4})$/, handler: (m) => ({ day: m[1], month: monthNames[m[2].toLowerCase()], year: m[3] }) }, // D M AAAA { regex: /^(\d{1,2})\s+de\s+([a-zA-Zñ]+)\s+(\d{4})$/, handler: (m) => ({ day: m[1], month: monthNames[m[2].toLowerCase()], year: m[3] }) }, // D de M AAAA { regex: /^(\d{1,2})\s+al\s+(\d{1,2})\s+de\s+([a-zA-Zñ]+)\s+de\s+(\d{4})$/, handler: (m) => ({ month: monthNames[m[3].toLowerCase()], year: m[4] }) }, // D1 al D2 de M de AAAA { regex: /^(\d{1,2})-(\d{1,2})\s+de\s+([a-zA-Zñ]+)\s+de\s+(\d{4})$/, handler: (m) => ({ month: monthNames[m[3].toLowerCase()], year: m[4] }) }, // D1-D2 de M de AAAA @@ -682,6 +648,8 @@ export class PdfViewerComponent implements AfterViewInit { return filterTransformer.toUpperCase(text); case 'lowerCase': return filterTransformer.toLowerCase(text); + case 'capitalize': + return filterTransformer.toCapitalize(text); case 'splitByDelimiter': const parts = filterTransformer.splitByDelimiter(text); this.showDynamicInputs = true; // Mostrar inputs dinámicos @@ -700,6 +668,13 @@ export class PdfViewerComponent implements AfterViewInit { return filterTransformer.removeTitles(text); case 'removeReferences': return filterTransformer.removeReferences(text); + case 'reorderPerson': + const result = filterTransformer.reorderPerson(text); + if (!result) { + alert('El filtro solo se puede aplicar cuando se tiene UN nombre y UN apellido.'); + return text; + } + return result; default: return text; } @@ -726,9 +701,72 @@ export class PdfViewerComponent implements AfterViewInit { ngAfterViewInit() { this.addButtonsToInputs(); + + // Extiende el método original para interceptar los cambios + const originalDispatch = this.formOperationsService.dispatchOperationsFromChangeEvent; + this.formOperationsService.dispatchOperationsFromChangeEvent = + (pathCombiner: JsonPatchOperationPathCombiner, event: any, previousValue: any, hasStoredValue: boolean) => { + // Intercepta el cambio antes de procesarlo + if (event?.model?.id === 'dc_type' || event?.model?.id === 'sedici_subtype') { + setTimeout(() => { + this.addButtonsToInputs(); + }, 500); + } + // Llama al método original + return originalDispatch.call(this.formOperationsService, pathCombiner, event, previousValue, hasStoredValue); + }; + } + + private elementsAmount: number = 0; + ngAfterViewChecked() { + if (!this.tieneBotonesDinamicos()) { + this.addButtonsToInputs(); + } + + const textareas = Array.from(document.querySelectorAll('textarea')); + const inputss = Array.from(document.querySelectorAll('input')); + const elements = [...textareas, ...inputss]; + if (this.elementsAmount !== elements.length) { + this.elementsAmount = elements.length; + this.addButtonsToInputs(); + } } + private previousButtonCount = new Map(); + // BUSCAR OTRO MÉTODO DE CHEQUEO DE FALTA DE BOTONES (interceptar momento del guardado automático) + tieneBotonesDinamicos(): boolean { + const secciones = ['traditionalpageone', 'traditionalpagetwo', 'traditionalpageone2', 'traditionalpagetwo2']; + let cambiosDetectados = false; + let tieneBotones = true; + + for (const seccion of secciones) { + const sectionElement = document.querySelector(`[id="${seccion}-header"]`); + const parent = sectionElement?.closest('.card'); + const buttons = parent?.querySelectorAll('app-dynamic-button-dropdown'); + const currentCount = buttons?.length || 0; + + if (!this.previousButtonCount.has(seccion)) { + this.previousButtonCount.set(seccion, currentCount); + } + + if (currentCount !== this.previousButtonCount.get(seccion)) { + this.previousButtonCount.set(seccion, currentCount); + cambiosDetectados = true; + } + + if (cambiosDetectados && currentCount === 0) { + tieneBotones = false; + break; // Salir del bucle si se detecta un cambio y no hay botones + } + } + + return tieneBotones; + } + + addButtonsToInputs() { + this.removeFiltersButtons(); + const textareas = Array.from(document.querySelectorAll('textarea')); const inputss = Array.from(document.querySelectorAll('input')); const elements = [...textareas, ...inputss]; @@ -753,21 +791,20 @@ export class PdfViewerComponent implements AfterViewInit { 'sedici_date_exposure_month', 'sedici_date_exposure_day', ]); - const uniqueIdParts = new Set(); + const uniqueId = new Set(); this.metadataOptions = elements + .filter(element => window.getComputedStyle(element).visibility === 'visible') // Filtrar elementos visibles .filter(element => element.id) // Filtrar elementos con id .filter(element => { // Excluir elementos específicos y aquellos que coinciden con el patrón (Bitstreams subidos, FileUploader y otros) return !excludedIds.has(element.id) && !element.id.match(/^primaryBitstream\d+$/) && !element.id.match(/^inputFileUploader-ds-drag-and-drop-uploader\d+$/) && !element.id.match(/^SL_locer\d+$/) && !element.id.match(/^SL_BBL_locer\d+$/); }) .filter(element => { - const match = element.id.match(/(dc|sedici|mods|thesis).*/); // Extraer la parte común del id - const idPart = match ? match[0] : element.id; - if (uniqueIdParts.has(idPart)) { - return false; // Si el idPart ya está en el Set, filtrar el elemento (para que no haya repetidos) + if (uniqueId.has(element.id)) { + return false; // Si el id ya está en el Set, filtrar el elemento (para que no haya repetidos) } else { - uniqueIdParts.add(idPart); // Agregar el idPart al Set + uniqueId.add(element.id); // Agregar el id al Set return true; // Mantener el elemento } }) @@ -781,26 +818,46 @@ export class PdfViewerComponent implements AfterViewInit { }; const inputs = document.querySelectorAll(this.metadataOptions.map(option => `[id="${escapeSelector(option.value)}"]`).join(', ')); - console.log('metadataOptions', this.metadataOptions); - console.log('inputs', inputs); + inputs.forEach(input => { - // VER DE UNIFICAR2 - if (!input.id) return; + let container; - const parent = input.closest('.col'); + if (!input.id) return; + + const parent = input.closest('.col') || input.closest('ds-dynamic-form-control-container'); if (!parent) return; - const element = input as HTMLTextAreaElement | HTMLInputElement; + if (parent.classList.contains('col-sm-12')) { + container = document.createElement('div'); + container.classList.add('d-flex', 'align-items-center', 'w-100'); + container.style.gap = '8px'; + + input.classList.add('flex-grow-1'); + input.parentNode.insertBefore(container, input); + container.appendChild(input); + } + const factory = this.componentFactoryResolver.resolveComponentFactory(DynamicButtonDropdownComponent); const componentRef = this.viewContainerRef.createComponent(factory); + componentRef.instance.filterApplied.subscribe((filter: string) => { this.applyFilterToSubmissionField = true; - const newValue = this.applyFilter(filter, element.value); - this.setMetadataValue(element, newValue, true); + const newValue = this.applyFilter(filter, (input as HTMLInputElement).value); + this.setMetadataValue(input as HTMLInputElement, newValue, true); }); - parent.insertAdjacentElement('afterend', componentRef.location.nativeElement); - // FIN VER DE UNIFICAR2 + + if (parent.classList.contains('col-sm-12')) { + container.appendChild(componentRef.location.nativeElement); + } else { + parent.insertAdjacentElement('afterend', componentRef.location.nativeElement); + } }); } + + removeFiltersButtons(): void { + const buttons = document.querySelectorAll('app-dynamic-button-dropdown'); + buttons.forEach((button) => button.remove()); + } + // Fin prueba botones filter inputs formulario submission } \ No newline at end of file diff --git a/src/app/submission/form/shortcuts-buttons.component.html b/src/app/submission/form/shortcuts-buttons.component.html new file mode 100644 index 00000000000..6838da401e7 --- /dev/null +++ b/src/app/submission/form/shortcuts-buttons.component.html @@ -0,0 +1,9 @@ +
+ +
\ No newline at end of file diff --git a/src/app/submission/form/shortcuts-buttons.component.scss b/src/app/submission/form/shortcuts-buttons.component.scss new file mode 100644 index 00000000000..d4ecbf25fde --- /dev/null +++ b/src/app/submission/form/shortcuts-buttons.component.scss @@ -0,0 +1,17 @@ +.button-group { + position: absolute; + transform: translateX(-50%); // Centrar el contenedor en el eje X + z-index: 1000; + display: flex; // Estilo para alinear los botones horizontalmente + gap: 0px; // Sin separación entre botones +} + +.selection-button { + color: #fff; + border: none; + padding: 5px; + cursor: pointer; + width: 35px; // Ancho fijo para todos los botones + height: 30px; // Alto fijo para todos los botones + font-size: .75rem; +} \ No newline at end of file diff --git a/src/app/submission/form/shortcuts-buttons.component.ts b/src/app/submission/form/shortcuts-buttons.component.ts new file mode 100644 index 00000000000..9958fb9023d --- /dev/null +++ b/src/app/submission/form/shortcuts-buttons.component.ts @@ -0,0 +1,47 @@ +import { Component, Input, Output, EventEmitter } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +@Component({ + selector: 'app-shortcuts-buttons', + templateUrl: './shortcuts-buttons.component.html', + styleUrls: ['./shortcuts-buttons.component.scss'], + standalone: true, + imports: [CommonModule] +}) +export class ShortcutsButtonsComponent { + @Input() rect: DOMRect; + @Output() buttonClicked = new EventEmitter<{ idElement: string, multiple: boolean }>(); + + buttonsConfig = [ + { + text: 'Título', + label: 'T', + action: () => this.buttonClicked.emit({ idElement: 'dc_title', multiple: false }), + color: '#00ff00' // Verde + }, + { + text: 'Autor', + label: 'A', + action: () => this.buttonClicked.emit({ idElement: 'sedici_creator_person', multiple: false }), + color: '#0000ff' // Azul + }, + { + text: 'Autores', + label: 'As', + action: () => this.buttonClicked.emit({ idElement: 'sedici_creator_person', multiple: true }), + color: '#0000ff' // Azul + }, + { + text: 'Palabra clave', + label: 'PC', + action: () => this.buttonClicked.emit({ idElement: 'dc_subject', multiple: false }), + color: '#ff0000' // Rojo + }, + { + text: 'Palabras clave', + label: 'PCs', + action: () => this.buttonClicked.emit({ idElement: 'dc_subject', multiple: true }), + color: '#ff0000' // Rojo + } + ]; +} \ No newline at end of file diff --git a/src/app/submission/sections/form/section-form-operations.service.ts b/src/app/submission/sections/form/section-form-operations.service.ts index 6ef2fc51b84..565e38f12d1 100644 --- a/src/app/submission/sections/form/section-form-operations.service.ts +++ b/src/app/submission/sections/form/section-form-operations.service.ts @@ -368,7 +368,7 @@ export class SectionFormOperationsService { * @param hasStoredValue * representing if field value related to the specified operation has stored value */ - protected dispatchOperationsFromChangeEvent(pathCombiner: JsonPatchOperationPathCombiner, + public dispatchOperationsFromChangeEvent(pathCombiner: JsonPatchOperationPathCombiner, event: DynamicFormControlEvent, previousValue: FormFieldPreviousValueObject, hasStoredValue: boolean): void { diff --git a/zfilterTransformer/filterTransformer.js b/zfilterTransformer/filterTransformer.js index fcbecc496c9..4f709bae997 100644 --- a/zfilterTransformer/filterTransformer.js +++ b/zfilterTransformer/filterTransformer.js @@ -20,21 +20,13 @@ export const filterTransformer = { return text.toLowerCase(); }, - // Divide el texto por uno o varios delimitadores - splitByDelimiter: (text, delimiters = [';', '/', '-', '–', ',', '.'] ) => { // La coma y el punto van al final porque pueden ser parte del texto sin tener que dividirlo - // Manejar el caso específico de dividir por la coma ';' y la palabra ' y ' - // VER CASO 'Iván Moreno y Fabianesi' - if (text.includes(';') && text.includes(' y ')) { - const parts = text.split(/;| y /).map(item => item.trim()).filter(item => item !== ''); - return parts; - } - - // Manejar el caso específico de dividir por la coma ',' y la palabra ' y ' - if (text.includes(',') && text.includes(' y ')) { - const parts = text.split(/,| y /).map(item => item.trim()).filter(item => item !== ''); - return parts; - } + // Convierte la primera letra de cada oración a mayúsculas (COMPLEMENTAR CON NER PARA EL TEMA NOMBRES PROPIOS) + toCapitalize(text) { + return text.toLowerCase().replace(/(^\s*\w|[.!?¿¡]\s*\w)/g, match => match.toUpperCase()); + }, + // Divide el texto por uno o varios delimitadores + splitByDelimiter: (text, delimiters = [';', '/', ' - ', ' – ', ' * ', '•', ',', '.'] ) => { // La coma y el punto van al final porque pueden ser parte del texto sin tener que dividirlo // Manejar el caso específico de dividir por el guión '-' y el guión largo '–' if (text.includes('-') && text.includes('–')) { const parts = text.split(/-|–/).map(item => item.trim()).filter(item => item !== ''); @@ -43,13 +35,23 @@ export const filterTransformer = { let parts = [text]; for (const delimiter of delimiters) { - const regex = new RegExp(`\\${delimiter}`, 'g'); + const escapedDelimiter = delimiter.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); // Escapar caracteres especiales en el delimitador + const regex = new RegExp(escapedDelimiter, 'g'); const newParts = parts.flatMap(part => part.split(regex).map(item => item.trim()).filter(item => item !== '')); if (newParts.length > 1) { parts = newParts; break; // Detener el bucle si se ha realizado una división exitosa } } + + // VER CASO PALABRAS CLAVES 'mito; leer; historia; teseo y el minotauro' + // Verificar si la última parte contiene ' y ', ' & ' o ' and ' + const lastPart = parts[parts.length - 1]; + if (lastPart.includes(' y ') || lastPart.includes(' & ') || lastPart.includes(' and ')) { + const newParts = lastPart.split(/ y | & | and /).map(item => item.trim()).filter(item => item !== ''); + parts.pop(); // Eliminar la última parte + parts = parts.concat(newParts); + } return parts; }, @@ -88,10 +90,18 @@ export const filterTransformer = { // Filtro para eliminar referencias asociadas de las personas removeReferences: (text) => { - // return text.replace(/(\d+|\*+|\([a-zA-Z0-9]+\)|(? { + const words = text.trim().split(/\s+/); + if (words.length === 2) { + return `${words[1]}, ${words[0]}`; + } + return false; + }, + // FIN representación de personas From 566dbec67c464f2c0538f2a623f7f2ba9b93288d Mon Sep 17 00:00:00 2001 From: 178Pelado Date: Tue, 18 Feb 2025 12:57:45 -0300 Subject: [PATCH 03/58] =?UTF-8?q?cambio=20iconos=20filtros=20y=20agrego=20?= =?UTF-8?q?extracci=C3=B3n=20"Tomo"=20en=20n=C3=BAmeros=20romanos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../submission/form/pdf-viewer.component.html | 20 +++---- .../submission/form/pdf-viewer.component.scss | 34 ++++++----- .../submission/form/pdf-viewer.component.ts | 57 +++++++++++++++++-- 3 files changed, 79 insertions(+), 32 deletions(-) diff --git a/src/app/submission/form/pdf-viewer.component.html b/src/app/submission/form/pdf-viewer.component.html index 3787cd188b6..93b17ddeb01 100644 --- a/src/app/submission/form/pdf-viewer.component.html +++ b/src/app/submission/form/pdf-viewer.component.html @@ -58,19 +58,19 @@
- - - + + +
diff --git a/src/app/submission/form/pdf-viewer.component.scss b/src/app/submission/form/pdf-viewer.component.scss index 9c093b9b0a5..61a88f1d904 100644 --- a/src/app/submission/form/pdf-viewer.component.scss +++ b/src/app/submission/form/pdf-viewer.component.scss @@ -3,6 +3,7 @@ border-radius: 5px; padding: 10px; background-color: #f9f9f9; + margin-bottom: 5px; .section { margin-bottom: 10px; @@ -52,13 +53,23 @@ justify-content: space-between; gap: 10px; + .filter-btn { + padding: 10px 15px; + font-size: 14px; + border: none; + border-radius: 5px; + } + + .filter-btn:hover { + background-color: #e0e0e0; + } + .btn { flex: 1; padding: 10px 15px; font-size: 14px; border: none; border-radius: 5px; - cursor: pointer; &.btn-primary { background-color: #007bff; @@ -69,23 +80,10 @@ background-color: #6c757d; color: #fff; } - - .square { - width: 40px; - height: 40px; - display: flex; - justify-content: center; - align-items: center; - background-color: #f0f0f0; - border: 1px solid #ddd; - border-radius: 5px; - cursor: pointer; - font-size: 16px; - } - - .square:hover { - background-color: #e0e0e0; - } + } + + .filter-btn:hover { + background-color: #e0e0e0; } .dropdown { diff --git a/src/app/submission/form/pdf-viewer.component.ts b/src/app/submission/form/pdf-viewer.component.ts index 2c89a387ef6..6b1ab5c20f6 100644 --- a/src/app/submission/form/pdf-viewer.component.ts +++ b/src/app/submission/form/pdf-viewer.component.ts @@ -270,10 +270,17 @@ export class PdfViewerComponent implements AfterViewInit { else { this.setMetadataValue(element, `año ${journalVolumeAndIssue.year}`, true); } + } else if (journalVolumeAndIssue.tomo) { + if(journalVolumeAndIssue.issue) { + this.setMetadataValue(element, `tomo ${journalVolumeAndIssue.tomo}, no. ${journalVolumeAndIssue.issue}`, true); + } + else { + this.setMetadataValue(element, `tomo ${journalVolumeAndIssue.tomo}`, true); + } } else if (journalVolumeAndIssue.issue) { - this.setMetadataValue(element, `no. ${journalVolumeAndIssue.issue}`, true); + this.setMetadataValue(element, `no. ${journalVolumeAndIssue.issue}`, this.replaceText); } else { - alert('No se encontraron año, volumen o número'); + alert('No se encontraron año, volumen, tomo o número'); } } else if (this.isRepeatableMetadataName(idPart) || (this.isRepeatableAndExtensibleMetadataName(idPart) && this.replaceText)) { this.processRepeatableMetadata([this.selectedText]); @@ -437,6 +444,39 @@ export class PdfViewerComponent implements AfterViewInit { } splitVolumeIssueYear(data: string) { + function romanToInt(roman: string): number { + const romanNumeralMap: { [key: string]: number } = { + I: 1, + IV: 4, + V: 5, + IX: 9, + X: 10, + XL: 40, + L: 50, + XC: 90, + C: 100, + CD: 400, + D: 500, + CM: 900, + M: 1000, + }; + + let i = 0; + let num = 0; + + while (i < roman.length) { + if (i + 1 < roman.length && romanNumeralMap[roman.substring(i, i + 2)]) { + num += romanNumeralMap[roman.substring(i, i + 2)]; + i += 2; + } else { + num += romanNumeralMap[roman.charAt(i)]; + i += 1; + } + } + + return num; + } + const issuePatterns = [ { regex: /(?:\bN[º°.\s]*\s*(\d+)|\((?:N[º°.\s]*?)?\s*(\d+)\)|N\.(\d+)|(?:Número|número)\s*(\d+)|\((\d+)\))/i, @@ -456,6 +496,10 @@ export class PdfViewerComponent implements AfterViewInit { regex: /(?:[Vv](?:ol(?:\.|umen)?)?\.?\s*(\d+)|\b\w+,\s*(\d+))/i, fields: ['volume'], }, + { + regex: /\b[IVXLCDM]+\b/g, // Números romanos + fields: ['tomo'], + }, ]; const extraPatterns = [ @@ -469,7 +513,7 @@ export class PdfViewerComponent implements AfterViewInit { }, ]; - let result: { year?: string; volume?: string; issue?: string } = {}; + let result: { year?: string; volume?: string; tomo?: string; issue?: string } = {}; for (const pattern of issuePatterns) { const match = data.match(pattern.regex); @@ -501,7 +545,12 @@ export class PdfViewerComponent implements AfterViewInit { const match = data.match(pattern.regex); if (match) { pattern.fields.forEach((field) => { - const capturedValue = match.slice(1).find((value) => value !== undefined); + let capturedValue = ''; + if (field === 'tomo') { + capturedValue = romanToInt(match[0].toUpperCase()).toString(); + } else { + capturedValue = match.slice(1).find((value) => value !== undefined); + } if (capturedValue && result[field] === undefined) { result[field] = capturedValue; } From a387484846ce8791151abfcf27a3f6593704d9d9 Mon Sep 17 00:00:00 2001 From: Felipe Mosqueira Date: Thu, 1 May 2025 18:23:34 -0300 Subject: [PATCH 04/58] =?UTF-8?q?comento=20el=20evento=20que=20resetea=20e?= =?UTF-8?q?l=20submission=20al=20hacer=20click=20en=20otra=20secci=C3=B3n?= =?UTF-8?q?=20que=20no=20sea=20la=20actual?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/submission/sections/sections.directive.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/submission/sections/sections.directive.ts b/src/app/submission/sections/sections.directive.ts index 9d326997706..12a68920aac 100644 --- a/src/app/submission/sections/sections.directive.ts +++ b/src/app/submission/sections/sections.directive.ts @@ -148,9 +148,9 @@ export class SectionsDirective implements OnDestroy, OnInit { if (previousActive !== this.active) { this.changeDetectorRef.detectChanges(); // If section is no longer active dispatch save action - if (!this.active && isNotNull(activeSectionId)) { - this.submissionService.dispatchSave(this.submissionId); - } + // if (!this.active && isNotNull(activeSectionId)) { + // this.submissionService.dispatchSave(this.submissionId); + // } } }), ); From 9cf86c1b9e35668320b32f7995644bdce32ee94e Mon Sep 17 00:00:00 2001 From: Felipe Mosqueira Date: Thu, 1 May 2025 18:24:46 -0300 Subject: [PATCH 05/58] =?UTF-8?q?soluciono=20el=20error=20que=20hac=C3=ADa?= =?UTF-8?q?=20que=20algunos=20campos=20tarden=20m=C3=A1s=20que=20el=20rest?= =?UTF-8?q?o=20en=20cargar=20y=20los=20espacios=20extras=20en=20las=20fech?= =?UTF-8?q?as.,=20Elimino=20c=C3=B3digo=20obsoleto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../submission/form/pdf-viewer.component.ts | 101 ++++++------------ 1 file changed, 32 insertions(+), 69 deletions(-) diff --git a/src/app/submission/form/pdf-viewer.component.ts b/src/app/submission/form/pdf-viewer.component.ts index 6b1ab5c20f6..08c7a6fbb17 100644 --- a/src/app/submission/form/pdf-viewer.component.ts +++ b/src/app/submission/form/pdf-viewer.component.ts @@ -123,42 +123,39 @@ export class PdfViewerComponent implements AfterViewInit { 'sedici_date_exposure_month', 'sedici_date_exposure_day', ]); + const uniqueIdParts = new Set(); - this.metadataOptions = elements - .filter(element => window.getComputedStyle(element).visibility === 'visible') // Filtrar elementos visibles - .filter(element => element.id) // Filtrar elementos con id - .filter(element => { - // Excluir elementos específicos y aquellos que coinciden con el patrón (Bitstreams subidos, FileUploader y otros) - return !excludedIds.has(element.id) && !element.id.match(/^primaryBitstream\d+$/) && !element.id.match(/^inputFileUploader-ds-drag-and-drop-uploader\d+$/) && !element.id.match(/^SL_locer\d+$/) && !element.id.match(/^SL_BBL_locer\d+$/); - }) - .filter(element => { - const match = element.id.match(/(dc|sedici|mods|thesis).*/); // Extraer la parte común del id - const idPart = match ? match[0] : element.id; - if (uniqueIdParts.has(idPart)) { - return false; // Si el idPart ya está en el Set, filtrar el elemento (para que no haya repetidos) - } else { - uniqueIdParts.add(idPart); // Agregar el idPart al Set - return true; // Mantener el elemento - } - }) - .map(element => { - let name; - switch (element.id) { - case 'dc_date_issued_year': - name = 'Fecha de publicación'; - break; - case 'dc_date_created_year': - name = 'Fecha de creación'; - break; - case 'sedici_date_exposure_year': - name = 'Fecha de presentación'; - break; - default: - name = element.placeholder || element.name || element.id; - } - return { name, value: element.id }; - }); + const nameMap = { + 'dc_date_issued_year': 'Fecha de publicación', + 'dc_date_created_year': 'Fecha de creación', + 'sedici_date_exposure_year': 'Fecha de presentación', + }; + + const visibleElements = elements.filter(element => + window.getComputedStyle(element).visibility === 'visible' && element.id); + + setTimeout(() => { + this.metadataOptions = visibleElements + .filter(element => { + // Excluir elementos específicos y aquellos que coinciden con el patrón (Bitstreams subidos, FileUploader y otros) + return !excludedIds.has(element.id) && !element.id.match(/^primaryBitstream\d+$/) && !element.id.match(/^inputFileUploader-ds-drag-and-drop-uploader\d+$/) && !element.id.match(/^SL_locer\d+$/) && !element.id.match(/^SL_BBL_locer\d+$/); + }) + .filter(element => { + const match = element.id.match(/(dc|sedici|mods|thesis).*/); // Extraer la parte común del id + const idPart = match ? match[0] : element.id; + if (uniqueIdParts.has(idPart)) { + return false; // Si el idPart ya está en el Set, filtrar el elemento (para que no haya repetidos) + } else { + uniqueIdParts.add(idPart); // Agregar el idPart al Set + return true; // Mantener el elemento + } + }) + .map(element => ({ + name: nameMap[element.id] || element.placeholder || element.name || element.id, + value: element.id + })); + }, 100); const selection = event.view.getSelection(); if (selection && selection.toString().length > 0) { @@ -241,7 +238,7 @@ export class PdfViewerComponent implements AfterViewInit { if (this.showDynamicInputs) { this.retrieveInputs(); } else if (idPart.includes('date')) { - const date = this.splitDate(this.selectedText); + const date = this.splitDate(this.selectedText.trim()); const metadataYear = document.getElementById(this.selectedTextarea) as HTMLTextAreaElement | HTMLInputElement; const metadataMonth = document.getElementById(this.selectedTextarea.replace(/_year$/, '_month')) as HTMLTextAreaElement | HTMLInputElement; const metadataDay = document.getElementById(this.selectedTextarea.replace(/_year$/, '_day')) as HTMLTextAreaElement | HTMLInputElement; @@ -768,9 +765,6 @@ export class PdfViewerComponent implements AfterViewInit { private elementsAmount: number = 0; ngAfterViewChecked() { - if (!this.tieneBotonesDinamicos()) { - this.addButtonsToInputs(); - } const textareas = Array.from(document.querySelectorAll('textarea')); const inputss = Array.from(document.querySelectorAll('input')); @@ -781,37 +775,6 @@ export class PdfViewerComponent implements AfterViewInit { } } - private previousButtonCount = new Map(); - // BUSCAR OTRO MÉTODO DE CHEQUEO DE FALTA DE BOTONES (interceptar momento del guardado automático) - tieneBotonesDinamicos(): boolean { - const secciones = ['traditionalpageone', 'traditionalpagetwo', 'traditionalpageone2', 'traditionalpagetwo2']; - let cambiosDetectados = false; - let tieneBotones = true; - - for (const seccion of secciones) { - const sectionElement = document.querySelector(`[id="${seccion}-header"]`); - const parent = sectionElement?.closest('.card'); - const buttons = parent?.querySelectorAll('app-dynamic-button-dropdown'); - const currentCount = buttons?.length || 0; - - if (!this.previousButtonCount.has(seccion)) { - this.previousButtonCount.set(seccion, currentCount); - } - - if (currentCount !== this.previousButtonCount.get(seccion)) { - this.previousButtonCount.set(seccion, currentCount); - cambiosDetectados = true; - } - - if (cambiosDetectados && currentCount === 0) { - tieneBotones = false; - break; // Salir del bucle si se detecta un cambio y no hay botones - } - } - - return tieneBotones; - } - addButtonsToInputs() { this.removeFiltersButtons(); From 203dad7048ff29cf35b767e9164da2d8b7409158 Mon Sep 17 00:00:00 2001 From: Felipe Mosqueira Date: Thu, 1 May 2025 20:24:56 -0300 Subject: [PATCH 06/58] refactoring general --- .../submission/form/pdf-viewer.component.html | 4 +- .../submission/form/pdf-viewer.component.ts | 1232 +++++++---------- .../models/metadata-config.model.ts | 77 ++ src/app/submission/utils/content-splitters.ts | 178 +++ 4 files changed, 792 insertions(+), 699 deletions(-) create mode 100644 src/app/submission/models/metadata-config.model.ts create mode 100644 src/app/submission/utils/content-splitters.ts diff --git a/src/app/submission/form/pdf-viewer.component.html b/src/app/submission/form/pdf-viewer.component.html index 93b17ddeb01..bc6baf6c6ea 100644 --- a/src/app/submission/form/pdf-viewer.component.html +++ b/src/app/submission/form/pdf-viewer.component.html @@ -34,7 +34,7 @@
-
+
- - -
- - - - - -
- - -
-
+ }
\ No newline at end of file diff --git a/src/app/submission/form/pdf-viewer.component.ts b/src/app/submission/form/pdf-viewer.component.ts index 6c8d35aab5e..99fdfc43d11 100644 --- a/src/app/submission/form/pdf-viewer.component.ts +++ b/src/app/submission/form/pdf-viewer.component.ts @@ -1,5 +1,5 @@ import { Component, Input, ViewChild, ChangeDetectorRef, NgZone, ComponentRef, AfterViewInit, ViewContainerRef, ComponentFactoryResolver } from '@angular/core'; -import { NgIf, NgFor, NgClass } from '@angular/common'; +import { NgClass } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { PdfJsViewerModule } from "ng2-pdfjs-viewer"; import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; @@ -19,15 +19,13 @@ import { FilterInfo, FilterConfig } from '../models/filter-config.model'; styleUrls: ['./pdf-viewer.component.scss'], standalone: true, imports: [ - NgIf, - NgFor, NgClass, - FormsModule, - PdfJsViewerModule, + FormsModule, + PdfJsViewerModule, NgbDropdownModule, DynamicButtonDropdownComponent, - ShortcutsButtonsComponent, - ], + ShortcutsButtonsComponent +], }) // export class PdfViewerComponent { export class PdfViewerComponent implements AfterViewInit { diff --git a/src/app/submission/form/shortcuts-buttons.component.html b/src/app/submission/form/shortcuts-buttons.component.html index 6838da401e7..1a65d725b99 100644 --- a/src/app/submission/form/shortcuts-buttons.component.html +++ b/src/app/submission/form/shortcuts-buttons.component.html @@ -1,9 +1,11 @@
- + @for (config of buttonsConfig; track config) { + + }
\ No newline at end of file diff --git a/src/app/submission/form/submission-form.component.html b/src/app/submission/form/submission-form.component.html index cc78290e8ad..975ac10f121 100644 --- a/src/app/submission/form/submission-form.component.html +++ b/src/app/submission/form/submission-form.component.html @@ -1,51 +1,60 @@
-
-
-
- -
+
+ @if ((isLoading$ | async) !== true) { +
+ @if ((uploadEnabled$ | async)) { +
+ +
+
+ } +
+ @if (!isSectionHidden) { + + + } +
+
+ + +
- -
- - - - -
-
- - -
-
+ }
- - + @if ((isLoading$ | async)) { + + } + @for (object of $any(submissionSections | async); track object) { + [submissionId]="submissionId" + [sectionData]="$any(object)"> - + }
-
-
- + @if (pdfBlobUrl && isAuthorizedForPdfViewer) { +
+
+ +
-
+ }
- +@if ((isLoading$ | async) !== true) { + +} diff --git a/src/app/submission/form/submission-form.component.ts b/src/app/submission/form/submission-form.component.ts index 66c3d3101d7..0d32889333f 100644 --- a/src/app/submission/form/submission-form.component.ts +++ b/src/app/submission/form/submission-form.component.ts @@ -1,7 +1,4 @@ -import { - CommonModule, - NgFor, -} from '@angular/common'; +import { CommonModule } from '@angular/common'; import { ChangeDetectorRef, Component, @@ -76,9 +73,8 @@ import { PdfViewerComponent } from './pdf-viewer.component'; ThemedSubmissionUploadFilesComponent, TranslatePipe, FormsModule, - NgFor, - PdfViewerComponent, - ], + PdfViewerComponent +], }) export class SubmissionFormComponent implements OnChanges, OnDestroy, OnInit { diff --git a/src/themes/custom/app/shared/browse-by/browse-by.component.ts b/src/themes/custom/app/shared/browse-by/browse-by.component.ts index 9d5281b8781..f690a8a0108 100644 --- a/src/themes/custom/app/shared/browse-by/browse-by.component.ts +++ b/src/themes/custom/app/shared/browse-by/browse-by.component.ts @@ -1,7 +1,4 @@ -import { - AsyncPipe, - CommonModule, -} from '@angular/common'; +import { AsyncPipe, CommonModule } from '@angular/common'; import { Component, Injector, From 39c1df0d49b7f19f1c7c1677a8ab3579b94ab0be Mon Sep 17 00:00:00 2001 From: 178Pelado Date: Tue, 31 Mar 2026 12:11:46 -0300 Subject: [PATCH 39/58] =?UTF-8?q?vuelvo=20a=20permitir=20que=20los=20autor?= =?UTF-8?q?es=20aparezcan=20con=20su=20filiaci=C3=B3n=20al=20momento=20de?= =?UTF-8?q?=20buscarlos=20por=20autoridad?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../models/onebox/dynamic-onebox.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/onebox/dynamic-onebox.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/models/onebox/dynamic-onebox.component.html index d246e2bd67a..cdcb7b812c5 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/onebox/dynamic-onebox.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/onebox/dynamic-onebox.component.html @@ -18,7 +18,7 @@
    -
  • {{entry.value}}
  • +
  • {{entry.display}}
From aeb81a0badd247beb332a1be793e143daa4d338e Mon Sep 17 00:00:00 2001 From: 178Pelado Date: Tue, 31 Mar 2026 12:13:21 -0300 Subject: [PATCH 40/58] =?UTF-8?q?Acomodo=20los=20botones=20y=20sus=20estil?= =?UTF-8?q?os,=20para=20la=20nueva=20versi=C3=B3n=20de=20DSpace.=20Agrego?= =?UTF-8?q?=20la=20posiblidad=20de=20filtrar=20por=20texto=20al=20momento?= =?UTF-8?q?=20de=20seleccionar=20a=20qu=C3=A9=20campo=20del=20formulario?= =?UTF-8?q?=20se=20est=C3=A1=20referenciando?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...amic-form-control-container.component.html | 2 +- .../dynamic-button-dropdown.component.scss | 8 + .../submission/form/pdf-viewer.component.html | 28 ++-- .../submission/form/pdf-viewer.component.scss | 86 +++++++---- .../submission/form/pdf-viewer.component.ts | 142 ++++++++++++++---- 5 files changed, 198 insertions(+), 68 deletions(-) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html index c4c1d79c294..9b9aab01141 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html @@ -39,7 +39,7 @@
@if (model.languageCodes && model.languageCodes.length > 0) { -
+
- - @for (option of metadataOptions; track option) { - - } - +