Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
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: 2 additions & 0 deletions dev/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
withBackground: false,
stretched: false,
caption: "kimitsu no yayiba",
alt: "Picture of anime characters"
},
},
],
Expand All @@ -44,6 +45,7 @@
border: false,
background: false,
stretch: true,
alt: "optional",
},
},
},
Expand Down
29 changes: 27 additions & 2 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@
}
}

&__caption {
&__caption,
&__alt {
visibility: hidden;
position: absolute;
bottom: 0;
Expand Down Expand Up @@ -85,6 +86,10 @@
^&__caption {
visibility: hidden !important;
}

^&__alt {
visibility: hidden !important;
}
}

.cdx-button {
Expand Down Expand Up @@ -163,7 +168,27 @@
visibility: visible;
}

padding-bottom: 50px
padding-bottom: 50px;
}

&--alt {
^&__alt {
visibility: visible;
}

padding-bottom: 50px;
}

&--caption&--alt {
^&__caption {
bottom: 40px;
}

^&__alt {
bottom: 0px;
}

padding-bottom: 80px;
}
}

Expand Down
61 changes: 59 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ export default class ImageTool implements BlockTool {
*/
private isCaptionEnabled: boolean | null = null;

/**
* Alt text enabled state
*/
private isAltEnabled: boolean = false;

/**
* @param tool - tool properties got from editor.js
* @param tool.data - previously saved data
Expand All @@ -104,6 +109,7 @@ export default class ImageTool implements BlockTool {
field: config.field,
types: config.types,
captionPlaceholder: this.api.i18n.t(config.captionPlaceholder ?? 'Caption'),
altPlaceholder: this.api.i18n.t(config.altPlaceholder ?? 'Alt text'),
buttonContent: config.buttonContent,
uploader: config.uploader,
actions: config.actions,
Expand Down Expand Up @@ -140,6 +146,7 @@ export default class ImageTool implements BlockTool {
*/
this._data = {
caption: '',
alt: '',
withBorder: false,
withBackground: false,
stretched: false,
Expand Down Expand Up @@ -204,6 +211,11 @@ export default class ImageTool implements BlockTool {
this.ui.applyTune('caption', true);
}

if (this.config.features?.alt === true || (this.config.features?.alt === 'optional' && this.data.alt)) {
this.isAltEnabled = true;
this.ui.applyTune('alt', true);
}

return this.ui.render() as HTMLDivElement;
}

Expand All @@ -221,8 +233,10 @@ export default class ImageTool implements BlockTool {
*/
public save(): ImageToolData {
const caption = this.ui.nodes.caption;
const alt = this.ui.nodes.alt;

this._data.caption = caption.innerHTML;
this._data.alt = alt.innerHTML;

return this.data;
}
Expand All @@ -240,13 +254,23 @@ export default class ImageTool implements BlockTool {
background: 'withBackground',
stretch: 'stretched',
caption: 'caption',
alt: 'alt',
};

if (this.config.features?.caption === 'optional') {
tunes.push({
name: 'caption',
name: this.api.i18n.t('caption'),
icon: IconText,
title: this.api.i18n.t('With caption'),
toggle: true,
});
}

if (this.config.features?.alt === 'optional') {
tunes.push({
name: this.api.i18n.t('alt'),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i18n is not needed for name because it is used only for key name in result JSON, not for the UI.

Leave it for title only.

icon: IconText,
title: 'With caption',
title: this.api.i18n.t('With alt text'),
toggle: true,
});
}
Expand All @@ -258,6 +282,10 @@ export default class ImageTool implements BlockTool {
return this.config.features?.caption !== false;
}

if (featureKey === 'alt') {
return this.config.features?.alt !== false;
}

return featureKey == null || this.config.features?.[featureKey as keyof FeaturesConfig] !== false;
});

Expand All @@ -272,6 +300,10 @@ export default class ImageTool implements BlockTool {
currentState = this.isCaptionEnabled ?? currentState;
}

if (tune.name === 'alt') {
currentState = this.isAltEnabled;
}

return currentState;
};

Expand Down Expand Up @@ -299,6 +331,15 @@ export default class ImageTool implements BlockTool {
newState = this.isCaptionEnabled;
}

/**
* For the alt tune, we can't rely on the this._data
* because it can be manualy toggled by user
*/
if (tune.name === 'alt') {
this.isAltEnabled = !this.isAltEnabled;
newState = this.isAltEnabled;
}

