media caption textalt modal

This commit is contained in:
Lone Iversen
2024-03-26 11:49:31 +01:00
committed by Jacob Overgaard
parent e26d948ab3
commit 943b379a3a
8 changed files with 174 additions and 20 deletions

View File

@@ -1 +1,2 @@
export * from './components/index.js';
export * from './modals/index.js';

View File

@@ -1,4 +1,5 @@
import { manifests as propertyEditors } from './property-editors/manifests.js';
import { manifests as plugins } from './plugins/manifests.js';
import { manifests as modalManifests } from './modals/manifests.js';
export const manifests = [...propertyEditors, ...plugins];
export const manifests = [...propertyEditors, ...plugins, ...modalManifests];

View File

@@ -0,0 +1 @@
export * from './media-caption-alt-text/index.js';

View File

@@ -0,0 +1,12 @@
import type { ManifestModal } from '@umbraco-cms/backoffice/extension-registry';
const modals: Array<ManifestModal> = [
{
type: 'modal',
alias: 'Umb.Modal.MediaCaptionAltText',
name: 'Media Caption Alt Text',
js: () => import('./media-caption-alt-text/media-caption-alt-text-modal.element.js'),
},
];
export const manifests = [...modals];

View File

@@ -0,0 +1,2 @@
export * from './media-caption-alt-text-modal.element.js';
export * from './media-caption-alt-text-modal.token.js';

View File

