Skip to content

Commit e45da06

Browse files
authored
Merge pull request #5 from codex-editor/paste-handlings
2.0.0: Paste handling, additional request params, custom button content
2 parents 24a5fe2 + 3a6e4bc commit e45da06

15 files changed

+1956
-266
lines changed

.eslintrc

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"extends": [
3+
"codex"
4+
]
5+
}

README.md

+47-13
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@ Image Block for the [CodeX Editor](https://ifmo.su/editor).
1111
- Uploading file from the device
1212
- Pasting copied content from the web
1313
- Pasting images by drag-n-drop
14+
- Pasting files and screenshots from Clipboard
1415
- Allows to add border, background
15-
- Allows to stretch image to the container's full-width
16+
- Allows to stretch image to the container's full-width
1617

1718
**Note** This Tool requires server-side implementation for file uploading. See [backend response format](#server-format) for more details.
18-
19+
1920
## Installation
2021

2122
### Install via NPM
@@ -52,29 +53,37 @@ Add a new Tool to the `tools` property of the CodeX Editor initial config.
5253
```javascript
5354
var editor = CodexEditor({
5455
...
55-
56+
5657
tools: {
5758
...
5859
image: {
5960
class: ImageTool,
6061
config: {
61-
url: 'http://localhost:8008/', // Your backend uploader endpoint
62+
endpoints: {
63+
byFile: 'http://localhost:8008/uploadFile', // Your backend file uploader endpoint
64+
byUrl: 'http://localhost:8008/fetchUrl', // Your endpoint that provides uploading by Url
65+
}
6266
}
6367
}
6468
}
65-
69+
6670
...
6771
});
6872
```
6973

70-
## Config Params
74+
## Config Params
75+
76+
Image Tool supports these configuration parameters:
7177

7278
| Field | Type | Description |
7379
| ----- | -------- | ------------------ |
74-
| url | `string` | **Required** Path for file uploading |
80+
| endpoints | `{byFile: string, byUrl: string}` | **Required** Endpoints for file uploading. <br> Contains 2 fields: <br> __byFile__ - for file uploading <br> __byUrl__ - for uploading by URL |
7581
| field | `string` | (default: `image`) Name of uploaded image field in POST request |
7682
| types | `string` | (default: `image/*`) Mime-types of files that can be [accepted with file selection](https://github.com/codex-team/ajax#accept-string).|
83+
| additionalRequestData | `object` | Object with any data you want to send with uploading requests |
84+
| additionalRequestHeaders | `object` | Object with any custom headers which will be added to request. [See example](https://github.com/codex-team/ajax/blob/e5bc2a2391a18574c88b7ecd6508c29974c3e27f/README.md#headers-object) |
7785
| captionPlaceholder | `string` | (default: `Caption`) Placeholder for Caption input |
86+
| buttonContent | `string` | Allows to override HTML content of «Select file» button |
7887

7988
## Tool's settings
8089

@@ -116,10 +125,19 @@ This Tool returns `data` with following format
116125

117126
## Backend response format <a name="server-format"></a>
118127

119-
This Tool works by following scheme:
128+
This Tool works by one of the following schemes:
129+
130+
1. Uploading files from the device
131+
2. Uploading by URL (handle image-like URL's pasting)
132+
3. Uploading by drag-n-drop file
133+
4. Uploading by pasting from Clipboard
134+
135+
### Uploading files from device <a name="from-device"></a>
136+
137+
Scenario:
120138

121139
1. User select file from the device
122-
2. Tool sends it to **your** backend
140+
2. Tool sends it to **your** backend (on `config.endpoint.byFile` route)
123141
3. Your backend should save file and return file data with JSON at specified format.
124142
4. Image tool shows saved image and stores server answer
125143

@@ -140,8 +158,24 @@ Response of your uploader **should** cover following format:
140158

141159
**success** - uploading status. 1 for successful, 0 for failed
142160

143-
**file** - uploaded file data. **Must** contain an `url` field with full public path to the uploaded image.
144-
Also, can contain any additional fields you want to store. For example, width, height, id etc.
145-
All additional fields will be saved at the `file` object of output data.
161+
**file** - uploaded file data. **Must** contain an `url` field with full public path to the uploaded image.
162+
Also, can contain any additional fields you want to store. For example, width, height, id etc.
163+
All additional fields will be saved at the `file` object of output data.
164+
165+
### Uploading by pasted URL
166+
167+
Scenario:
168+
169+
1. User pastes an URL of the image file to the Editor
170+
2. Editor pass pasted string to the Image Tool
171+
3. Tool sends it to **your** backend (on `config.endpoint.byUrl` route) via 'url' POST-parameter
172+
3. Your backend should accept URL, **download and save the original file by passed URL** and return file data with JSON at specified format.
173+
4. Image tool shows saved image and stores server answer
174+
175+
Response of your uploader should be at the same format as described at «[Uploading files from device](#from-device)» section
176+
177+
178+
### Uploading by drag-n-drop or from Clipboard
146179

147-
180+
Your backend will accept file as FormData object in field name, specified by `config.field` (by default, «`image`»).
181+
You should save it and return the same response format as described above.

dev/server.js

+194
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
/**
2+
* Sample HTTP server for accept uploaded images
3+
* [!] Use it only for debugging purposes
4+
*
5+
* How to use [requires Node.js 10.0.0+ and npm install]:
6+
*
7+
* 1. $ node dev/server.js
8+
* 2. set 'endpoints' at the Image Tools 'config' in example.html
9+
* endpoints : {
10+
* byFile: 'http://localhost:8008/uploadFile',
11+
* byUrl: 'http://localhost:8008/fetchUrl'
12+
* }
13+
*
14+
*/
15+
const http = require('http');
16+
const formidable = require('formidable');
17+
const { parse } = require('querystring');
18+
const fs = require('fs');
19+
const request = require('request');
20+
const crypto = require('crypto');
21+
22+
class ServerExample {
23+
constructor({port, fieldName}) {
24+
this.uploadDir = __dirname + '/\.tmp';
25+
this.fieldName = fieldName;
26+
this.server = http.createServer((req, res) => {
27+
this.onRequest(req, res);
28+
}).listen(port);
29+
30+
this.server.on('listening', () => {
31+
console.log('Server is listening ' + port + '...');
32+
});
33+
34+
this.server.on('error', (error) => {
35+
console.log('Failed to run server', error);
36+
});
37+
}
38+
39+
/**
40+
* Request handler
41+
* @param {http.IncomingMessage} request
42+
* @param {http.ServerResponse} response
43+
*/
44+
onRequest(request, response) {
45+
this.allowCors(response);
46+
47+
const {method, url} = request;
48+
49+
if (method.toLowerCase() !== 'post') {
50+
response.end();
51+
return;
52+
}
53+
54+
console.log('Got request on the ', url);
55+
56+
switch (url) {
57+
case '/uploadFile':
58+
this.uploadFile(request, response);
59+
break;
60+
case '/fetchUrl':
61+
this.fetchUrl(request, response);
62+
break;
63+
}
64+
}
65+
66+
/**
67+
* Allows CORS requests for debugging
68+
* @param response
69+
*/
70+
allowCors(response) {
71+
response.setHeader('Access-Control-Allow-Origin', '*');
72+
response.setHeader('Access-Control-Allow-Credentials', 'true');
73+
response.setHeader('Access-Control-Allow-Methods', 'GET,HEAD,OPTIONS,POST,PUT');
74+
response.setHeader('Access-Control-Allow-Headers', 'Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers');
75+
}
76+
77+
/**
78+
* Handles uploading by file
79+
* @param request
80+
* @param response
81+
*/
82+
uploadFile(request, response) {
83+
let responseJson = {
84+
success: 0
85+
};
86+
87+
this.getForm(request)
88+
.then(({files}) => {
89+
let image = files[this.fieldName] || {};
90+
91+
responseJson.success = 1;
92+
responseJson.file = {
93+
url: image.path,
94+
name: image.name,
95+
size: image.size
96+
};
97+
})
98+
.catch((error) => {
99+
console.log('Uploading error', error);
100+
})
101+
.finally(() => {
102+
response.writeHead(200, {'Content-Type': 'application/json'});
103+
response.end(JSON.stringify(responseJson));
104+
});
105+
}
106+
107+
/**
108+
* Handles uploading by URL
109+
* @param request
110+
* @param response
111+
*/
112+
fetchUrl(request, response) {
113+
let responseJson = {
114+
success: 0
115+
};
116+
117+
this.getForm(request)
118+
.then(({files, fields}) => {
119+
let url = fields.url;
120+
121+
let filename = this.uploadDir + '/' + this.md5(url) + '.png';
122+
123+
return this.downloadImage(url, filename)
124+
.then((path) => {
125+
responseJson.success = 1;
126+
responseJson.file = {
127+
url: path
128+
};
129+
});
130+
})
131+
.catch((error) => {
132+
console.log('Uploading error', error);
133+
})
134+
.finally(() => {
135+
response.writeHead(200, {'Content-Type': 'application/json'});
136+
response.end(JSON.stringify(responseJson));
137+
});
138+
}
139+
140+
/**
141+
* Accepts post form data
142+
* @param request
143+
* @return {Promise<{files: object, fields: object}>}
144+
*/
145+
getForm(request) {
146+
return new Promise((resolve, reject) => {
147+
const form = new formidable.IncomingForm();
148+
149+
form.uploadDir = this.uploadDir;
150+
form.keepExtensions = true;
151+
152+
form.parse(request, (err, fields, files) => {
153+
if (err) {
154+
reject(err);
155+
} else {
156+
console.log('fields', fields);
157+
console.log('files', files);
158+
resolve({files, fields});
159+
}
160+
});
161+
});
162+
}
163+
164+
/**
165+
* Download image by Url
166+
* @param {string} uri - endpoint
167+
* @param {string} filename - path for file saving
168+
* @return {Promise<string>} - filename
169+
*/
170+
downloadImage(uri, filename) {
171+
return new Promise((resolve, reject) => {
172+
request.head(uri, function (err, res, body) {
173+
request(uri).pipe(fs.createWriteStream(filename).on('erorr', reject))
174+
.on('close', () => {
175+
resolve(filename);
176+
});
177+
});
178+
});
179+
}
180+
181+
/**
182+
* Generates md5 hash for string
183+
* @param string
184+
* @return {string}
185+
*/
186+
md5(string) {
187+
return crypto.createHash('md5').update(string).digest('hex');
188+
}
189+
}
190+
191+
new ServerExample({
192+
port: 8008,
193+
fieldName: 'image'
194+
});

dist/bundle.js

+31-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)