this.tuneToggled(tune.name as keyof ImageToolData, newState);
},
}));
Expand Down Expand Up @@ -396,6 +437,9 @@ export default class ImageTool implements BlockTool {
this._data.caption = data.caption || '';
this.ui.fillCaption(this._data.caption);

this._data.alt = data.alt || '';
this.ui.fillAlt(this._data.alt);

ImageTool.tunes.forEach(({ name: tune }) => {
const value = typeof data[tune as keyof ImageToolData] !== 'undefined' ? data[tune as keyof ImageToolData] === true || data[tune as keyof ImageToolData] === 'true' : false;

Expand All @@ -407,6 +451,12 @@ export default class ImageTool implements BlockTool {
} else if (this.config.features?.caption === true) {
this.setTune('caption', true);
}

if (data.alt) {
this.setTune('alt', true);
} else if (this.config.features?.alt === true) {
this.setTune('alt', true);
}
}

/**
Expand Down Expand Up @@ -467,6 +517,13 @@ export default class ImageTool implements BlockTool {
this._data.caption = '';
this.ui.fillCaption('');
}
} else if (tuneName === 'alt') {
this.ui.applyTune(tuneName, state);

if (state == false) {
this._data.alt = '';
this.ui.fillAlt('');
}
} else {
/**
* Inverse tune state
Expand Down
15 changes: 15 additions & 0 deletions src/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ export type ImageToolData<Actions = {}, AdditionalFileData = {}> = {
*/
caption: string;

/**
* Alt text for the image.
*/
alt: string;

/**
* Flag indicating whether the image has a border.
*/
Expand Down Expand Up @@ -117,6 +122,11 @@ export type FeaturesConfig = {
* Can be set to 'optional' to allow users to toggle via block tunes.
*/
caption?: boolean | 'optional';
/**
* Flag to enable/disable alt text.
* Can be set to 'optional' to allow users to toggle via block tunes.
*/
alt?: boolean | 'optional';
/**
* Flag to enable/disable tune - stretched
*/
Expand Down Expand Up @@ -159,6 +169,11 @@ export interface ImageConfig {
*/
captionPlaceholder?: string;

/**
* Placeholder text for the alt text field.
*/
altPlaceholder?: string;

/**
* Additional data to send with requests.
*/
Expand Down
45 changes: 45 additions & 0 deletions src/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ interface Nodes {
* Caption element for the image.
*/
caption: HTMLElement;

/**
* Alt text element for the image.
*/
alt: HTMLElement;
}

/**
Expand Down Expand Up @@ -133,6 +138,9 @@ export default class Ui {
caption: make('div', [this.CSS.input, this.CSS.caption], {
contentEditable: !this.readOnly,
}),
alt: make('div', [this.CSS.input, this.CSS.alt], {
contentEditable: !this.readOnly,
}),
};

/**
Expand All @@ -142,13 +150,22 @@ export default class Ui {
* <image-preloader />
* </image-container>
* <caption />
* <alt />
* <select-file-button />
* </wrapper>
*/
this.nodes.caption.dataset.placeholder = this.config.captionPlaceholder;
this.nodes.alt.dataset.placeholder = this.config.altPlaceholder || this.api.i18n.t('Alt text');

// Add event listener to update alt attribute when alt text changes
this.nodes.alt.addEventListener('input', () => {
this.updateImageAlt(this.nodes.alt.innerHTML);
});

this.nodes.imageContainer.appendChild(this.nodes.imagePreloader);
this.nodes.wrapper.appendChild(this.nodes.imageContainer);
this.nodes.wrapper.appendChild(this.nodes.caption);
this.nodes.wrapper.appendChild(this.nodes.alt);
this.nodes.wrapper.appendChild(this.nodes.fileButton);
}

Expand Down Expand Up @@ -202,6 +219,11 @@ export default class Ui {
src: url,
};

// Add alt attribute for IMG tags
if (tag === 'IMG') {
attributes.alt = this.nodes.alt.textContent || '';
}

/**
* We use eventName variable because IMG and VIDEO tags have different event to be called on source load
* - IMG: load
Expand Down Expand Up @@ -259,6 +281,28 @@ export default class Ui {
}
}

/**
* Shows alt text input
* @param text - alt text content
*/
public fillAlt(text: string): void {
if (this.nodes.alt !== undefined) {
this.nodes.alt.innerHTML = text;
// Update the alt attribute on the image element if it exists
this.updateImageAlt(text);
}
}

/**
* Updates the alt attribute on the image element
* @param altText - alt text to set
*/
public updateImageAlt(altText: string): void {
if (this.nodes.imageEl && this.nodes.imageEl.tagName === 'IMG') {
this.nodes.imageEl.setAttribute('alt', altText);
}
}

/**
* Changes UI status
* @param status - see {@link Ui.status} constants
Expand Down Expand Up @@ -291,6 +335,7 @@ export default class Ui {
imagePreloader: 'image-tool__image-preloader',
imageEl: 'image-tool__image-picture',
caption: 'image-tool__caption',
alt: 'image-tool__alt',
};
};

Expand Down