@@ -0,0 +1,87 @@
import type {
UmbMediaCaptionAltTextModalData,
UmbMediaCaptionAltTextModalValue,
} from './media-caption-alt-text-modal.token.js';
import { css, html, customElement, state, ifDefined } from '@umbraco-cms/backoffice/external/lit';
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
import '@umbraco-cms/backoffice/block-type';
import { UmbMediaDetailRepository } from '@umbraco-cms/backoffice/media';
import type { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui';
@customElement('umb-media-caption-alt-text-modal')
export class UmbMediaCaptionAltTextModalElement extends UmbModalBaseElement<
UmbMediaCaptionAltTextModalData,
UmbMediaCaptionAltTextModalValue
> {
#mediaUnique?: string;
#mediaDetailRepository = new UmbMediaDetailRepository(this);
connectedCallback() {
super.connectedCallback();
this.#mediaUnique = this.data?.mediaUnique;
this.#getMediaDetail();
}
async #getMediaDetail() {
if (!this.#mediaUnique) return;
const { data } = await this.#mediaDetailRepository.requestByUnique(this.#mediaUnique);
if (!data) return;
this.value = { altText: data.variants[0].name, caption: undefined, url: data.urls[0]?.url ?? '' };
}
render() {
return html`
<umb-body-layout .headline=${this.localize.term('defaultdialogs_editSelectedMedia')}>
<div id="wrapper">
<uui-label for="alt-text">${this.localize.term('content_altTextOptional')}</uui-label>
<uui-input
id="alt-text"
label="alt text"
.value=${this.value?.altText ?? ''}
@input=${(e: UUIInputEvent) =>
(this.value = { ...this.value, altText: e.target.value as string })}></uui-input>
<uui-label for="caption">${this.localize.term('content_captionTextOptional')}</uui-label>
<uui-input
id="caption"
label="caption"
@input=${(e: UUIInputEvent) =>
(this.value = { ...this.value, caption: e.target.value as string })}></uui-input>
<img src=${this.value?.url ?? ''} alt=${this.value?.altText ?? ''} />
${this.value?.caption ?? ''}
</div>
<div slot="actions">
<uui-button label=${this.localize.term('general_close')} @click=${this._rejectModal}></uui-button>
<uui-button
label=${this.localize.term('general_submit')}
look="primary"
color="positive"
@click=${this._submitModal}></uui-button>
</div>
</umb-body-layout>
`;
}
static styles = [
css`
uui-input {
margin-bottom: var(--uui-size-layout-1);
}
#wrapper {
display: flex;
flex-direction: column;
}
`,
];
}
export default UmbMediaCaptionAltTextModalElement;
declare global {
interface HTMLElementTagNameMap {
'umb-media-caption-alt-text-modal': UmbMediaCaptionAltTextModalElement;
}
}

View File

@@ -0,0 +1,21 @@
import { UmbModalToken } from '@umbraco-cms/backoffice/modal';
export interface UmbMediaCaptionAltTextModalData {
mediaUnique: string;
}
export type UmbMediaCaptionAltTextModalValue = {
altText?: string;
caption?: string;
url: string;
};
export const UMB_MEDIA_CAPTION_ALT_TEXT_MODAL = new UmbModalToken<
UmbMediaCaptionAltTextModalData,
UmbMediaCaptionAltTextModalValue
>('Umb.Modal.MediaCaptionAltText', {
modal: {
type: 'sidebar',
size: 'small',
},
});

View File

@@ -1,11 +1,11 @@
import { UMB_MEDIA_CAPTION_ALT_TEXT_MODAL } from '../modals/media-caption-alt-text/media-caption-alt-text-modal.token.js';
import { type TinyMcePluginArguments, UmbTinyMcePluginBase } from '../components/input-tiny-mce/tiny-mce-plugin.js';
import type { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal';
import { UMB_MEDIA_TREE_PICKER_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import type { UMB_CURRENT_USER_CONTEXT, UmbCurrentUserModel } from '@umbraco-cms/backoffice/current-user';
import type { RawEditorOptions } from '@umbraco-cms/backoffice/external/tinymce';
import { UmbTemporaryFileRepository } from '@umbraco-cms/backoffice/temporary-file';
import { UmbId } from '@umbraco-cms/backoffice/id';
import { sizeImageInEditor, uploadBlobImages } from '@umbraco-cms/backoffice/media';
import { UmbMediaDetailRepository, sizeImageInEditor, uploadBlobImages } from '@umbraco-cms/backoffice/media';
interface MediaPickerTargetData {
altText?: string;
@@ -27,13 +27,20 @@ interface MediaPickerResultData {
export default class UmbTinyMceMediaPickerPlugin extends UmbTinyMcePluginBase {
#currentUser?: UmbCurrentUserModel;
#currentUserContext?: typeof UMB_CURRENT_USER_CONTEXT.TYPE;
#modalManager?: typeof UMB_MODAL_MANAGER_CONTEXT.TYPE;
#temporaryFileRepository;
#mediaDetailRepository = new UmbMediaDetailRepository(this);
constructor(args: TinyMcePluginArguments) {
super(args);
this.#temporaryFileRepository = new UmbTemporaryFileRepository(args.host);
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => {
this.#modalManager = instance;
});
// TODO => this breaks tests. disabling for now
// will ignore user media start nodes
// this.host.consumeContext(UMB_CURRENT_USER_CONTEXT, (instance) => {
@@ -70,6 +77,8 @@ export default class UmbTinyMceMediaPickerPlugin extends UmbTinyMcePluginBase {
this.editor.on('SetContent', async (e) => {
const content = e.content;
console.log('set content', content);
// Handle images that are pasted in
uploadBlobImages(this.editor, content);
});
@@ -131,10 +140,12 @@ export default class UmbTinyMceMediaPickerPlugin extends UmbTinyMcePluginBase {
*/
// TODO => startNodeId and startNodeIsVirtual do not exist on ContentTreeItemResponseModel
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
const modalHandler = modalManager.open(this, UMB_MEDIA_TREE_PICKER_MODAL, {
const modalHandler = this.#modalManager?.open(this, UMB_MEDIA_TREE_PICKER_MODAL, {
data: {
multiple: false,
hideTreeRoot: true,
//startNodeId,
//startNodeIsVirtual,
},
@@ -145,31 +156,48 @@ export default class UmbTinyMceMediaPickerPlugin extends UmbTinyMcePluginBase {
if (!modalHandler) return;
const { selection } = await modalHandler.onSubmit();
if (!selection.length) return;
const { selection } = await modalHandler.onSubmit().catch(() => ({ selection: undefined }));
if (!selection || !selection.length) return;
this.#insertInEditor(selection[0]);
this.#showMediaCaptionAltText(selection[0]);
this.editor.dispatch('Change');
}
// TODO => mediaPicker returns a UDI, so need to fetch it. Wait for backend CLI before implementing
async #insertInEditor(img: any) {
if (!img) return;
async #showMediaCaptionAltText(mediaUnique: string | null) {
if (!mediaUnique) return;
const modalHandler = this.#modalManager?.open(this, UMB_MEDIA_CAPTION_ALT_TEXT_MODAL, { data: { mediaUnique } });
await modalHandler?.onSubmit().catch(() => undefined);
const mediaData = modalHandler?.getValue();
const media: MediaPickerTargetData = {
altText: mediaData?.altText,
caption: mediaData?.caption,
url: mediaData?.url,
udi: mediaUnique,
};
this.#insertInEditor(media);
}
async #insertInEditor(media: MediaPickerTargetData) {
if (!media) return;
// We need to create a NEW DOM <img> element to insert
// setting an attribute of ID to __mcenew, so we can gather a reference to the node, to be able to update its size accordingly to the size of the image.
const data: MediaPickerResultData = {
alt: img.altText || '',
src: img.url ? img.url : 'nothing.jpg',
const img: MediaPickerResultData = {
alt: media.altText,
src: media.url ? media.url : 'nothing.jpg',
id: '__mcenew',
'data-udi': img.udi,
'data-caption': img.caption,
'data-udi': media.udi,
'data-caption': media.caption,
};
const newImage = this.editor.dom.createHTML('img', data as Record<string, string | null>);
const newImage = this.editor.dom.createHTML('img', img as Record<string, string | null>);
const parentElement = this.editor.selection.getNode().parentElement;
if (img.caption && parentElement) {
const figCaption = this.editor.dom.createHTML('figcaption', {}, img.caption);
if (img['data-caption'] && parentElement) {
const figCaption = this.editor.dom.createHTML('figcaption', {}, img['data-caption']);
const combined = newImage + figCaption;
if (parentElement.nodeName !== 'FIGURE') {
@@ -190,13 +218,14 @@ export default class UmbTinyMceMediaPickerPlugin extends UmbTinyMcePluginBase {
// Using settimeout to wait for a DoM-render, so we can find the new element by ID.
setTimeout(() => {
const imgElm = this.editor.dom.get('__mcenew') as HTMLImageElement;
console.log('timeout?', imgElm);
if (!imgElm) return;
this.editor.dom.setAttrib(imgElm, 'id', null);
// When image is loaded we are ready to call sizeImageInEditor.
const onImageLoaded = () => {
sizeImageInEditor(this.editor, imgElm, img.url);
sizeImageInEditor(this.editor, imgElm, img.src);
this.editor.dispatch('Change');
};