media caption textalt modal
This commit is contained in:
committed by
Jacob Overgaard
parent
e26d948ab3
commit
943b379a3a
@@ -1 +1,2 @@
|
||||
export * from './components/index.js';
|
||||
export * from './modals/index.js';
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from './media-caption-alt-text/index.js';
|
||||
@@ -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];
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './media-caption-alt-text-modal.element.js';
|
||||
export * from './media-caption-alt-text-modal.token.js';
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
});
|
||||
@@ -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');
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user