Visual customization and tune system for @editorjs/image providing borders, backgrounds, stretching, captions, and custom actions with full UI state management.
Core tune system providing standard image customization options with toggle functionality and visual state management.
static get tunes(): Array<ActionConfig> {
return [
{
name: 'withBorder',
icon: IconAddBorder,
title: 'With border',
toggle: true,
},
{
name: 'stretched',
icon: IconStretch,
title: 'Stretch image',
toggle: true,
},
{
name: 'withBackground',
icon: IconAddBackground,
title: 'With background',
toggle: true,
},
];
}
interface ActionConfig {
/** Unique identifier for the action/tune */
name: string;
/** SVG icon string to display in settings menu */
icon: string;
/** Human-readable title for the action/tune */
title: string;
/** Whether this action is a toggle button (true) or single-action button (false) */
toggle?: boolean;
/** Custom function to execute when action is triggered, receives action name as parameter */
action?: (actionName: string) => void;
}Usage Examples:
// Built-in tunes are automatically available
// Users can toggle these options in the settings menu:
// 1. Border tune - adds border around image
// 2. Stretch tune - stretches image to full container width
// 3. Background tune - adds background color behind image
// Data structure includes tune states
interface ImageToolData {
withBorder: boolean; // Border tune state
stretched: boolean; // Stretch tune state
withBackground: boolean; // Background tune state
caption: string;
file: { url: string };
}Dynamic settings menu generation with tune activation handling and state management.
/**
* Generate configuration for block tunes settings menu
* Combines built-in tunes with custom actions
* @returns Configuration for Editor.js tune menu
*/
renderSettings(): TunesMenuConfig;
interface TunesMenuConfig {
icon: string;
label: string;
name: string;
toggle?: boolean;
isActive: boolean;
onActivate: () => void;
}Usage Examples:
// Settings menu automatically includes:
class ImageTool {
renderSettings(): TunesMenuConfig {
const allTunes = [
...ImageTool.tunes, // Built-in tunes
...(this.config.actions || []) // Custom actions
];
// Filter based on feature configuration
const availableTunes = allTunes.filter(tune => {
return isFeatureEnabled(tune.name);
});
return availableTunes.map(tune => ({
icon: tune.icon,
label: this.api.i18n.t(tune.title),
name: tune.name,
toggle: tune.toggle,
isActive: this.getTuneState(tune.name),
onActivate: () => this.handleTuneToggle(tune)
}));
}
}Comprehensive caption system with optional toggle functionality and placeholder customization.
interface CaptionConfig {
captionPlaceholder?: string;
features?: {
caption?: boolean | 'optional';
};
}
/**
* Handle caption tune toggling and state management
* @param tuneName - Name of the tune being toggled
* @param state - New state for the tune
*/
private tuneToggled(tuneName: keyof ImageToolData, state: boolean): void;Usage Examples:
// Always enabled captions (default)
const editor = new EditorJS({
tools: {
image: {
class: ImageTool,
config: {
endpoints: { /* ... */ },
captionPlaceholder: 'Enter image description...'
}
}
}
});
// Optional captions (user can toggle)
const editor = new EditorJS({
tools: {
image: {
class: ImageTool,
config: {
endpoints: { /* ... */ },
features: {
caption: 'optional' // Adds caption toggle in settings
},
captionPlaceholder: 'Add a caption...'
}
}
}
});
// Disabled captions
const editor = new EditorJS({
tools: {
image: {
class: ImageTool,
config: {
endpoints: { /* ... */ },
features: {
caption: false // No caption functionality
}
}
}
}
});Extensible action system allowing addition of custom tunes with custom behavior and UI integration.
interface CustomActionConfig extends ActionConfig {
name: string;
icon: string;
title: string;
toggle?: boolean;
action?: (actionName: string) => void;
}
interface ImageConfig {
actions?: ActionConfig[];
}Usage Examples:
// Adding custom actions
const editor = new EditorJS({
tools: {
image: {
class: ImageTool,
config: {
endpoints: { /* ... */ },
actions: [
{
name: 'rotate',
icon: `<svg viewBox="0 0 24 24">
<path d="M7.11 8.53L5.7 7.11C4.8 8.27 4.24 9.61 4.07 11h2.02c.14-.87.49-1.72 1.02-2.47zM6.09 13H4.07c.17 1.39.72 2.73 1.62 3.89l1.41-1.42c-.52-.75-.87-1.59-1.01-2.47zm1.01 5.32c1.16.9 2.51 1.44 3.9 1.61V17.9c-.87-.15-1.71-.49-2.46-1.03L7.1 18.32zM13 4.07V1L8.45 5.55 13 10V6.09c2.84.48 5 2.94 5 5.91s-2.16 5.43-5 5.91v2.02c3.95-.49 7-3.85 7-7.93s-3.05-7.44-7-7.93z"/>
</svg>`,
title: 'Rotate Image',
action: (actionName) => {
// Custom rotation logic
const imageEl = document.querySelector('.image-tool__image-picture');
if (imageEl) {
const currentRotation = imageEl.dataset.rotation || '0';
const newRotation = (parseInt(currentRotation) + 90) % 360;
imageEl.style.transform = `rotate(${newRotation}deg)`;
imageEl.dataset.rotation = newRotation.toString();
}
}
},
{
name: 'filters',
icon: `<svg viewBox="0 0 24 24">
<path d="M9.5,3C13.09,3 16,5.91 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16C5.91,16 3,13.09 3,9.5C3,5.91 5.91,3 9.5,3M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z"/>
</svg>`,
title: 'Apply Filters',
toggle: true,
action: (actionName) => {
// Toggle filter modal or panel
openFilterPanel();
}
},
{
name: 'alt-text',
icon: `<svg viewBox="0 0 24 24">
<path d="M9,7H11L13,15H11L10.5,13H7.5L7,15H5L7,7M8,9L7.8,11H9.2L9,9M14,17V15H21V17H14M14,13V11H21V13H14M14,9V7H21V9H14Z"/>
</svg>`,
title: 'Add Alt Text',
action: (actionName) => {
// Open alt text input dialog
const altText = prompt('Enter alt text for accessibility:');
if (altText) {
const imageEl = document.querySelector('.image-tool__image-picture');
if (imageEl) {
imageEl.setAttribute('alt', altText);
}
}
}
}
]
}
}
}
});Granular control over available features and tunes with configuration-based enabling/disabling.
interface FeaturesConfig {
/** Enable/disable background tune (default: true) */
background?: boolean;
/** Enable/disable border tune (default: true) */
border?: boolean;
/** Enable/disable caption: true=always visible, false=disabled, 'optional'=user toggleable (default: true) */
caption?: boolean | 'optional';
/** Enable/disable stretch tune (default: true) */
stretch?: boolean;
}
/**
* Generate settings menu filtering tunes based on feature configuration
* Combines built-in tunes with custom actions and applies feature filters
* @returns Configuration for Editor.js tune menu
*/
renderSettings(): TunesMenuConfig;Usage Examples:
// Selective feature enabling
const editor = new EditorJS({
tools: {
image: {
class: ImageTool,
config: {
endpoints: { /* ... */ },
features: {
border: true, // Enable border tune
background: false, // Disable background tune
stretch: true, // Enable stretch tune
caption: 'optional' // Make caption optional
}
}
}
}
});
// Minimal feature set
const editor = new EditorJS({
tools: {
image: {
class: ImageTool,
config: {
endpoints: { /* ... */ },
features: {
border: false,
background: false,
stretch: false,
caption: false
}
}
}
}
});
// Maximum features (default behavior)
const editor = new EditorJS({
tools: {
image: {
class: ImageTool,
config: {
endpoints: { /* ... */ }
// All features enabled by default
}
}
}
});Comprehensive UI state management system handling visual representation of tune states and user interactions.
/**
* Apply visual representation of activated tune
* @param tuneName - Name of the tune to apply
* @param status - Enable or disable state
*/
applyTune(tuneName: string, status: boolean): void;
/**
* Set tune state and update UI accordingly
* @param tuneName - Name of the tune to set
* @param value - New state value
*/
private setTune(tuneName: keyof ImageToolData, value: boolean): void;
enum UiState {
Empty = 'empty',
Uploading = 'uploading',
Filled = 'filled'
}Usage Examples:
// UI classes applied automatically based on tune states:
// Border tune active
// .image-tool--withBorder
// Stretched tune active
// .image-tool--stretched
// Background tune active
// .image-tool--withBackground
// Caption tune active
// .image-tool--caption
// Multiple tunes can be active simultaneously
// .image-tool--withBorder.image-tool--stretched.image-tool--withBackground
// CSS styling examples
.image-tool--withBorder .image-tool__image-picture {
border: 2px solid #e6e9ec;
border-radius: 3px;
}
.image-tool--withBackground .image-tool__image {
background: #f8f9fa;
padding: 15px;
}
.image-tool--stretched {
width: 100vw;
position: relative;
left: 50%;
transform: translateX(-50%);
}
.image-tool--caption .image-tool__caption {
display: block;
}Comprehensive data state management preserving tune states and handling data validation with type safety.
interface ImageToolData<Actions = {}, AdditionalFileData = {}> {
caption: string;
withBorder: boolean;
withBackground: boolean;
stretched: boolean;
file: {
url: string;
} & AdditionalFileData;
} & (Actions extends Record<string, boolean> ? Actions : {});
/**
* Save current block data including all tune states
* @returns Complete image tool data
*/
save(): ImageToolData;
/**
* Validate saved data ensuring required fields are present
* @param savedData - Data to validate
* @returns Validation result
*/
validate(savedData: ImageToolData): boolean;Usage Examples:
// Saved data structure includes all tune states
{
"caption": "Beautiful sunset over the mountains",
"withBorder": true,
"withBackground": false,
"stretched": true,
"file": {
"url": "https://cdn.example.com/sunset.jpg",
"dimensions": {
"width": 1920,
"height": 1080
}
}
}
// Data restoration automatically applies tune states
class ImageTool {
constructor({ data }) {
// Restore tune states from saved data
this.data = {
caption: data.caption || '',
withBorder: data.withBorder || false,
withBackground: data.withBackground || false,
stretched: data.stretched || false,
file: data.file || { url: '' }
};
// Apply UI states based on data
this.applyTunesFromData();
}
}