Merge branch 'main' into feature/section-alias-condition-one-of
This commit is contained in:
@@ -1,22 +1,17 @@
|
||||
const { rest } = window.MockServiceWorker;
|
||||
import type { OEmbedResult} from '@umbraco-cms/backoffice/modal';
|
||||
import { OEmbedStatus } from '@umbraco-cms/backoffice/modal';
|
||||
import type { OEmbedResponseModel } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
import { umbracoPath } from '@umbraco-cms/backoffice/utils';
|
||||
|
||||
export const handlers = [
|
||||
rest.get(umbracoPath('/rteembed'), (req, res, ctx) => {
|
||||
const widthParam = req.url.searchParams.get('width');
|
||||
rest.get(umbracoPath('/oembed/query'), (req, res, ctx) => {
|
||||
const widthParam = req.url.searchParams.get('maxWidth');
|
||||
const width = widthParam ? parseInt(widthParam) : 360;
|
||||
|
||||
const heightParam = req.url.searchParams.get('height');
|
||||
const heightParam = req.url.searchParams.get('maxHeight');
|
||||
const height = heightParam ? parseInt(heightParam) : 240;
|
||||
|
||||
const response: OEmbedResult = {
|
||||
supportsDimensions: true,
|
||||
const response: OEmbedResponseModel = {
|
||||
markup: `<iframe width="${width}" height="${height}" src="https://www.youtube.com/embed/wJNbtYdr-Hg?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen title="Sleep Token - The Summoning"></iframe>`,
|
||||
oEmbedStatus: OEmbedStatus.Success,
|
||||
width,
|
||||
height,
|
||||
};
|
||||
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
|
||||
@@ -31,7 +31,7 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
|
||||
identifier: 'content-type-tabs-sorter',
|
||||
itemSelector: 'uui-tab',
|
||||
containerSelector: 'uui-tab-group',
|
||||
disabledItemSelector: '#root-tab',
|
||||
disabledItemSelector: ':not([sortable])',
|
||||
resolvePlacement: (args) => args.relatedRect.left + args.relatedRect.width * 0.5 > args.pointerX,
|
||||
onChange: ({ model }) => {
|
||||
this._tabs = model;
|
||||
@@ -47,30 +47,30 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
|
||||
// Doesn't exist in model
|
||||
if (newIndex === -1) return;
|
||||
|
||||
// First in list
|
||||
if (newIndex === 0 && model.length > 1) {
|
||||
this.#tabsStructureHelper.partialUpdateContainer(item.id, { sortOrder: model[1].sortOrder - 1 });
|
||||
return;
|
||||
// As origin we set prev sort order to -1, so if no other then our item will become 0
|
||||
let prevSortOrder = -1;
|
||||
|
||||
// If not first in list, then get the sortOrder of the item before. [NL]
|
||||
if (newIndex > 0 && model.length > 0) {
|
||||
prevSortOrder = model[newIndex - 1].sortOrder;
|
||||
}
|
||||
|
||||
// Not first in list
|
||||
if (newIndex > 0 && model.length > 1) {
|
||||
const prevItemSortOrder = model[newIndex - 1].sortOrder;
|
||||
// increase the prevSortOrder and use it for the moved item,
|
||||
this.#tabsStructureHelper.partialUpdateContainer(item.id, {
|
||||
sortOrder: ++prevSortOrder,
|
||||
});
|
||||
|
||||
let weight = 1;
|
||||
this.#tabsStructureHelper.partialUpdateContainer(item.id, { sortOrder: prevItemSortOrder + weight });
|
||||
|
||||
// Check for overlaps
|
||||
// TODO: Make sure this take inheritance into considerations.
|
||||
model.some((entry, index) => {
|
||||
if (index <= newIndex) return;
|
||||
if (entry.sortOrder === prevItemSortOrder + weight) {
|
||||
weight++;
|
||||
this.#tabsStructureHelper.partialUpdateContainer(entry.id, { sortOrder: prevItemSortOrder + weight });
|
||||
}
|
||||
// Break the loop
|
||||
return true;
|
||||
// Adjust everyone right after, until there is a gap between the sortOrders: [NL]
|
||||
let i = newIndex + 1;
|
||||
let entry: UmbPropertyTypeContainerModel | undefined;
|
||||
// As long as there is an item with the index & the sortOrder is less or equal to the prevSortOrder, we will update the sortOrder:
|
||||
while ((entry = model[i]) !== undefined && entry.sortOrder <= prevSortOrder) {
|
||||
// Increase the prevSortOrder and use it for the item:
|
||||
this.#tabsStructureHelper.partialUpdateContainer(entry.id, {
|
||||
sortOrder: ++prevSortOrder,
|
||||
});
|
||||
|
||||
i++;
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -399,7 +399,7 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
|
||||
? this.localize.term('general_reorderDone')
|
||||
: this.localize.term('general_reorder');
|
||||
|
||||
return html`<div class="tab-actions">
|
||||
return html`<div>
|
||||
${this._compositionRepositoryAlias
|
||||
? html`<uui-button
|
||||
look="outline"
|
||||
@@ -458,7 +458,8 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
|
||||
label=${tab.name && tab.name !== '' ? tab.name : 'unnamed'}
|
||||
.active=${tabActive}
|
||||
href=${path}
|
||||
data-umb-tab-id=${ifDefined(tab.id)}>
|
||||
data-umb-tab-id=${ifDefined(tab.id)}
|
||||
?sortable=${ownedTab}>
|
||||
${this.renderTabInner(tab, tabActive, ownedTab)}
|
||||
</uui-tab>`;
|
||||
}
|
||||
@@ -581,6 +582,7 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
|
||||
position: relative;
|
||||
border-left: 1px hidden transparent;
|
||||
border-right: 1px solid var(--uui-color-border);
|
||||
background-color: var(--uui-color-surface);
|
||||
}
|
||||
|
||||
.not-active uui-button {
|
||||
|
||||
@@ -1,235 +1,146 @@
|
||||
import { css, html, unsafeHTML, when, customElement, property, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbOEmbedRepository } from './repository/oembed.repository.js';
|
||||
import { css, html, unsafeHTML, when, customElement, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import type {
|
||||
OEmbedResult,
|
||||
UmbEmbeddedMediaModalData,
|
||||
UmbEmbeddedMediaModalValue} from '@umbraco-cms/backoffice/modal';
|
||||
import {
|
||||
OEmbedStatus,
|
||||
UmbModalBaseElement,
|
||||
} from '@umbraco-cms/backoffice/modal';
|
||||
import { umbracoPath } from '@umbraco-cms/backoffice/utils';
|
||||
|
||||
interface UmbEmbeddedMediaModalModel {
|
||||
url?: string;
|
||||
info?: string;
|
||||
a11yInfo?: string;
|
||||
originalWidth: number;
|
||||
originalHeight: number;
|
||||
width: number;
|
||||
height: number;
|
||||
constrain: boolean;
|
||||
}
|
||||
import type { UmbEmbeddedMediaModalData, UmbEmbeddedMediaModalValue } from '@umbraco-cms/backoffice/modal';
|
||||
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
|
||||
import type { UUIButtonState, UUIInputEvent } from '@umbraco-cms/backoffice/external/uui';
|
||||
|
||||
@customElement('umb-embedded-media-modal')
|
||||
export class UmbEmbeddedMediaModalElement extends UmbModalBaseElement<
|
||||
UmbEmbeddedMediaModalData,
|
||||
UmbEmbeddedMediaModalValue
|
||||
> {
|
||||
#loading = false;
|
||||
#embedResult!: OEmbedResult;
|
||||
|
||||
#handleConfirm() {
|
||||
this.value = {
|
||||
preview: this.#embedResult.markup,
|
||||
originalWidth: this._model.width,
|
||||
originalHeight: this._model.originalHeight,
|
||||
width: this.#embedResult.width,
|
||||
height: this.#embedResult.height,
|
||||
};
|
||||
this.modalContext?.submit();
|
||||
}
|
||||
|
||||
#handleCancel() {
|
||||
this.modalContext?.reject();
|
||||
}
|
||||
#oEmbedRepository = new UmbOEmbedRepository(this);
|
||||
#validUrl?: string;
|
||||
|
||||
@state()
|
||||
private _model: UmbEmbeddedMediaModalModel = {
|
||||
url: '',
|
||||
width: 360,
|
||||
height: 240,
|
||||
constrain: true,
|
||||
info: '',
|
||||
a11yInfo: '',
|
||||
originalHeight: 240,
|
||||
originalWidth: 360,
|
||||
};
|
||||
private _loading?: UUIButtonState;
|
||||
|
||||
@state()
|
||||
private _width = 360;
|
||||
|
||||
@state()
|
||||
private _height = 240;
|
||||
|
||||
@state()
|
||||
private _url = '';
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
if (this.data?.width) this._width = this.data.width;
|
||||
if (this.data?.height) this._height = this.data.height;
|
||||
if (this.data?.constrain) this.value = { ...this.value, constrain: this.data.constrain };
|
||||
|
||||
if (this.data?.url) {
|
||||
Object.assign(this._model, this.data);
|
||||
this._url = this.data.url;
|
||||
this.#getPreview();
|
||||
}
|
||||
}
|
||||
|
||||
async #getPreview() {
|
||||
this._model.info = '';
|
||||
this._model.a11yInfo = '';
|
||||
this._loading = 'waiting';
|
||||
|
||||
this.#loading = true;
|
||||
this.requestUpdate('_model');
|
||||
const { data } = await this.#oEmbedRepository.requestOEmbed({
|
||||
url: this._url,
|
||||
maxWidth: this._width,
|
||||
maxHeight: this._height,
|
||||
});
|
||||
|
||||
try {
|
||||
// TODO => use backend cli when available
|
||||
const result = await fetch(
|
||||
umbracoPath('/rteembed?') +
|
||||
new URLSearchParams({
|
||||
url: this._model.url,
|
||||
width: this._model.width?.toString(),
|
||||
height: this._model.height?.toString(),
|
||||
} as { [key: string]: string }),
|
||||
);
|
||||
|
||||
this.#embedResult = await result.json();
|
||||
|
||||
switch (this.#embedResult.oEmbedStatus) {
|
||||
case 0:
|
||||
this.#onPreviewFailed('Not supported');
|
||||
break;
|
||||
case 1:
|
||||
this.#onPreviewFailed('Could not embed media - please ensure the URL is valid');
|
||||
break;
|
||||
case 2:
|
||||
this._model.info = '';
|
||||
this._model.a11yInfo = 'Retrieved URL';
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
this.#onPreviewFailed('Could not embed media - please ensure the URL is valid');
|
||||
if (data) {
|
||||
this.#validUrl = this._url;
|
||||
this.value = { ...this.value, markup: data.markup, url: this.#validUrl };
|
||||
this._loading = 'success';
|
||||
} else {
|
||||
this.#validUrl = undefined;
|
||||
this._loading = 'failed';
|
||||
}
|
||||
|
||||
this.#loading = false;
|
||||
this.requestUpdate('_model');
|
||||
}
|
||||
|
||||
#onPreviewFailed(message: string) {
|
||||
this._model.info = message;
|
||||
this._model.a11yInfo = message;
|
||||
#onUrlChange(e: UUIInputEvent) {
|
||||
this._url = e.target.value as string;
|
||||
}
|
||||
|
||||
#onUrlChange(e: InputEvent) {
|
||||
this._model.url = (e.target as HTMLInputElement).value;
|
||||
this.requestUpdate('_model');
|
||||
#onWidthChange(e: UUIInputEvent) {
|
||||
this._width = parseInt(e.target.value as string, 10);
|
||||
this.#getPreview();
|
||||
}
|
||||
|
||||
#onWidthChange(e: InputEvent) {
|
||||
this._model.width = parseInt((e.target as HTMLInputElement).value, 10);
|
||||
this.#changeSize('width');
|
||||
}
|
||||
|
||||
#onHeightChange(e: InputEvent) {
|
||||
this._model.height = parseInt((e.target as HTMLInputElement).value, 10);
|
||||
this.#changeSize('height');
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the width or height axis dimension when the other is changed.
|
||||
* If constrain is false, axis change independently
|
||||
* @param axis {string}
|
||||
*/
|
||||
#changeSize(axis: 'width' | 'height') {
|
||||
const resize = this._model.originalWidth !== this._model.width || this._model.originalHeight !== this._model.height;
|
||||
|
||||
if (this._model.constrain) {
|
||||
if (axis === 'width') {
|
||||
this._model.height = Math.round((this._model.width / this._model.originalWidth) * this._model.height);
|
||||
} else {
|
||||
this._model.width = Math.round((this._model.height / this._model.originalHeight) * this._model.width);
|
||||
}
|
||||
}
|
||||
|
||||
this._model.originalWidth = this._model.width;
|
||||
this._model.originalHeight = this._model.height;
|
||||
|
||||
if (this._model.url !== '' && resize) {
|
||||
this.#getPreview();
|
||||
}
|
||||
#onHeightChange(e: UUIInputEvent) {
|
||||
this._height = parseInt(e.target.value as string, 10);
|
||||
this.#getPreview();
|
||||
}
|
||||
|
||||
#onConstrainChange() {
|
||||
this._model.constrain = !this._model.constrain;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the embed does not support dimensions, or was not requested successfully
|
||||
* the width, height and constrain controls are disabled
|
||||
* @returns {boolean}
|
||||
*/
|
||||
#dimensionControlsDisabled() {
|
||||
return !this.#embedResult?.supportsDimensions || this.#embedResult?.oEmbedStatus !== OEmbedStatus.Success;
|
||||
const constrain = !this.value?.constrain;
|
||||
this.value = { ...this.value, constrain };
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<umb-body-layout headline="Embed">
|
||||
<uui-box>
|
||||
<umb-property-layout label="URL" orientation="vertical">
|
||||
<umb-property-layout label=${this.localize.term('general_url')} orientation="vertical">
|
||||
<div slot="editor">
|
||||
<uui-input .value=${this._model.url} type="text" @change=${this.#onUrlChange} required="true">
|
||||
<uui-input id="url" .value=${this._url} @input=${this.#onUrlChange} required="true">
|
||||
<uui-button
|
||||
slot="append"
|
||||
look="primary"
|
||||
color="positive"
|
||||
@click=${this.#getPreview}
|
||||
?disabled=${!this._model.url}
|
||||
label="Retrieve"></uui-button>
|
||||
label=${this.localize.term('general_retrieve')}></uui-button>
|
||||
</uui-input>
|
||||
</div>
|
||||
</umb-property-layout>
|
||||
|
||||
${when(
|
||||
this.#embedResult?.oEmbedStatus === OEmbedStatus.Success || this._model.a11yInfo,
|
||||
this.#validUrl !== undefined,
|
||||
() =>
|
||||
html` <umb-property-layout label="Preview" orientation="vertical">
|
||||
html` <umb-property-layout label=${this.localize.term('general_preview')} orientation="vertical">
|
||||
<div slot="editor">
|
||||
${when(this.#loading, () => html`<uui-loader-circle></uui-loader-circle>`)}
|
||||
${when(this.#embedResult?.markup, () => html`${unsafeHTML(this.#embedResult.markup)}`)}
|
||||
${when(this._model.info, () => html` <p aria-hidden="true">${this._model.info}</p>`)}
|
||||
${when(
|
||||
this._model.a11yInfo,
|
||||
() => html` <p class="sr-only" role="alert">${this._model.a11yInfo}</p>`,
|
||||
)}
|
||||
${when(this._loading === 'waiting', () => html`<uui-loader-circle></uui-loader-circle>`)}
|
||||
${when(this.value?.markup, () => html`${unsafeHTML(this.value.markup)}`)}
|
||||
</div>
|
||||
</umb-property-layout>`,
|
||||
)}
|
||||
|
||||
<umb-property-layout label="Width" orientation="vertical">
|
||||
<umb-property-layout label=${this.localize.term('general_width')} orientation="vertical">
|
||||
<uui-input
|
||||
slot="editor"
|
||||
.value=${this._model.width}
|
||||
.value=${this._width}
|
||||
type="number"
|
||||
?disabled=${this.#dimensionControlsDisabled()}
|
||||
@change=${this.#onWidthChange}></uui-input>
|
||||
@change=${this.#onWidthChange}
|
||||
?disabled=${this.#validUrl ? false : true}></uui-input>
|
||||
</umb-property-layout>
|
||||
|
||||
<umb-property-layout label="Height" orientation="vertical">
|
||||
<umb-property-layout label=${this.localize.term('general_height')} orientation="vertical">
|
||||
<uui-input
|
||||
slot="editor"
|
||||
.value=${this._model.height}
|
||||
.value=${this._height}
|
||||
type="number"
|
||||
?disabled=${this.#dimensionControlsDisabled()}
|
||||
@change=${this.#onHeightChange}></uui-input>
|
||||
@change=${this.#onHeightChange}
|
||||
?disabled=${this.#validUrl ? false : true}></uui-input>
|
||||
</umb-property-layout>
|
||||
|
||||
<umb-property-layout label="Constrain" orientation="vertical">
|
||||
<umb-property-layout label=${this.localize.term('general_constrainProportions')} orientation="vertical">
|
||||
<uui-toggle
|
||||
slot="editor"
|
||||
@change=${this.#onConstrainChange}
|
||||
?disabled=${this.#dimensionControlsDisabled()}
|
||||
.checked=${this._model.constrain}></uui-toggle>
|
||||
.checked=${this.value?.constrain ?? false}></uui-toggle>
|
||||
</umb-property-layout>
|
||||
</uui-box>
|
||||
|
||||
<uui-button slot="actions" id="cancel" label="Cancel" @click=${this.#handleCancel}>Cancel</uui-button>
|
||||
<uui-button
|
||||
slot="actions"
|
||||
id="cancel"
|
||||
label=${this.localize.term('buttons_confirmActionCancel')}
|
||||
@click=${() => this.modalContext?.reject()}></uui-button>
|
||||
<uui-button
|
||||
slot="actions"
|
||||
id="submit"
|
||||
color="positive"
|
||||
look="primary"
|
||||
label="Submit"
|
||||
@click=${this.#handleConfirm}></uui-button>
|
||||
label=${this.localize.term('buttons_confirmActionConfirm')}
|
||||
@click=${() => this.modalContext?.submit()}></uui-button>
|
||||
</umb-body-layout>
|
||||
`;
|
||||
}
|
||||
@@ -237,27 +148,11 @@ export class UmbEmbeddedMediaModalElement extends UmbModalBaseElement<
|
||||
static styles = [
|
||||
UmbTextStyles,
|
||||
css`
|
||||
h3 {
|
||||
margin-left: var(--uui-size-space-5);
|
||||
margin-right: var(--uui-size-space-5);
|
||||
}
|
||||
|
||||
uui-input {
|
||||
width: 100%;
|
||||
--uui-button-border-radius: 0;
|
||||
}
|
||||
|
||||
.sr-only {
|
||||
clip: rect(0, 0, 0, 0);
|
||||
border: 0;
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
umb-property-layout:first-child {
|
||||
padding-top: 0;
|
||||
}
|
||||
@@ -265,10 +160,6 @@ export class UmbEmbeddedMediaModalElement extends UmbModalBaseElement<
|
||||
umb-property-layout:last-child {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from './repository/index.js';
|
||||
@@ -0,0 +1,13 @@
|
||||
import { manifests as repositories } from './repository/manifests.js';
|
||||
import type { ManifestModal } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
const modals: Array<ManifestModal> = [
|
||||
{
|
||||
type: 'modal',
|
||||
alias: 'Umb.Modal.EmbeddedMedia',
|
||||
name: 'Embedded Media Modal',
|
||||
element: () => import('./embedded-media-modal.element.js'),
|
||||
},
|
||||
];
|
||||
|
||||
export const manifests = [...modals, ...repositories];
|
||||
@@ -0,0 +1,2 @@
|
||||
export { UmbOEmbedRepository } from './oembed.repository.js';
|
||||
export { UMB_OEMBED_REPOSITORY_ALIAS } from './manifests.js';
|
||||
@@ -0,0 +1,13 @@
|
||||
import { UmbOEmbedRepository } from './oembed.repository.js';
|
||||
import type { ManifestRepository, ManifestTypes } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
export const UMB_OEMBED_REPOSITORY_ALIAS = 'Umb.Repository.OEmbed';
|
||||
|
||||
const repository: ManifestRepository = {
|
||||
type: 'repository',
|
||||
alias: UMB_OEMBED_REPOSITORY_ALIAS,
|
||||
name: 'OEmbed Repository',
|
||||
api: UmbOEmbedRepository,
|
||||
};
|
||||
|
||||
export const manifests: Array<ManifestTypes> = [repository];
|
||||
@@ -0,0 +1,20 @@
|
||||
import { UmbOEmbedServerDataSource } from './oembed.server.data.js';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
|
||||
|
||||
export class UmbOEmbedRepository extends UmbControllerBase implements UmbApi {
|
||||
#dataSource = new UmbOEmbedServerDataSource(this);
|
||||
|
||||
constructor(host: UmbControllerHost) {
|
||||
super(host);
|
||||
}
|
||||
|
||||
async requestOEmbed({ url, maxWidth, maxHeight }: { url?: string; maxWidth?: number; maxHeight?: number }) {
|
||||
const { data, error } = await this.#dataSource.getOEmbedQuery({ url, maxWidth, maxHeight });
|
||||
if (!error) {
|
||||
return { data };
|
||||
}
|
||||
return { error };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { OEmbedService } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources';
|
||||
|
||||
/**
|
||||
* A data source for the OEmbed that fetches data from a given URL.
|
||||
* @export
|
||||
* @class UmbOEmbedServerDataSource
|
||||
* @implements {RepositoryDetailDataSource}
|
||||
*/
|
||||
export class UmbOEmbedServerDataSource {
|
||||
#host: UmbControllerHost;
|
||||
|
||||
/**
|
||||
* Creates an instance of UmbOEmbedServerDataSource.
|
||||
* @param {UmbControllerHost} host
|
||||
* @memberof UmbOEmbedServerDataSource
|
||||
*/
|
||||
constructor(host: UmbControllerHost) {
|
||||
this.#host = host;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches markup for the given URL.
|
||||
* @param {string} unique
|
||||
* @memberof UmbOEmbedServerDataSource
|
||||
*/
|
||||
async getOEmbedQuery({ url, maxWidth, maxHeight }: { url?: string; maxWidth?: number; maxHeight?: number }) {
|
||||
return tryExecuteAndNotify(this.#host, OEmbedService.getOembedQuery({ url, maxWidth, maxHeight }));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './embedded-media/index.js';
|
||||
@@ -1,31 +1,18 @@
|
||||
import { UmbModalToken } from './modal-token.js';
|
||||
|
||||
export enum OEmbedStatus {
|
||||
NotSupported,
|
||||
Error,
|
||||
Success,
|
||||
}
|
||||
|
||||
export interface UmbEmbeddedMediaDimensions {
|
||||
width: number;
|
||||
height: number;
|
||||
constrain?: boolean;
|
||||
}
|
||||
|
||||
export interface UmbEmbeddedMediaModalData extends UmbEmbeddedMediaDimensions {
|
||||
export interface UmbEmbeddedMediaModalData extends Partial<UmbEmbeddedMediaDimensionsModel> {
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export interface OEmbedResult extends UmbEmbeddedMediaDimensions {
|
||||
oEmbedStatus: OEmbedStatus;
|
||||
supportsDimensions: boolean;
|
||||
markup?: string;
|
||||
export interface UmbEmbeddedMediaDimensionsModel {
|
||||
constrain: boolean;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface UmbEmbeddedMediaModalValue extends UmbEmbeddedMediaModalData {
|
||||
preview?: string;
|
||||
originalWidth: number;
|
||||
originalHeight: number;
|
||||
markup: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export const UMB_EMBEDDED_MEDIA_MODAL = new UmbModalToken<UmbEmbeddedMediaModalData, UmbEmbeddedMediaModalValue>(
|
||||
|
||||
@@ -6,10 +6,22 @@ export default class UmbTinyMceEmbeddedMediaPlugin extends UmbTinyMcePluginBase
|
||||
constructor(args: TinyMcePluginArguments) {
|
||||
super(args);
|
||||
|
||||
this.editor.ui.registry.addButton('umbembeddialog', {
|
||||
this.editor.ui.registry.addToggleButton('umbembeddialog', {
|
||||
icon: 'embed',
|
||||
tooltip: 'Embed',
|
||||
onAction: () => this.#onAction(),
|
||||
onSetup: (api) => {
|
||||
const editor = this.editor;
|
||||
const onNodeChange = () => {
|
||||
const selectedElm = editor.selection.getNode();
|
||||
api.setActive(
|
||||
selectedElm.nodeName.toUpperCase() === 'DIV' && selectedElm.classList.contains('umb-embed-holder'),
|
||||
);
|
||||
};
|
||||
|
||||
editor.on('NodeChange', onNodeChange);
|
||||
return () => editor.off('NodeChange', onNodeChange);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -44,17 +56,18 @@ export default class UmbTinyMceEmbeddedMediaPlugin extends UmbTinyMcePluginBase
|
||||
#insertInEditor(embed: UmbEmbeddedMediaModalValue, activeElement: HTMLElement) {
|
||||
// Wrap HTML preview content here in a DIV with non-editable class of .mceNonEditable
|
||||
// This turns it into a selectable/cutable block to move about
|
||||
|
||||
const wrapper = this.editor.dom.create(
|
||||
'div',
|
||||
{
|
||||
class: 'mceNonEditable umb-embed-holder',
|
||||
'data-embed-url': embed.url ?? '',
|
||||
'data-embed-height': embed.height,
|
||||
'data-embed-width': embed.width,
|
||||
'data-embed-height': embed.height!,
|
||||
'data-embed-width': embed.width!,
|
||||
'data-embed-constrain': embed.constrain ?? false,
|
||||
contenteditable: false,
|
||||
},
|
||||
embed.preview,
|
||||
embed.markup,
|
||||
);
|
||||
|
||||
// Only replace if activeElement is an Embed element.
|
||||
|
||||
Reference in New Issue
Block a user