Skip to content

Commit 2cddffc

Browse files
authored
Merge pull request #278 from editor-js/fix/patch-2.10.2
fix: resolve caption state handling and CSS issues
2 parents c8236e5 + b40381f commit 2cddffc

File tree

6 files changed

+129
-59
lines changed

6 files changed

+129
-59
lines changed

dev/index.html

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,43 @@
77
</head>
88
<body>
99
<div id="editorjs"></div>
10-
<script src="https://cdn.jsdelivr.net/npm/@editorjs/editorjs@latest/dist/editor.js"></script>
10+
<script src="https://cdn.jsdelivr.net/npm/@editorjs/editorjs@latest"></script>
1111
<script type="module">
12-
import ImageTool from "../src/index.ts"
12+
import ImageTool from "../src/index.ts";
1313
const editor = new EditorJS({
1414
holder: "editorjs",
15+
data: {
16+
time: 1700475383740,
17+
blocks: [
18+
{
19+
id: "hZAjSnqYMX",
20+
type: "image",
21+
data: {
22+
file: {
23+
url: "https://images.unsplash.com/photo-1607604276583-eef5d076aa5f?q=80&w=1974&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
24+
},
25+
withBorder: false,
26+
withBackground: false,
27+
stretched: false,
28+
caption: "kimitsu no yayiba",
29+
},
30+
},
31+
],
32+
},
33+
1534
tools: {
16-
code: {
35+
image: {
1736
class: ImageTool,
1837
config: {
1938
endpoints: {
2039
byFile: "http://localhost:8008/uploadFile",
2140
byUrl: "http://localhost:8008/fetchUrl",
2241
},
2342
features: {
24-
// caption: false,
43+
caption: "optional",
2544
border: false,
2645
background: false,
27-
stretch: false,
46+
stretch: true,
2847
},
2948
},
3049
},

dev/server.js

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ const crypto = require('crypto');
2222
const SERVER_PORT = 8008;
2323

2424
class ServerExample {
25-
constructor({port, fieldName}) {
25+
constructor({ port, fieldName }) {
2626
this.uploadDir = __dirname + '/\.tmp';
2727
this.fieldName = fieldName;
2828
this.server = http.createServer((req, res) => {
@@ -46,7 +46,7 @@ class ServerExample {
4646
onRequest(request, response) {
4747
this.allowCors(response);
4848

49-
const {method, url} = request;
49+
const { method, url } = request;
5050

5151
console.log('Got request on the ', url);
5252

@@ -97,7 +97,7 @@ class ServerExample {
9797
response.end(JSON.stringify({ success: 0, message: 'File not found' }));
9898
return;
9999
}
100-
100+
101101
const fileStream = fs.createReadStream(filePath);
102102
response.writeHead(200, { 'Content-Type': 'image/png' });
103103
// Pipe the file stream to the response, sending the file content directly to the client
@@ -114,9 +114,8 @@ class ServerExample {
114114
let responseJson = {
115115
success: 0
116116
};
117-
118117
this.getForm(request)
119-
.then(({files}) => {
118+
.then(({ files }) => {
120119
let image = files[this.fieldName][0] || {};
121120

122121
responseJson.success = 1;
@@ -130,7 +129,7 @@ class ServerExample {
130129
console.log('Uploading error', error);
131130
})
132131
.finally(() => {
133-
response.writeHead(200, {'Content-Type': 'application/json'});
132+
response.writeHead(200, { 'Content-Type': 'application/json' });
134133
response.end(JSON.stringify(responseJson));
135134
});
136135
}
@@ -144,29 +143,30 @@ class ServerExample {
144143
let responseJson = {
145144
success: 0
146145
};
147-
148146
this.getForm(request)
149-
.then(({files, fields}) => {
147+
.then(({ files, fields }) => {
150148
let url = fields.url;
151149

152150
const results = url.match(/https?:\/\/\S+\.(gif|jpe?g|tiff|png|svg|webp)(\?[a-z0-9=]*)?$/i);
153-
const extension = results ? results[1] : '.png';
154-
155-
let filename = this.uploadDir + '/' + this.md5(url) + `.${extension}`;
151+
const extension = results ? results[1] : 'png';
152+
153+
const filename = `${this.md5(url)}.${extension}`;
154+
const filePath = `${this.uploadDir}/${filename}`;
155+
const publicUrl = `http://localhost:${SERVER_PORT}/image/${filename}`; // Public URL for serving the image
156156

157-
return this.downloadImage(url, filename)
158-
.then((path) => {
157+
return this.downloadImage(url, filePath)
158+
.then(() => {
159159
responseJson.success = 1;
160160
responseJson.file = {
161-
url: path
161+
url: publicUrl, // Return the public URL instead of the file path
162162
};
163163
});
164164
})
165165
.catch((error) => {
166166
console.log('Uploading error', error);
167167
})
168168
.finally(() => {
169-
response.writeHead(200, {'Content-Type': 'application/json'});
169+
response.writeHead(200, { 'Content-Type': 'application/json' });
170170
response.end(JSON.stringify(responseJson));
171171
});
172172
}
@@ -191,7 +191,7 @@ class ServerExample {
191191
} else {
192192
console.log('fields', fields);
193193
console.log('files', files);
194-
resolve({files, fields});
194+
resolve({ files, fields });
195195
}
196196
});
197197
});

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@editorjs/image",
3-
"version": "2.10.1",
3+
"version": "2.10.2",
44
"keywords": [
55
"codex editor",
66
"image",

src/index.css

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
border-radius: 3px;
88
overflow: hidden;
99
margin-bottom: 10px;
10+
padding-bottom: 0;
1011

1112
&-picture {
1213
max-width: 100%;
@@ -44,7 +45,11 @@
4445
}
4546

4647
&__caption {
47-
display: none;
48+
visibility: hidden;
49+
position: absolute;
50+
bottom: 0;
51+
left: 0;
52+
margin-bottom: 10px;
4853

4954
&[contentEditable="true"][data-placeholder]::before {
5055
position: absolute !important;
@@ -68,13 +73,17 @@
6873
&--empty {
6974
^&__image {
7075
display: none;
76+
77+
&-preloader {
78+
display: none;
79+
}
7180
}
7281
}
7382

7483
&--empty,
7584
&--uploading {
7685
^&__caption {
77-
display: none !important;
86+
visibility: hidden !important;
7887
}
7988
}
8089

@@ -151,8 +160,10 @@
151160

152161
&--caption {
153162
^&__caption {
154-
display: block;
163+
visibility: visible;
155164
}
165+
166+
padding-bottom: 50px
156167
}
157168
}
158169

src/index.ts

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,14 @@ export default class ImageTool implements BlockTool {
7474
*/
7575
private _data: ImageToolData;
7676

77+
/**
78+
* Caption enabled state
79+
* Null when user has not toggled the caption tune
80+
* True when user has toggled the caption tune
81+
* False when user has toggled the caption tune
82+
*/
83+
private isCaptionEnabled: boolean | null = null;
84+
7785
/**
7886
* @param tool - tool properties got from editor.js
7987
* @param tool.data - previously saved data
@@ -192,10 +200,10 @@ export default class ImageTool implements BlockTool {
192200
*/
193201
public render(): HTMLDivElement {
194202
if (this.config.features?.caption === true || this.config.features?.caption === undefined || (this.config.features?.caption === 'optional' && this.data.caption)) {
195-
this.ui.applyTune('caption', true);
203+
this.isCaptionEnabled = true;
196204
}
197205

198-
return this.ui.render(this.data) as HTMLDivElement;
206+
return this.ui.render() as HTMLDivElement;
199207
}
200208

201209
/**
@@ -252,20 +260,45 @@ export default class ImageTool implements BlockTool {
252260
return featureKey == null || this.config.features?.[featureKey as keyof FeaturesConfig] !== false;
253261
});
254262

263+
/**
264+
* Check if the tune is active
265+
* @param tune - tune to check
266+
*/
267+
const isActive = (tune: ActionConfig): boolean => {
268+
let currentState = this.data[tune.name as keyof ImageToolData] as boolean;
269+
270+
if (tune.name === 'caption') {
271+
currentState = this.isCaptionEnabled ?? currentState;
272+
}
273+
274+
return currentState;
275+
};
276+
255277
return availableTunes.map(tune => ({
256278
icon: tune.icon,
257279
label: this.api.i18n.t(tune.title),
258280
name: tune.name,
259281
toggle: tune.toggle,
260-
isActive: this.data[tune.name as keyof ImageToolData] as boolean,
282+
isActive: isActive(tune),
261283
onActivate: () => {
262284
/** If it'a user defined tune, execute it's callback stored in action property */
263285
if (typeof tune.action === 'function') {
264286
tune.action(tune.name);
265287

266288
return;
267289
}
268-
this.tuneToggled(tune.name as keyof ImageToolData);
290+
let newState = !isActive(tune);
291+
292+
/**
293+
* For the caption tune, we can't rely on the this._data
294+
* because it can be manualy toggled by user
295+
*/
296+
if (tune.name === 'caption') {
297+
this.isCaptionEnabled = !(this.isCaptionEnabled ?? false);
298+
newState = this.isCaptionEnabled;
299+
}
300+
301+
this.tuneToggled(tune.name as keyof ImageToolData, newState);
269302
},
270303
}));
271304
}
@@ -367,6 +400,10 @@ export default class ImageTool implements BlockTool {
367400

368401
this.setTune(tune as keyof ImageToolData, value);
369402
});
403+
404+
if (data.caption) {
405+
this.setTune('caption', true);
406+
}
370407
}
371408

372409
/**
@@ -417,15 +454,21 @@ export default class ImageTool implements BlockTool {
417454
/**
418455
* Callback fired when Block Tune is activated
419456
* @param tuneName - tune that has been clicked
457+
* @param state - new state
420458
*/
421-
private tuneToggled(tuneName: keyof ImageToolData): void {
422-
// inverse tune state
423-
this.setTune(tuneName, !(this._data[tuneName] as boolean));
424-
425-
// reset caption on toggle
426-
if (tuneName === 'caption' && !this._data[tuneName]) {
427-
this._data.caption = '';
428-
this.ui.fillCaption('');
459+
private tuneToggled(tuneName: keyof ImageToolData, state: boolean): void {
460+
if (tuneName === 'caption') {
461+
this.ui.applyTune(tuneName, state);
462+
463+
if (state == false) {
464+
this._data.caption = '';
465+
this.ui.fillCaption('');
466+
}
467+
} else {
468+
/**
469+
* Inverse tune state
470+
*/
471+
this.setTune(tuneName, state);
429472
}
430473
}
431474

src/ui.ts

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import { IconPicture } from '@codexteam/icons';
22
import { make } from './utils/dom';
33
import type { API } from '@editorjs/editorjs';
4-
import type { ImageToolData, ImageConfig } from './types/types';
4+
import type { ImageConfig } from './types/types';
55

66
/**
77
* Enumeration representing the different states of the UI.
88
*/
9-
enum UiState {
9+
export enum UiState {
1010
/**
11-
* The UI is in an empty state, with no image loaded or being uploaded.
11+
* The UI is in an empty state, with no image loaded or being selected.
1212
*/
1313
Empty = 'empty',
1414

@@ -163,14 +163,9 @@ export default class Ui {
163163

164164
/**
165165
* Renders tool UI
166-
* @param toolData - saved tool data
167166
*/
168-
public render(toolData: ImageToolData): HTMLElement {
169-
if (toolData.file === undefined || Object.keys(toolData.file).length === 0) {
170-
this.toggleStatus(UiState.Empty);
171-
} else {
172-
this.toggleStatus(UiState.Uploading);
173-
}
167+
public render(): HTMLElement {
168+
this.toggleStatus(UiState.Empty);
174169

175170
return this.nodes.wrapper;
176171
}
@@ -264,6 +259,20 @@ export default class Ui {
264259
}
265260
}
266261

262+
/**
263+
* Changes UI status
264+
* @param status - see {@link Ui.status} constants
265+
*/
266+
public toggleStatus(status: UiState): void {
267+
for (const statusType in UiState) {
268+
if (Object.prototype.hasOwnProperty.call(UiState, statusType)) {
269+
const state = UiState[statusType as keyof typeof UiState];
270+
271+
this.nodes.wrapper.classList.toggle(`${this.CSS.wrapper}--${state}`, state === status);
272+
}
273+
}
274+
}
275+
267276
/**
268277
* CSS classes
269278
*/
@@ -299,16 +308,4 @@ export default class Ui {
299308

300309
return button;
301310
}
302-
303-
/**
304-
* Changes UI status
305-
* @param status - see {@link Ui.status} constants
306-
*/
307-
private toggleStatus(status: UiState): void {
308-
for (const statusType in UiState) {
309-
if (Object.prototype.hasOwnProperty.call(UiState, statusType)) {
310-
this.nodes.wrapper.classList.toggle(`${this.CSS.wrapper}--${UiState[statusType as keyof typeof UiState]}`, status === UiState[statusType as keyof typeof UiState]);
311-
}
312-
}
313-
}
314311
}

0 commit comments

Comments
 (0)