Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
6a9fbc1
Fixed inconsistency of capital letters by making it lowercase
jr-rk Sep 26, 2025
18b473e
Implementation of combobox functionality - dropdowns for search options
jr-rk Sep 26, 2025
c7cb8b4
Refactoring the new search functionality
jr-rk Sep 26, 2025
bcb7111
Added i18n translation keys
jr-rk Sep 29, 2025
83d1ca0
VSB-TUO/Creative Common license view for the Item Page (#966)
milanmajchrak Sep 16, 2025
7943741
VSB-TUO/Fixed still loading of the `Saving` bar for the CC licenses (…
milanmajchrak Sep 25, 2025
54cad6f
Changed multiple styles from css to bootstrap classes in html
jr-rk Oct 29, 2025
1d55c6b
Using existing constants for recognizing resourceTypes
jr-rk Oct 29, 2025
51334db
Add constant for invalid resource-type
jr-rk Oct 29, 2025
5b97683
Changed button to select elements
jr-rk Oct 29, 2025
828e5a7
Fixed deserializer to return null by default
jr-rk Oct 30, 2025
d53e995
If searchQuery becomes empty, the dropdown will visually reset to sho…
jr-rk Oct 30, 2025
6dd7a61
Removed unused methods to avoid dead code
jr-rk Oct 30, 2025
4207c1e
Normalized query toLowerCase to maintain robust handling
jr-rk Nov 5, 2025
c57a5ef
Merge branch 'dtq-dev' into ufal/manage-handles-table-finetunes
jr-rk Nov 5, 2025
3f25108
Adding the cs translations again
jr-rk Nov 5, 2025
ba660dd
Merge branch 'dtq-dev' into ufal/cherrypick-cc-license
jr-rk Nov 5, 2025
4a18d16
Using chevron-down icon instead of image of chevron includes by url i…
jr-rk Nov 6, 2025
ba187ca
sync-i18n
jr-rk Nov 6, 2025
f729ea9
Removed usage of CC license field
jr-rk Nov 6, 2025
a54793e
sync-i18n
jr-rk Nov 6, 2025
f1bf3c6
fix sync-i18n
jr-rk Nov 6, 2025
17991d2
Merge branch 'ufal/manage-handles-table-finetunes' of github.com:data…
jr-rk Nov 6, 2025
7508107
fix sync-i18n
jr-rk Nov 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/app/core/handle/HandleResourceTypeIdserializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ export const HandleResourceTypeIdSerializer = {
return 3;
case COMMUNITY:
return 4;
case SITE:
return 5;
default:
return null;
}
Expand All @@ -27,8 +29,10 @@ export const HandleResourceTypeIdSerializer = {
return COLLECTION;
case 4:
return COMMUNITY;
default:
case 5:
return SITE;
default:
return null;
}
}
};
1 change: 1 addition & 0 deletions src/app/core/handle/handle.resource-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ResourceType } from '../shared/resource-type';

export const HANDLE = new ResourceType('handle');
export const SUCCESSFUL_RESPONSE_START_CHAR = '2';
export const INVALID_RESOURCE_TYPE_ID = -1;
export const COMMUNITY = 'Community';
export const COLLECTION = 'Collection';
export const ITEM = 'Item';
Expand Down
54 changes: 49 additions & 5 deletions src/app/handle-page/handle-table/handle-table.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ <h5 class="card-header">{{ 'handle-table.title' | translate }}</h5>
class="btn btn-outline-secondary dropdown-toggle"
data-bs-toggle="dropdown" aria-haspopup="true"
aria-expanded="false">
<span *ngIf="searchOption != null" [ngModel]="searchOption" name="searchOption" ngDefaultControl>{{searchOption}}</span>
<span *ngIf="searchOption != null">{{searchOption}}</span>
<span *ngIf="searchOption == null" >{{'handle-table.dropdown.search-option' | translate}}</span>
</button>
<div ngbDropdownMenu aria-labelledby="resultdropdown">
Expand All @@ -20,9 +20,53 @@ <h5 class="card-header">{{ 'handle-table.title' | translate }}</h5>
<button class="dropdown-item" (click)="setSearchOption($event)">{{"handle-table.table.resource-type" | translate}}</button>
</div>
</div>
<input type="text" id="clarin-dc-search-box" class="form-control" aria-label="Text input with dropdown button"
[(ngModel)]="searchQuery"
[disabled]="!searchOption">

