Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
29 changes: 24 additions & 5 deletions dev/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,43 @@
</head>
<body>
<div id="editorjs"></div>
<script src="https://cdn.jsdelivr.net/npm/@editorjs/editorjs@latest/dist/editor.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@editorjs/editorjs@latest"></script>
<script type="module">
import ImageTool from "../src/index.ts"
import ImageTool from "../src/index.ts";
const editor = new EditorJS({
holder: "editorjs",
data: {
time: 1700475383740,
blocks: [
{
id: "hZAjSnqYMX",
type: "image",
data: {
file: {
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",
},
withBorder: false,
withBackground: false,
stretched: false,
caption: "kimitsu no yayiba",
},
},
],
},

tools: {
code: {
image: {
class: ImageTool,
config: {
endpoints: {
byFile: "http://localhost:8008/uploadFile",
byUrl: "http://localhost:8008/fetchUrl",
},
features: {
// caption: false,
caption: "optional",
border: false,
background: false,
stretch: false,
stretch: true,
},
},
},
Expand Down
32 changes: 16 additions & 16 deletions dev/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const crypto = require('crypto');
const SERVER_PORT = 8008;

class ServerExample {
constructor({port, fieldName}) {
constructor({ port, fieldName }) {
this.uploadDir = __dirname + '/\.tmp';
this.fieldName = fieldName;
this.server = http.createServer((req, res) => {
Expand All @@ -46,7 +46,7 @@ class ServerExample {
onRequest(request, response) {
this.allowCors(response);

const {method, url} = request;
const { method, url } = request;

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

Expand Down Expand Up @@ -97,7 +97,7 @@ class ServerExample {
response.end(JSON.stringify({ success: 0, message: 'File not found' }));
return;
}

const fileStream = fs.createReadStream(filePath);
response.writeHead(200, { 'Content-Type': 'image/png' });
// Pipe the file stream to the response, sending the file content directly to the client
Expand All @@ -114,9 +114,8 @@ class ServerExample {
let responseJson = {
success: 0
};

this.getForm(request)
.then(({files}) => {
.then(({ files }) => {
let image = files[this.fieldName][0] || {};

responseJson.success = 1;
Expand All @@ -130,7 +129,7 @@ class ServerExample {
console.log('Uploading error', error);
})
.finally(() => {
response.writeHead(200, {'Content-Type': 'application/json'});
response.writeHead(200, { 'Content-Type': 'application/json' });
response.end(JSON.stringify(responseJson));
});
}
Expand All @@ -144,29 +143,30 @@ class ServerExample {
let responseJson = {
success: 0
};

this.getForm(request)
.then(({files, fields}) => {
.then(({ files, fields }) => {
let url = fields.url;

const results = url.match(/https?:\/\/\S+\.(gif|jpe?g|tiff|png|svg|webp)(\?[a-z0-9=]*)?$/i);
const extension = results ? results[1] : '.png';

let filename = this.uploadDir + '/' + this.md5(url) + `.${extension}`;
const extension = results ? results[1] : 'png';

const filename = `${this.md5(url)}.${extension}`;
const filePath = `${this.uploadDir}/${filename}`;
const publicUrl = `http://localhost:${SERVER_PORT}/image/${filename}`; // Public URL for serving the image

return this.downloadImage(url, filename)
.then((path) => {
return this.downloadImage(url, filePath)
.then(() => {
responseJson.success = 1;
responseJson.file = {
url: path
url: publicUrl, // Return the public URL instead of the file path
};
});
})
.catch((error) => {
console.log('Uploading error', error);
})
.finally(() => {
response.writeHead(200, {'Content-Type': 'application/json'});
response.writeHead(200, { 'Content-Type': 'application/json' });
response.end(JSON.stringify(responseJson));
});
}
Expand All @@ -191,7 +191,7 @@ class ServerExample {
} else {
console.log('fields', fields);
console.log('files', files);
resolve({files, fields});
resolve({ files, fields });
}
});
});
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@editorjs/image",
"version": "2.10.1",
"version": "2.10.2",
"keywords": [
"codex editor",
"image",
Expand Down
17 changes: 14 additions & 3 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
border-radius: 3px;
overflow: hidden;
margin-bottom: 10px;
padding-bottom: 0;

&-picture {
max-width: 100%;
Expand Down Expand Up @@ -44,7 +45,11 @@
}

&__caption {
display: none;
visibility: hidden;
position: absolute;
bottom: 0;
left: 0;
margin-bottom: 10px;

&[contentEditable="true"][data-placeholder]::before {
position: absolute !important;
Expand All @@ -68,13 +73,17 @@
&--empty {
^&__image {
display: none;

&-preloader {
display: none;
}
}
}

&--empty,
&--uploading {
^&__caption {
display: none !important;
visibility: hidden !important;
}
}

Expand Down Expand Up @@ -151,8 +160,10 @@

&--caption {
^&__caption {
display: block;
visibility: visible;
}

padding-bottom: 50px
}
}

Expand Down
67 changes: 55 additions & 12 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,14 @@ export default class ImageTool implements BlockTool {
*/
private _data: ImageToolData;

/**
* Caption enabled state
* Null when user has not toggled the caption tune
* True when user has toggled the caption tune
* False when user has toggled the caption tune
*/
private isCaptionEnabled: boolean | null = null;

/**
* @param tool - tool properties got from editor.js
* @param tool.data - previously saved data
Expand Down Expand Up @@ -192,10 +200,10 @@ export default class ImageTool implements BlockTool {
*/
public render(): HTMLDivElement {
if (this.config.features?.caption === true || this.config.features?.caption === undefined || (this.config.features?.caption === 'optional' && this.data.caption)) {
this.ui.applyTune('caption', true);
this.isCaptionEnabled = true;
}

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

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

/**
* Check if the tune is active
* @param tune - tune to check
*/
const isActive = (tune: ActionConfig): boolean => {
let currentState = this.data[tune.name as keyof ImageToolData] as boolean;

if (tune.name === 'caption') {
currentState = this.isCaptionEnabled ?? currentState;
}

return currentState;
};

return availableTunes.map(tune => ({
icon: tune.icon,
label: this.api.i18n.t(tune.title),
name: tune.name,
toggle: tune.toggle,
isActive: this.data[tune.name as keyof ImageToolData] as boolean,
isActive: isActive(tune),
onActivate: () => {
/** If it'a user defined tune, execute it's callback stored in action property */
if (typeof tune.action === 'function') {
tune.action(tune.name);

return;
}
this.tuneToggled(tune.name as keyof ImageToolData);
let newState = !isActive(tune);

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

this.tuneToggled(tune.name as keyof ImageToolData, newState);
},
}));
}
Expand Down Expand Up @@ -367,6 +400,10 @@ export default class ImageTool implements BlockTool {

this.setTune(tune as keyof ImageToolData, value);
});

if (data.caption) {
this.setTune('caption', true);
}
}

/**
Expand Down Expand Up @@ -417,15 +454,21 @@ export default class ImageTool implements BlockTool {
/**
* Callback fired when Block Tune is activated
* @param tuneName - tune that has been clicked
* @param state - new state
*/
private tuneToggled(tuneName: keyof ImageToolData): void {
// inverse tune state
this.setTune(tuneName, !(this._data[tuneName] as boolean));

// reset caption on toggle
if (tuneName === 'caption' && !this._data[tuneName]) {
this._data.caption = '';
this.ui.fillCaption('');
private tuneToggled(tuneName: keyof ImageToolData, state: boolean): void {
if (tuneName === 'caption') {
this.ui.applyTune(tuneName, state);

if (state == false) {
this._data.caption = '';
this.ui.fillCaption('');
}
} else {
/**
* Inverse tune state
*/
this.setTune(tuneName, state);
}
}

Expand Down
41 changes: 19 additions & 22 deletions src/ui.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { IconPicture } from '@codexteam/icons';
import { make } from './utils/dom';
import type { API } from '@editorjs/editorjs';
import type { ImageToolData, ImageConfig } from './types/types';
import type { ImageConfig } from './types/types';

/**
* Enumeration representing the different states of the UI.
*/
enum UiState {
export enum UiState {
/**
* The UI is in an empty state, with no image loaded or being uploaded.
* The UI is in an empty state, with no image loaded or being selected.
*/
Empty = 'empty',

Expand Down Expand Up @@ -163,14 +163,9 @@ export default class Ui {

/**
* Renders tool UI
* @param toolData - saved tool data
*/
public render(toolData: ImageToolData): HTMLElement {
if (toolData.file === undefined || Object.keys(toolData.file).length === 0) {
this.toggleStatus(UiState.Empty);
} else {
this.toggleStatus(UiState.Uploading);
}
public render(): HTMLElement {
this.toggleStatus(UiState.Empty);

return this.nodes.wrapper;
}
Expand Down Expand Up @@ -264,6 +259,20 @@ export default class Ui {
}
}

/**
* Changes UI status
* @param status - see {@link Ui.status} constants
*/
public toggleStatus(status: UiState): void {
for (const statusType in UiState) {
if (Object.prototype.hasOwnProperty.call(UiState, statusType)) {
const state = UiState[statusType as keyof typeof UiState];

this.nodes.wrapper.classList.toggle(`${this.CSS.wrapper}--${state}`, state === status);
}
}
}

/**
* CSS classes
*/
Expand Down Expand Up @@ -299,16 +308,4 @@ export default class Ui {

return button;
}

/**
* Changes UI status
* @param status - see {@link Ui.status} constants
*/
private toggleStatus(status: UiState): void {
for (const statusType in UiState) {
if (Object.prototype.hasOwnProperty.call(UiState, statusType)) {
this.nodes.wrapper.classList.toggle(`${this.CSS.wrapper}--${UiState[statusType as keyof typeof UiState]}`, status === UiState[statusType as keyof typeof UiState]);
}
}
}
}
Loading