Skip to content

Commit 08a621b

Browse files
committed
Merge branch 'v15/dev' into v15/feature/enable-document-rollback-as-entity-action
2 parents aa5c58f + f2b337a commit 08a621b

File tree

46 files changed

+789
-258
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+789
-258
lines changed

src/Umbraco.Cms.Api.Management/OpenApi.json

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35978,14 +35978,11 @@
3597835978
"mediaStartNodeIds",
3597935979
"name",
3598035980
"permissions",
35981+
"userGroupIds",
3598135982
"userName"
3598235983
],
3598335984
"type": "object",
3598435985
"properties": {
35985-
"id": {
35986-
"type": "string",
35987-
"format": "uuid"
35988-
},
3598935986
"email": {
3599035987
"type": "string"
3599135988
},
@@ -35995,6 +35992,21 @@
3599535992
"name": {
3599635993
"type": "string"
3599735994
},
35995+
"userGroupIds": {
35996+
"uniqueItems": true,
35997+
"type": "array",
35998+
"items": {
35999+
"oneOf": [
36000+
{
36001+
"$ref": "#/components/schemas/ReferenceByIdModel"
36002+
}
36003+
]
36004+
}
36005+
},
36006+
"id": {
36007+
"type": "string",
36008+
"format": "uuid"
36009+
},
3599836010
"languageIsoCode": {
3599936011
"type": "string",
3600036012
"nullable": true
@@ -37794,6 +37806,16 @@
3779437806
"type": "string",
3779537807
"format": "date-time",
3779637808
"nullable": true
37809+
},
37810+
"scheduledPublishDate": {
37811+
"type": "string",
37812+
"format": "date-time",
37813+
"nullable": true
37814+
},
37815+
"scheduledUnpublishDate": {
37816+
"type": "string",
37817+
"format": "date-time",
37818+
"nullable": true
3779737819
}
3779837820
},
3779937821
"additionalProperties": false

src/Umbraco.Core/Routing/NewDefaultUrlProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ public virtual IEnumerable<UrlInfo> GetOtherUrls(int id, Uri current)
117117