<!-- Search input area -->
<div class="search-input-container flex-fill">
<!-- Default disabled search input when no option is selected -->
<input *ngIf="!searchOption"
type="text"
class="form-control search-input"
[placeholder]="'handle-table.search.placeholder.no-option' | translate"
disabled>

<!-- Text input for Handle search -->
<input *ngIf="searchOption === handleOption"
type="text"
class="form-control search-input"
[(ngModel)]="searchQuery"
(keyup.enter)="searchHandles()"
[placeholder]="'handle-table.search.placeholder.handle' | translate">

<!-- Dropdown for Internal search -->
<div *ngIf="searchOption === internalOption" class="select-wrapper">
<select class="form-select w-100"
[(ngModel)]="searchQuery"
(change)="searchHandles()"
[attr.aria-label]="'handle-table.search.aria.select-internal' | translate">
<option value="" disabled [selected]="!searchQuery">{{'handle-table.search.internal.select' | translate}}</option>
<option value="yes">{{'handle-table.search.internal.yes' | translate}}</option>
<option value="no">{{'handle-table.search.internal.no' | translate}}</option>
</select>
<i class="fas fa-chevron-down select-icon"></i>
</div>

<!-- Dropdown for Resource Type search -->
<div *ngIf="searchOption === resourceTypeOption" class="select-wrapper">
<select class="form-select w-100"
[(ngModel)]="searchQuery"
(change)="searchHandles()"
[attr.aria-label]="'handle-table.search.aria.select-resource-type' | translate">
<option value="" disabled [selected]="!searchQuery">{{'handle-table.search.resource-type.select' | translate}}</option>
<option value="Site">{{'handle-table.search.resource-type.site' | translate}}</option>
<option value="Community">{{'handle-table.search.resource-type.community' | translate}}</option>
<option value="Collection">{{'handle-table.search.resource-type.collection' | translate}}</option>
<option value="Item">{{'handle-table.search.resource-type.item' | translate}}</option>
</select>
<i class="fas fa-chevron-down select-icon"></i>
</div>
</div>

<span class="input-group-append" (click)="searchHandles()">
<button type="submit" class="btn btn-primary search-button">
<i class="fas fa-search"></i>{{'handle-table.dropdown.search-button' | translate}}</button>
Expand Down Expand Up @@ -70,7 +114,7 @@ <h5 class="card-header">{{ 'handle-table.title' | translate }}</h5>
</a>
</td>
<td class="version-row-element-editor">
{{handle?.resourceTypeID}}
{{getTranslatedResourceType(handle?.resourceTypeID)}}
</td>
<td class="version-row-element-editor">
<span *ngIf="handle?.resourceId != null">
Expand Down
47 changes: 46 additions & 1 deletion src/app/handle-page/handle-table/handle-table.component.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,48 @@
/**
The file for styling `handle-table.component.html`. No styling needed.
* Styling for handle-table component search functionality
*/

