Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
170da21
refactor(title-custom-field): Enhance title field handling with new e…
nicobytes Dec 16, 2025
dd4ac22
feat(cachettl): Introduce new cache TTL custom field with modern API …
nicobytes Dec 16, 2025
063de05
feat(native-field): Add custom field component with SCSS styling and …
nicobytes Dec 16, 2025
9520d7c
Merge branch 'main' into 34029-task-migrate-pages-vtls-to-new-api
nicobytes Dec 16, 2025
bacfdf3
feat(dot-edit-content): Enhance native field component with styling a…
nicobytes Dec 16, 2025
c04b1b0
Merge branch '34029-task-migrate-pages-vtls-to-new-api' of github.com…
nicobytes Dec 16, 2025
73cbc2c
Merge branch 'main' into 34029-task-migrate-pages-vtls-to-new-api
nicobytes Dec 17, 2025
8dc9e3c
Merge branch '34029-task-migrate-pages-vtls-to-new-api' of github.com…
nicobytes Dec 17, 2025
33398e9
refactor(template): Clean up CSS comments and improve URL generation …
nicobytes Dec 17, 2025
203e088
refactor(dot-edit-content): Remove unnecessary header and console log…
nicobytes Dec 17, 2025
f3fb68e
feat(og-preview): Introduce new Open Graph preview component with imp…
nicobytes Dec 17, 2025
c10a9ff
feat(dot-browsing): Add DotBrowsingService for site and folder manage…
nicobytes Dec 17, 2025
120a54d
refactor(dot-edit-content): Update imports and utilize DotBrowsingSer…
nicobytes Dec 17, 2025
13bb7af
refactor(dot-browser-selector): Update import paths for DotMessagePip…
nicobytes Dec 17, 2025
c1c68c6
Merge branch 'main' into 34029-browser-component
nicobytes Dec 18, 2025
e2b077a
feat(dialog): Integrate DialogService into IframeField and NativeFiel…
nicobytes Dec 18, 2025
f1ac101
feat(dialog): Integrate DialogService into IframeField and NativeFiel…
nicobytes Dec 18, 2025
152e505
Merge branch 'main' into 34029-browser-component
nicobytes Dec 18, 2025
e28ed90
Merge branch 'main' into 34029-browser-component
nicobytes Dec 22, 2025
351a9d8
Refactor DotContentletService tests to use Spectator for improved cla…
nicobytes Dec 22, 2025
ce3074e
Refactor tests for DotFileFieldUploadService to utilize Spectator for…
nicobytes Dec 22, 2025
4865ab7
Add unit tests for DotBrowsingService using Spectator for improved cl…
nicobytes Dec 22, 2025
8022035
Update ESLint configuration and project settings for improved linting…
nicobytes Dec 23, 2025
149ede2
Remove linting configuration from project.json to streamline project …
nicobytes Dec 23, 2025
6202714
Refactor DotBrowsingService and DotSiteService to utilize ContentByFo…
nicobytes Dec 23, 2025
c52469a
Refactor unit tests for DotBrowsingService and DotEditContentService …
nicobytes Dec 23, 2025
0a74af3
Enhance redirect_custom_field_new.vtl to include additional parameter…
nicobytes Dec 23, 2025
c1a0a5b
fix(docs): in documentation comments for DotBrowsingService, IframeFi…
nicobytes Dec 24, 2025
1e9bd64
Merge branch 'main' into 34029-browser-component
nicobytes Dec 24, 2025
6e86bbc
refactor(DotBrowsingService): migrate from data-access to ui module f…
nicobytes Dec 26, 2025
4822f6f
Merge branch '34029-browser-component' of github.com:dotCMS/core into…
nicobytes Dec 26, 2025
8014b2b
fix(DotFolderService, DotBrowsingService): update service decorators …
nicobytes Dec 26, 2025
e91a91f
refactor(DotBrowserSelectorStore): update import paths for DotBrowsin…
nicobytes Dec 26, 2025
be0c6c2
Merge branch 'main' into 34029-browser-component
nicobytes Dec 26, 2025
9342e8d
Merge branch 'main' into 34029-browser-component
nicobytes Dec 26, 2025
dbe86bd
Merge branch 'main' into 34029-browser-component
nicobytes Dec 26, 2025
6297543
Merge branch 'main' into 34029-browser-component
nicobytes Dec 26, 2025
3aed122
Merge branch 'main' into 34029-browser-component
nicobytes Dec 29, 2025
159208d
Merge branch 'main' into 34029-browser-component
nicobytes Dec 30, 2025
7446f4e
style: update native field styles and enhance custom dropdown functio…
nicobytes Dec 30, 2025
0ce29c6
fix(native-field): enhance cleanup logic and improve host element man…
nicobytes Dec 30, 2025
ef8366d
Merge branch 'main' into 34029-custom-field-template
nicobytes Dec 31, 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
2 changes: 1 addition & 1 deletion core-web/apps/mcp-server/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"extends": ["../../.eslintrc.base.json"],
"ignorePatterns": ["!**/*"],
"ignorePatterns": ["!**/*", "**/node_modules/**", "node_modules/**"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
Expand Down
1 change: 0 additions & 1 deletion core-web/libs/data-access/jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ export default {
'^.+\\.(ts|mjs|js|html)$': [
'jest-preset-angular',
{
isolatedModules: true, // Prevent type checking in tests and deps
tsconfig: '<rootDir>/tsconfig.spec.json',
stringifyContentPathRegex: '\\.(html|svg)$'
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,68 +1,44 @@
import { createHttpFactory, HttpMethod, SpectatorHttp } from '@ngneat/spectator/jest';

import { DotCMSContentlet, DotLanguage } from '@dotcms/dotcms-models';
import {
createHttpFactory,
HttpMethod,
mockProvider,
SpectatorHttp,
SpyObject
} from '@ngneat/spectator/jest';
import { of } from 'rxjs';

import { DotContentletCanLock } from '@dotcms/dotcms-models';
import { createFakeContentlet, createFakeLanguage } from '@dotcms/utils-testing';

import { DotContentletService } from './dot-contentlet.service';

const mockContentletVersionsResponse = {
entity: {
versions: {
en: [{ content: 'one' }, { content: 'two' }] as unknown as DotCMSContentlet[]
}
}
};

const mockContentletByInodeResponse = {
entity: {
archived: false,
baseType: 'CONTENT',
caategory: [{ boys: 'Boys' }, { girls: 'Girls' }],
contentType: 'ContentType1',
date: 1639548000000,
dateTime: 1639612800000,
folder: 'SYSTEM_FOLDER',
hasLiveVersion: true,
hasTitleImage: false,
host: '48190c8c-42c4-46af-8d1a-0cd5db894797',
hostName: 'demo.dotcms.com',
identifier: '758cb37699eae8500d64acc16ebc468e',
inode: '18f707db-ebf3-45f8-9b5a-d8bf6a6f383a',
keyValue: { Colorado: 'snow', 'Costa Rica': 'summer' },
languageId: 1,
live: true,
locked: false,
modDate: 1639784363639,
modUser: 'dotcms.org.1',
modUserName: 'Admin User',
owner: 'dotcms.org.1',
publishDate: 1639784363639,
sortOrder: 0,
stInode: '0121c052881956cd95bfe5dde968ca07',
text: 'final value',
time: 104400000,
title: '758cb37699eae8500d64acc16ebc468e',
titleImage: 'TITLE_IMAGE_NOT_FOUND',
url: '/content.40e5d7cd-2117-47d5-b96d-3278b188deeb',
working: true
} as unknown as DotCMSContentlet
};

export const mockDotContentletCanLock = {
entity: {
canLock: true,
id: '1',
inode: '1',
locked: true
}
};
import { DotUploadFileService } from '../dot-upload-file/dot-upload-file.service';

describe('DotContentletService', () => {
let spectator: SpectatorHttp<DotContentletService>;
const createHttp = createHttpFactory(DotContentletService);
let dotUploadFileService: SpyObject<DotUploadFileService>;

const createHttp = createHttpFactory({
service: DotContentletService,
providers: [mockProvider(DotUploadFileService)]
});

beforeEach(() => (spectator = createHttp()));
beforeEach(() => {
spectator = createHttp();
dotUploadFileService = spectator.inject(DotUploadFileService);
});

it('should bring the contentlet versions by language', () => {
const mockContentlet1 = createFakeContentlet({ content: 'one' });
const mockContentlet2 = createFakeContentlet({ content: 'two' });
const mockContentletVersionsResponse = {
entity: {
versions: {
en: [mockContentlet1, mockContentlet2]
}
}
};

spectator.service.getContentletVersions('123', 'en').subscribe((res) => {
expect(res).toEqual(mockContentletVersionsResponse.entity.versions.en);
});
Expand All @@ -75,59 +51,101 @@ describe('DotContentletService', () => {
});

it('should retrieve a contentlet by its inode', () => {
spectator.service
.getContentletByInode(mockContentletByInodeResponse.entity.inode)
.subscribe((res) => {
expect(res).toEqual(mockContentletByInodeResponse.entity);
});
const mockContentlet = createFakeContentlet({
inode: '18f707db-ebf3-45f8-9b5a-d8bf6a6f383a'
});
const mockResponse = {
entity: mockContentlet
};

const req = spectator.expectOne(
'/api/v1/content/' + mockContentletByInodeResponse.entity.inode,
HttpMethod.GET
);
req.flush(mockContentletByInodeResponse);
spectator.service.getContentletByInode(mockContentlet.inode).subscribe((res) => {
expect(res).toEqual(mockContentlet);
});

const req = spectator.expectOne(`/api/v1/content/${mockContentlet.inode}`, HttpMethod.GET);
req.flush(mockResponse);
});

it('should retrieve a contentlet by its inode with content', () => {
const mockContentlet = createFakeContentlet({
inode: '18f707db-ebf3-45f8-9b5a-d8bf6a6f383a'
});
const mockContentletWithContent = { ...mockContentlet, content: 'file content' };
const mockResponse = {
entity: mockContentlet
};

dotUploadFileService.addContent.mockReturnValue(of(mockContentletWithContent));

spectator.service.getContentletByInodeWithContent(mockContentlet.inode).subscribe((res) => {
expect(res).toEqual(mockContentletWithContent);
expect(dotUploadFileService.addContent).toHaveBeenCalledWith(mockContentlet);
});

const req = spectator.expectOne(`/api/v1/content/${mockContentlet.inode}`, HttpMethod.GET);
req.flush(mockResponse);
});

it('should retrieve available languages for a contentlet', () => {
const mockLanguage1 = createFakeLanguage({ id: 1, language: 'English' });
const mockLanguage2 = createFakeLanguage({ id: 2, language: 'Spanish' });
const mockLanguagesResponse = {
entity: [
{ languageId: 1, language: 'English' },
{ languageId: 2, language: 'Spanish' }
]
entity: [mockLanguage1, mockLanguage2]
};

spectator.service.getLanguages('1').subscribe((res) => {
expect(res).toEqual(mockLanguagesResponse.entity as unknown as DotLanguage[]);
expect(res).toEqual(mockLanguagesResponse.entity);
});

const req = spectator.expectOne('/api/v1/content/1/languages', HttpMethod.GET);
req.flush(mockLanguagesResponse);
});

it('should lock a contentlet', () => {
const mockContentlet = createFakeContentlet({ inode: '1' });
const mockResponse = {
entity: mockContentlet
};

spectator.service.lockContent('1').subscribe((res) => {
expect(res).toEqual(mockContentletByInodeResponse.entity);
expect(res).toEqual(mockContentlet);
});

const req = spectator.expectOne('/api/v1/content/_lock/1', HttpMethod.PUT);
req.flush(mockContentletByInodeResponse);
req.flush(mockResponse);
});

it('should unlock a contentlet', () => {
const mockContentlet = createFakeContentlet({ inode: '1' });
const mockResponse = {
entity: mockContentlet
};

spectator.service.unlockContent('1').subscribe((res) => {
expect(res).toEqual(mockContentletByInodeResponse.entity);
expect(res).toEqual(mockContentlet);
});

const req = spectator.expectOne('/api/v1/content/_unlock/1', HttpMethod.PUT);
req.flush(mockContentletByInodeResponse);
req.flush(mockResponse);
});

it('should check if a contentlet can be locked', () => {
const mockDotContentletCanLock: DotContentletCanLock = {
canLock: true,
id: '1',
inode: '1',
locked: true,
lockedBy: 'user1'
};
const mockResponse = {
entity: mockDotContentletCanLock
};

spectator.service.canLock('1').subscribe((res) => {
expect(res).toEqual(mockDotContentletCanLock.entity);
expect(res).toEqual(mockDotContentletCanLock);
});

const req = spectator.expectOne('/api/v1/content/_canlock/1', HttpMethod.GET);
req.flush(mockDotContentletCanLock);
req.flush(mockResponse);
});
});
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
import { Observable } from 'rxjs';

import { HttpClient } from '@angular/common/http';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Injectable, inject } from '@angular/core';

import { pluck, take } from 'rxjs/operators';
import { map, pluck, switchMap } from 'rxjs/operators';

import { DotCMSContentlet, DotContentletCanLock, DotLanguage } from '@dotcms/dotcms-models';
import {
DotCMSAPIResponse,
DotCMSContentlet,
DotContentletCanLock,
DotLanguage
} from '@dotcms/dotcms-models';

import { DotUploadFileService } from '../dot-upload-file/dot-upload-file.service';

@Injectable({
providedIn: 'root'
})
export class DotContentletService {
private http = inject(HttpClient);
readonly #http = inject(HttpClient);
readonly #dotUploadFileService = inject(DotUploadFileService);

private readonly CONTENTLET_API_URL = '/api/v1/content/';

Expand All @@ -24,9 +32,11 @@ export class DotContentletService {
* @memberof DotContentletService
*/
getContentletVersions(identifier: string, language: string): Observable<DotCMSContentlet[]> {
return this.http
.get(`${this.CONTENTLET_API_URL}versions?identifier=${identifier}&groupByLang=1`)
.pipe(take(1), pluck('entity', 'versions', language));
return this.#http
.get<
DotCMSAPIResponse<DotCMSContentlet[]>
>(`${this.CONTENTLET_API_URL}versions?identifier=${identifier}&groupByLang=1`)
.pipe(pluck('entity', 'versions', language));
}

/**
Expand All @@ -36,8 +46,28 @@ export class DotContentletService {
* @returns {Observable<DotCMSContentlet>} An observable emitting the contentlet.
* @memberof DotContentletService
*/
getContentletByInode(inode: string): Observable<DotCMSContentlet> {
return this.http.get(`${this.CONTENTLET_API_URL}${inode}`).pipe(take(1), pluck('entity'));
getContentletByInode(inode: string, httpParams?: HttpParams): Observable<DotCMSContentlet> {
return this.#http
.get<
DotCMSAPIResponse<DotCMSContentlet>
>(`${this.CONTENTLET_API_URL}${inode}`, { params: httpParams })
.pipe(map((response) => response.entity));
}

/**
* Get the Contentlet by its inode and adds the content if it's a editable as text file.
*
* @param {string} inode - The inode of the contentlet.
* @returns {Observable<DotCMSContentlet>} An observable emitting the contentlet.
* @memberof DotContentletService
*/
getContentletByInodeWithContent(
inode: string,
httpParams?: HttpParams
): Observable<DotCMSContentlet> {
return this.getContentletByInode(inode, httpParams).pipe(
switchMap((contentlet) => this.#dotUploadFileService.addContent(contentlet))
);
}

/**
Expand All @@ -48,9 +78,11 @@ export class DotContentletService {
* @memberof DotContentletService
*/
getLanguages(identifier: string): Observable<DotLanguage[]> {
return this.http
.get(`${this.CONTENTLET_API_URL}${identifier}/languages`)
.pipe(take(1), pluck('entity'));
return this.#http
.get<
DotCMSAPIResponse<DotLanguage[]>
>(`${this.CONTENTLET_API_URL}${identifier}/languages`)
.pipe(map((response) => response.entity));
}

/**
Expand All @@ -61,9 +93,11 @@ export class DotContentletService {
* @memberof DotContentletService
*/
lockContent(inode: string): Observable<DotCMSContentlet> {
return this.http
.put(`${this.CONTENTLET_API_URL}_lock/${inode}`, {})
.pipe(take(1), pluck('entity'));
return this.#http
.put<
DotCMSAPIResponse<DotCMSContentlet>
>(`${this.CONTENTLET_API_URL}_lock/${inode}`, {})
.pipe(map((response) => response.entity));
}

/**
Expand All @@ -74,9 +108,11 @@ export class DotContentletService {
* @memberof DotContentletService
*/
unlockContent(inode: string): Observable<DotCMSContentlet> {
return this.http
.put(`${this.CONTENTLET_API_URL}_unlock/${inode}`, {})
.pipe(take(1), pluck('entity'));
return this.#http
.put<
DotCMSAPIResponse<DotCMSContentlet>
>(`${this.CONTENTLET_API_URL}_unlock/${inode}`, {})
.pipe(map((response) => response.entity));
}

/**
Expand All @@ -87,8 +123,10 @@ export class DotContentletService {
* @memberof DotContentletService
*/
canLock(inode: string): Observable<DotContentletCanLock> {
return this.http
.get(`${this.CONTENTLET_API_URL}_canlock/${inode}`)
.pipe(take(1), pluck('entity'));
return this.#http
.get<
DotCMSAPIResponse<DotContentletCanLock>
>(`${this.CONTENTLET_API_URL}_canlock/${inode}`)
.pipe(map((response) => response.entity));
}
}
Loading
Loading