118118
// although we are passing in culture here, if any node in this path is invariant, it ignores the culture anyways so this is ok
119119
var route = GetLegacyRouteFormatById(key, culture);
120-
if (route == null)
120+
if (route == null || route == "#")
121121
{
122122
continue;
123123
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { Node, mergeAttributes } from '@tiptap/core';
2+
3+
export interface DivOptions {
4+
/**
5+
* HTML attributes to add to the element.
6+
* @default {}
7+
* @example { class: 'foo' }
8+
*/
9+
HTMLAttributes: Record<string, any>;
10+
}
11+
12+
export const Div = Node.create<DivOptions>({
13+
name: 'div',
14+
15+
priority: 50,
16+
17+
group: 'block',
18+
19+
content: 'inline*',
20+
21+
addOptions() {
22+
return { HTMLAttributes: {} };
23+
},
24+
25+
parseHTML() {
26+
return [{ tag: 'div' }];
27+
},
28+
29+
renderHTML({ HTMLAttributes }) {
30+
return ['div', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
31+
},
32+
});
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { Extension } from '@tiptap/core';
2+
3+
/**
4+
* Converts camelCase to kebab-case.
5+
* @param {string} str - The string to convert.
6+
* @returns {string} The converted string.
7+
*/
8+
function camelCaseToKebabCase(str: string): string {
9+
return str.replace(/[A-Z]+(?![a-z])|[A-Z]/g, ($, ofs) => (ofs ? '-' : '') + $.toLowerCase());
10+
}
11+
12+
export interface HtmlGlobalAttributesOptions {
13+
/**
14+
* The types where the text align attribute can be applied.
15+
* @default []
16+
* @example ['heading', 'paragraph']
17+
*/
18+
types: Array<string>;
19+
}
20+
21+
export const HtmlGlobalAttributes = Extension.create<HtmlGlobalAttributesOptions>({
22+
name: 'htmlGlobalAttributes',
23+
24+
addOptions() {
25+
return { types: [] };
26+
},
27+
28+
addGlobalAttributes() {
29+
return [
30+
{
31+
types: this.options.types,
32+
attributes: {
33+
class: {},
34+
dataset: {
35+
parseHTML: (element) => element.dataset,
36+
renderHTML: (attributes) => {
37+
const keys = attributes.dataset ? Object.keys(attributes.dataset) : [];
38+
if (!keys.length) return {};
39+
const dataAtrrs: Record<string, string> = {};
40+
keys.forEach((key) => {
41+
dataAtrrs['data-' + camelCaseToKebabCase(key)] = attributes.dataset[key];
42+
});
43+
return dataAtrrs;
44+
},
45+
},
46+
id: {},
47+
style: {},
48+
},
49+
},
50+
];
51+
},
52+
});
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { Node, mergeAttributes } from '@tiptap/core';
2+
3+
export interface SpanOptions {
4+
/**
5+
* HTML attributes to add to the element.
6+
* @default {}
7+
* @example { class: 'foo' }
8+
*/
9+
HTMLAttributes: Record<string, any>;
10+
}
11+
12+
export const Span = Node.create<SpanOptions>({
13+
name: 'span',
14+
15+
group: 'inline',
16+
17+
inline: true,
18+
19+
content: 'inline*',
20+
21+
addOptions() {
22+
return { HTMLAttributes: {} };
23+
},
24+
25+
parseHTML() {
26+
return [{ tag: 'span' }];
27+
},
28+
29+
renderHTML({ HTMLAttributes }) {
30+
return ['span', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
31+
},
32+
});

src/Umbraco.Web.UI.Client/src/external/tiptap/extensions/tiptap-umb-embedded-media.extension.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export const umbEmbeddedMedia = Node.create({
88
inline() {
99
return this.options.inline;
1010
},
11+
1112
atom: true,
1213
marks: '',
1314
draggable: true,
@@ -19,12 +20,18 @@ export const umbEmbeddedMedia = Node.create({
1920
'data-embed-height': { default: 240 },
2021
'data-embed-url': { default: null },
2122
'data-embed-width': { default: 360 },
22-
markup: { default: null },
23+
markup: { default: null, parseHTML: (element) => element.innerHTML },
2324
};
2425
},
2526

2627
parseHTML() {
27-
return [{ tag: 'div', class: 'umb-embed-holder', getAttrs: (node) => ({ markup: node.innerHTML }) }];
28+
return [
29+
{
30+
tag: 'div',
31+
priority: 100,
32+
getAttrs: (dom) => dom.classList.contains('umb-embed-holder') && null,
33+
},
34+
];
2835
},
2936

3037
renderHTML({ HTMLAttributes }) {

src/Umbraco.Web.UI.Client/src/external/tiptap/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,11 @@ export { TextAlign } from '@tiptap/extension-text-align';
2828
export { Underline } from '@tiptap/extension-underline';
2929

3030
// CUSTOM EXTENSIONS
31-
export * from './extensions/tiptap-umb-embedded-media.extension.js';
31+
export * from './extensions/tiptap-div.extension.js';
3232
export * from './extensions/tiptap-figcaption.extension.js';
3333
export * from './extensions/tiptap-figure.extension.js';
34+
export * from './extensions/tiptap-span.extension.js';
35+
export * from './extensions/tiptap-html-global-attributes.extension.js';
36+
export * from './extensions/tiptap-umb-embedded-media.extension.js';
3437
export * from './extensions/tiptap-umb-image.extension.js';
3538
export * from './extensions/tiptap-umb-link.extension.js';

src/Umbraco.Web.UI.Client/src/mocks/data/media-type/media-type.data.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ export type UmbMockMediaTypeModel = MediaTypeResponseModel &
88
MediaTypeTreeItemResponseModel &
99
MediaTypeItemResponseModel;
1010

11+
export type UmbMockMediaTypeUnionModel =
12+
| MediaTypeResponseModel
13+
| MediaTypeTreeItemResponseModel
14+
| MediaTypeItemResponseModel;
15+
1116
export const data: Array<UmbMockMediaTypeModel> = [
1217
{
1318
name: 'Media Type 1',

src/Umbraco.Web.UI.Client/src/mocks/data/media-type/media-type.db.ts

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,21 @@ import { UmbMockEntityFolderManager } from '../utils/entity/entity-folder.manage
33
import { UmbMockEntityTreeManager } from '../utils/entity/entity-tree.manager.js';
44
import { UmbMockEntityItemManager } from '../utils/entity/entity-item.manager.js';
55
import { UmbMockEntityDetailManager } from '../utils/entity/entity-detail.manager.js';
6-
import type { UmbMockMediaTypeModel } from './media-type.data.js';
6+
import type { UmbMockMediaTypeModel, UmbMockMediaTypeUnionModel } from './media-type.data.js';
77
import { data } from './media-type.data.js';
88
import { UmbId } from '@umbraco-cms/backoffice/id';
99
import type {
1010
AllowedMediaTypeModel,
1111
CreateFolderRequestModel,
1212
CreateMediaTypeRequestModel,
13+
GetItemMediaTypeAllowedResponse,
1314
MediaTypeItemResponseModel,
1415
MediaTypeResponseModel,
1516
MediaTypeSortModel,
1617
MediaTypeTreeItemResponseModel,
1718
PagedAllowedMediaTypeModel,
1819
} from '@umbraco-cms/backoffice/external/backend-api';
20+
import { umbDataTypeMockDb } from '../data-type/data-type.db.js';
1921

2022
class UmbMediaTypeMockDB extends UmbEntityMockDbBase<UmbMockMediaTypeModel> {
2123
tree = new UmbMockEntityTreeManager<UmbMockMediaTypeModel>(this, mediaTypeTreeItemMapper);
@@ -45,6 +47,26 @@ class UmbMediaTypeMockDB extends UmbEntityMockDbBase<UmbMockMediaTypeModel> {
4547
const mappedItems = mockItems.map((item) => allowedMediaTypeMapper(item));
4648
return { items: mappedItems, total: mappedItems.length };
4749
}
50+
51+
getAllowedByFileExtension(fileExtension: string): GetItemMediaTypeAllowedResponse {
52+
const allowedTypes = this.data.filter((field) => {
53+
const allProperties = field.properties.flat();
54+
55+
const fileUploadType = allProperties.find((prop) => prop.alias === 'umbracoFile');
56+
if (!fileUploadType) return false;
57+
58+
const dataType = umbDataTypeMockDb.read(fileUploadType.dataType.id);
59+
if (dataType?.editorAlias !== 'Umbraco.UploadField') return false;
60+
61+
const allowedFileExtensions = dataType.values.find((value) => value.alias === 'fileExtensions')?.value;
62+
if (!allowedFileExtensions || !Array.isArray(allowedFileExtensions)) return false;
63+
64+
return allowedFileExtensions.includes(fileExtension);
65+
});
66+
67+
const mappedTypes = allowedTypes.map(mediaTypeItemMapper);
68+
return allowedExtensionMediaTypeMapper(mappedTypes, mappedTypes.length);
69+
}
4870
}
4971

5072
const createMockMediaTypeFolderMapper = (request: CreateFolderRequestModel): UmbMockMediaTypeModel => {
@@ -128,7 +150,7 @@ const mediaTypeTreeItemMapper = (item: UmbMockMediaTypeModel): MediaTypeTreeItem
128150
};
129151
};
130152

131-
const mediaTypeItemMapper = (item: UmbMockMediaTypeModel): MediaTypeItemResponseModel => {
153+
const mediaTypeItemMapper = (item: UmbMockMediaTypeUnionModel): MediaTypeItemResponseModel => {
132154
return {
133155
id: item.id,
134156
name: item.name,
@@ -145,4 +167,14 @@ const allowedMediaTypeMapper = (item: UmbMockMediaTypeModel): AllowedMediaTypeMo
145167
};
146168
};
147169

170+
const allowedExtensionMediaTypeMapper = (
171+
items: Array<MediaTypeItemResponseModel>,
172+
total: number,
173+
): GetItemMediaTypeAllowedResponse => {
174+
return {
175+
items,
176+
total,
177+
};
178+
};
179+
148180
export const umbMediaTypeMockDb = new UmbMediaTypeMockDB(data);

src/Umbraco.Web.UI.Client/src/mocks/handlers/media-type/item.handlers.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,13 @@ export const itemHandlers = [
1010
const items = umbMediaTypeMockDb.item.getItems(ids);
1111
return res(ctx.status(200), ctx.json(items));
1212
}),
13+
14+
rest.get(umbracoPath(`/item${UMB_SLUG}/allowed`), (req, res, ctx) => {
15+
const fileExtension = req.url.searchParams.get('fileExtension');
16+
if (!fileExtension) return;
17+
18+
const response = umbMediaTypeMockDb.getAllowedByFileExtension(fileExtension);
19+
20+
return res(ctx.status(200), ctx.json(response));
21+
}),
1322
];

0 commit comments

Comments
 (0)