// Make select elements look like buttons to match previous dropdown styling
.search-input-container {
.select-wrapper {
position: relative;
display: inline-block;
width: 100%;
}

.form-select {
background: white;
border: 1px solid #ced4da;
border-radius: 0.25rem;
padding: 0.375rem 2.5rem 0.375rem 0.75rem;
color: #495057;
appearance: none;
cursor: pointer;

&:focus {
border-color: #80bdff;
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
outline: 0;
}

&:disabled {
background-color: #f8f9fa;
color: #6c757d;
cursor: not-allowed;
}
}

.select-icon {
position: absolute;
right: 0.75rem;
top: 50%;
transform: translateY(-50%);
pointer-events: none;
color: #495057;
font-size: 0.875rem;
}

.form-control {
border-radius: 0;
}
}
70 changes: 45 additions & 25 deletions src/app/handle-page/handle-table/handle-table.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@ import { Handle } from '../../core/handle/handle.model';
import {
COLLECTION,
COMMUNITY,
INVALID_RESOURCE_TYPE_ID,
ITEM,
SITE,
SUCCESSFUL_RESPONSE_START_CHAR
} from '../../core/handle/handle.resource-type';
import { getCommunityPageRoute } from '../../community-page/community-page-routing-paths';
import { getCollectionPageRoute } from '../../collection-page/collection-page-routing-paths';
import { getEntityPageRoute } from '../../item-page/item-page-routing-paths';
import { HandleResourceTypeIdSerializer } from '../../core/handle/HandleResourceTypeIdserializer';

/**
* Constants for converting the searchQuery for the server
Expand Down Expand Up @@ -359,6 +361,47 @@ export class HandleTableComponent implements OnInit {
*/
setSearchOption(event) {
this.searchOption = event?.target?.innerHTML;
// Reset search query when changing search option
this.searchQuery = '';
}

/**
* Get translated resource type name for table display
* Converts constants like 'Community', 'Collection', 'Item', 'Site' to translated strings
*/
getTranslatedResourceType(resourceTypeID: string): string {
if (!resourceTypeID) {
return '';
}

// Map the constant values to lowercase for translation keys
const resourceTypeKey = resourceTypeID.toLowerCase();
const translationKey = `handle-table.search.resource-type.${resourceTypeKey}`;

// Return translated value, fallback to original if translation not found
const translated = this.translateService.instant(translationKey);
return translated !== translationKey ? translated : resourceTypeID;
}

/**
* Parse internal search query to server format
*/
private parseInternalSearchQuery(searchQuery: string): string {
const normalizedQuery = searchQuery.toLowerCase();
if (normalizedQuery === 'yes') {
return 'internal';
} else if (normalizedQuery === 'no') {
return 'external';
}
return searchQuery;
}

/**
* Parse resource type search query to server format (converts to numeric ID)
*/
private parseResourceTypeSearchQuery(searchQuery: string): string {
const id = HandleResourceTypeIdSerializer.Serialize(searchQuery);
return id ? id.toString() : INVALID_RESOURCE_TYPE_ID.toString();
}

/**
Expand All @@ -381,35 +424,12 @@ export class HandleTableComponent implements OnInit {
parsedSearchOption = HANDLE_SEARCH_OPTION;
break;
case this.internalOption:
// if the handle doesn't have the URL - is internal, if it does - is external
parsedSearchOption = URL_SEARCH_OPTION;
if (this.searchQuery.toLowerCase() === 'yes') {
parsedSearchQuery = 'internal';
} else if (this.searchQuery.toLowerCase() === 'no') {
parsedSearchQuery = 'external';
}
parsedSearchQuery = this.parseInternalSearchQuery(this.searchQuery);
break;
case this.resourceTypeOption:
parsedSearchOption = RESOURCE_TYPE_SEARCH_OPTION;
// parse resourceType from string to the number because the resourceType is integer on the server
switch (this.searchQuery.toLowerCase()) {
case ITEM.toLowerCase():
parsedSearchQuery = '' + 2;
break;
case COLLECTION.toLowerCase():
parsedSearchQuery = '' + 3;
break;
case COMMUNITY.toLowerCase():
parsedSearchQuery = '' + 4;
break;
case SITE.toLowerCase():
parsedSearchQuery = '' + 5;
break;
// no results for invalid search inputs
default:
parsedSearchQuery = '' + -1;
break;
}
parsedSearchQuery = this.parseResourceTypeSearchQuery(this.searchQuery);
break;
}
}
Expand Down
5 changes: 4 additions & 1 deletion src/app/item-page/item-page.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ import { ClarinIdentifierItemFieldComponent } from './simple/field-components/cl
import { ClarinDateItemFieldComponent } from './simple/field-components/clarin-date-item-field/clarin-date-item-field.component';
import { ClarinDescriptionItemFieldComponent } from './simple/field-components/clarin-description-item-field/clarin-description-item-field.component';
import { ClarinFilesSectionComponent } from './clarin-files-section/clarin-files-section.component';
import { UsageReportDataService } from '../core/statistics/usage-report-data.service';
import { CreativeCommonsLicenseFieldComponent } from './simple/field-components/creative-commons-license-field/creative-commons-license-field.component';

const ENTRY_COMPONENTS = [
// put only entry components that use custom decorator
Expand Down Expand Up @@ -154,7 +156,8 @@ const DECLARATIONS = [
ClarinIdentifierItemFieldComponent,
ClarinDateItemFieldComponent,
ClarinDescriptionItemFieldComponent,
ClarinFilesSectionComponent
ClarinFilesSectionComponent,
CreativeCommonsLicenseFieldComponent
];

@NgModule({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<div class="clarin-item-page-field justify-content-start" *ngIf="hasCcLicense$ | async">
<div class="d-flex">
<div><i class="fab fa-creative-commons fa-xs"></i></div>
<div class="pl-1"><b>{{ 'item.page.cc-license' | translate }}</b></div>
</div>
<div>
<div>
<ng-container *ngIf="(ccLicenseUrl$ | async) as licenseUrl">
<ng-container *ngIf="(ccLicenseName$ | async) as licenseName">
<div class="cc-license-content">
<div class="cc-license-row">
<a [href]="licenseUrl"
target="_blank"
rel="noopener noreferrer"
class="cc-license-link">
<div class="cc-icons">
<ng-container [ngSwitch]="getLicenseType(licenseName)">
<ng-container *ngSwitchCase="'by'">
<i class="fab fa-creative-commons"></i>
<i class="fab fa-creative-commons-by"></i>
</ng-container>
<ng-container *ngSwitchCase="'by-sa'">
<i class="fab fa-creative-commons"></i>
<i class="fab fa-creative-commons-by"></i>
<i class="fab fa-creative-commons-sa"></i>
</ng-container>
<ng-container *ngSwitchCase="'by-nc'">
<i class="fab fa-creative-commons"></i>
<i class="fab fa-creative-commons-by"></i>
<i class="fab fa-creative-commons-nc"></i>
</ng-container>
<ng-container *ngSwitchCase="'by-nc-sa'">
<i class="fab fa-creative-commons"></i>
<i class="fab fa-creative-commons-by"></i>
<i class="fab fa-creative-commons-nc"></i>
<i class="fab fa-creative-commons-sa"></i>
</ng-container>
<ng-container *ngSwitchCase="'by-nd'">
<i class="fab fa-creative-commons"></i>
<i class="fab fa-creative-commons-by"></i>
<i class="fab fa-creative-commons-nd"></i>
</ng-container>
<ng-container *ngSwitchCase="'by-nc-nd'">
<i class="fab fa-creative-commons"></i>
<i class="fab fa-creative-commons-by"></i>
<i class="fab fa-creative-commons-nc"></i>
<i class="fab fa-creative-commons-nd"></i>
</ng-container>
<ng-container *ngSwitchCase="'cc0'">
<i class="fab fa-creative-commons"></i>
<i class="fab fa-creative-commons-zero"></i>
</ng-container>
<ng-container *ngSwitchDefault>
<i class="fab fa-creative-commons"></i>
</ng-container>
</ng-container>
</div>
<span class="cc-license-text">{{ licenseName }}</span>
</a>
</div>
</div>
</ng-container>
</ng-container>
</div>
</div>
</div>
Loading
Loading