├── .gitignore
├── assets
└── 2cd4f84f-cb1c-4a15-b281-25af67a9f4e9.jpg
├── simple-image.css
├── example.html
├── README.md
└── simple-image.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
--------------------------------------------------------------------------------
/assets/2cd4f84f-cb1c-4a15-b281-25af67a9f4e9.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/editor-js/simple-image-tutorial/master/assets/2cd4f84f-cb1c-4a15-b281-25af67a9f4e9.jpg
--------------------------------------------------------------------------------
/simple-image.css:
--------------------------------------------------------------------------------
1 | .simple-image {
2 | padding: 20px 0;
3 | }
4 |
5 | .simple-image input,
6 | .simple-image [contenteditable] {
7 | width: 100%;
8 | padding: 10px;
9 | border: 1px solid #e4e4e4;
10 | background: #fff;
11 | box-sizing: border-box;
12 | border-radius: 3px;
13 | outline: none;
14 | font-size: 14px;
15 | }
16 |
17 | .simple-image img {
18 | max-width: 100%;
19 | margin-bottom: 15px;
20 | }
21 |
22 | .simple-image.withBorder img {
23 | border: 1px solid #e8e8eb;
24 | }
25 |
26 | .simple-image.withBackground {
27 | background: #eff2f5;
28 | padding: 10px;
29 | }
30 |
31 | .simple-image.withBackground img {
32 | display: block;
33 | max-width: 60%;
34 | margin: 0 auto 15px;
35 | }
--------------------------------------------------------------------------------
/example.html:
--------------------------------------------------------------------------------
1 |
5 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Simple Image tutorial project
2 | Final result of guide series learning [how to create own Block Tool](https://editorjs.io/creating-a-block-tool) for Editor.js
3 |
4 | Simple Image — paste image URL and get the image Block rendered.
5 | Also supports pasting files and substitutions of `
` tags on pasting.
6 | Does not support uploading images from the device.
7 |
8 | 
9 |
10 | ## Chapters
11 |
12 | - [The first plugin](https://editorjs.io/the-first-plugin)
13 | - [Fill Block with saved data](https://editorjs.io/fill-block-with-saved-data)
14 | - [Saved data validation](https://editorjs.io/saved-data-validation)
15 | - [Changing a view](https://editorjs.io/changing-a-view)
16 | - [Enable Inline Toolbar](https://editorjs.io/enable-inline-toolbar)
17 | - [Making a Block Settings](https://editorjs.io/making-a-block-settings)
18 | - [Access Editor's API](https://editorjs.io/access-api)
19 | - [Paste substitutions](https://editorjs.io/paste-substitutions)
20 | - [Sanitize saved data](https://editorjs.io/sanitize-saved-data)
21 | - [Provide custom configuration](https://editorjs.io/provide-custom-configuration)
22 |
23 |
24 | ## Installation
25 |
26 | **Note.** If you want to use such tool in production, see full-featured [Simple Image](https://github.com/editor-js/simple-image) Tool.
27 | The tutorial code and functionality are simplified for better understanding of basics API usage.
28 |
29 | ## Configuration
30 |
31 | This Tool supports following configuration properties:
32 |
33 | | name | description |
34 | | -- | -- |
35 | | `placeholder` | Custom placeholder for «Paste URL» field |
36 |
37 | Example of connection:
38 |
39 | ```js
40 | /**
41 | * Initialize the Editor
42 | */
43 | const editor = new EditorJS({
44 | autofocus: true,
45 | tools: {
46 | image: {
47 | class: SimpleImage,
48 | inlineToolbar: true,
49 | config: {
50 | placeholder: 'Paste image URL'
51 | }
52 | }
53 | },
54 | });
55 | ```
56 |
57 | ## Output data format
58 |
59 | This Tool return following data format:
60 |
61 | ```js
62 | {
63 | url: 'https://cdn.pixabay.com/photo/2017/09/01/21/53/blue-2705642_1280.jpg'
64 | caption: 'Image caption example',
65 | withBorder: false,
66 | withBackground: false,
67 | stretched: false
68 | }
69 | ```
70 |
71 | | field | type | description |
72 | | -- | -- | -- |
73 | | `url` | __string__ | image source URL |
74 | | `caption` | __string__ | image caption |
75 | | `withBorder` | __boolean__ | flag for adding a border |
76 | | `withBackground` | __boolean__ | flag for adding a background |
77 | | `stretched` | __boolean__ | flag for stretching image to the full width of content |
--------------------------------------------------------------------------------
/simple-image.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Tool for creating image Blocks for Editor.js
3 | * Made with «Creating a Block Tool» tutorial {@link https://editorjs.io/creating-a-block-tool}
4 | *
5 | * @typedef {object} ImageToolData — Input/Output data format for our Tool
6 | * @property {string} url - image source URL
7 | * @property {string} caption - image caption
8 | * @property {boolean} withBorder - flag for adding a border
9 | * @property {boolean} withBackground - flag for adding a background
10 | * @property {boolean} stretched - flag for stretching image to the full width of content
11 | *
12 | * @typedef {object} ImageToolConfig
13 | * @property {string} placeholder — custom placeholder for URL field
14 | */
15 | class SimpleImage {
16 | /**
17 | * Our tool should be placed at the Toolbox, so describe an icon and title
18 | */
19 | static get toolbox() {
20 | return {
21 | title: 'Image',
22 | icon: ''
23 | };
24 | }
25 |
26 | /**
27 | * Allow render Image Blocks by pasting HTML tags, files and URLs
28 | * @see {@link https://editorjs.io/paste-substitutions}
29 | * @return {{tags: string[], files: {mimeTypes: string[], extensions: string[]}, patterns: {image: RegExp}}}
30 | */
31 | static get pasteConfig() {
32 | return {
33 | tags: ['IMG'],
34 | files: {
35 | mimeTypes: ['image/*'],
36 | extensions: ['gif', 'jpg', 'png'] // You can specify extensions instead of mime-types
37 | },
38 | patterns: {
39 | image: /https?:\/\/\S+\.(gif|jpe?g|tiff|png)$/i
40 | }
41 | }
42 | }
43 |
44 | /**
45 | * Automatic sanitize config
46 | * @see {@link https://editorjs.io/sanitize-saved-data}
47 | */
48 | static get sanitize(){
49 | return {
50 | url: {},
51 | caption: {
52 | b: true,
53 | a: {
54 | href: true
55 | },
56 | i: true
57 | }
58 | }
59 | }
60 |
61 | /**
62 | * Tool class constructor
63 | * @param {ImageToolData} data — previously saved data
64 | * @param {object} api — Editor.js Core API {@link https://editorjs.io/api}
65 | * @param {ImageToolConfig} config — custom config that we provide to our tool's user
66 | */
67 | constructor({data, api, config}){
68 | this.api = api;
69 | this.config = config || {};
70 | this.data = {
71 | url: data.url || '',
72 | caption: data.caption || '',
73 | withBorder: data.withBorder !== undefined ? data.withBorder : false,
74 | withBackground: data.withBackground !== undefined ? data.withBackground : false,
75 | stretched: data.stretched !== undefined ? data.stretched : false,
76 | };
77 |
78 | this.wrapper = undefined;
79 | this.settings = [
80 | {
81 | name: 'withBorder',
82 | icon: ``
83 | },
84 | {
85 | name: 'stretched',
86 | icon: ``
87 | },
88 | {
89 | name: 'withBackground',
90 | icon: ``
91 | }
92 | ];
93 | }
94 |
95 | /**
96 | * Return a Tool's UI
97 | * @return {HTMLElement}
98 | */
99 | render(){
100 | this.wrapper = document.createElement('div');
101 | this.wrapper.classList.add('simple-image');
102 |
103 | if (this.data && this.data.url){
104 | this._createImage(this.data.url, this.data.caption);
105 | return this.wrapper;
106 | }
107 |
108 | const input = document.createElement('input');
109 |
110 | input.placeholder = this.config.placeholder || 'Paste an image URL...';
111 | input.addEventListener('paste', (event) => {
112 | this._createImage(event.clipboardData.getData('text'));
113 | });
114 |
115 | this.wrapper.appendChild(input);
116 |
117 | return this.wrapper;
118 | }
119 |
120 | /**
121 | * @private
122 | * Create image with caption field
123 | * @param {string} url — image source
124 | * @param {string} captionText — caption value
125 | */
126 | _createImage(url, captionText){
127 | const image = document.createElement('img');
128 | const caption = document.createElement('div');
129 |
130 | image.src = url;
131 | caption.contentEditable = true;
132 | caption.innerHTML = captionText || '';
133 |
134 | this.wrapper.innerHTML = '';
135 | this.wrapper.appendChild(image);
136 | this.wrapper.appendChild(caption);
137 |
138 | this._acceptTuneView();
139 | }
140 |
141 | /**
142 | * Extract data from the UI
143 | * @param {HTMLElement} blockContent — element returned by render method
144 | * @return {SimpleImageData}
145 | */
146 | save(blockContent){
147 | const image = blockContent.querySelector('img');
148 | const caption = blockContent.querySelector('[contenteditable]');
149 |
150 | return Object.assign(this.data, {
151 | url: image.src,
152 | caption: caption.innerHTML || ''
153 | });
154 | }
155 |
156 | /**
157 | * Skip empty blocks
158 | * @see {@link https://editorjs.io/saved-data-validation}
159 | * @param {ImageToolConfig} savedData
160 | * @return {boolean}
161 | */
162 | validate(savedData){
163 | if (!savedData.url.trim()){
164 | return false;
165 | }
166 |
167 | return true;
168 | }
169 |
170 | /**
171 | * Making a Block settings: 'add border', 'add background', 'stretch to full width'
172 | * @see https://editorjs.io/making-a-block-settings — tutorial
173 | * @see https://editorjs.io/tools-api#rendersettings - API method description
174 | * @return {HTMLDivElement}
175 | */
176 | renderSettings(){
177 | const wrapper = document.createElement('div');
178 |
179 | this.settings.forEach( tune => {
180 | let button = document.createElement('div');
181 |
182 | button.classList.add(this.api.styles.settingsButton);
183 | button.classList.toggle(this.api.styles.settingsButtonActive, this.data[tune.name]);
184 | button.innerHTML = tune.icon;
185 | wrapper.appendChild(button);
186 |
187 | button.addEventListener('click', () => {
188 | this._toggleTune(tune.name);
189 | button.classList.toggle(this.api.styles.settingsButtonActive);
190 | });
191 |
192 | });
193 |
194 | return wrapper;
195 | }
196 |
197 | /**
198 | * @private
199 | * Click on the Settings Button
200 | * @param {string} tune — tune name from this.settings
201 | */
202 | _toggleTune(tune) {
203 | this.data[tune] = !this.data[tune];
204 | this._acceptTuneView();
205 | }
206 |
207 | /**
208 | * Add specified class corresponds with activated tunes
209 | * @private
210 | */
211 | _acceptTuneView() {
212 | this.settings.forEach( tune => {
213 | this.wrapper.classList.toggle(tune.name, !!this.data[tune.name]);
214 |
215 | if (tune.name === 'stretched') {
216 | this.api.blocks.stretchBlock(this.api.blocks.getCurrentBlockIndex(), !!this.data.stretched);
217 | }
218 | });
219 | }
220 |
221 | /**
222 | * Handle paste event
223 | * @see https://editorjs.io/tools-api#onpaste - API description
224 | * @param {CustomEvent }event
225 | */
226 | onPaste(event){
227 | switch (event.type){
228 | case 'tag':
229 | const imgTag = event.detail.data;
230 | this._createImage(imgTag.src);
231 | break;
232 | case 'file':
233 | /* We need to read file here as base64 string */
234 | const file = event.detail.file;
235 | const reader = new FileReader();
236 |
237 | reader.onload = (loadEvent) => {
238 | this._createImage(loadEvent.target.result);
239 | };
240 |
241 | reader.readAsDataURL(file);
242 | break;
243 | case 'pattern':
244 | const src = event.detail.data;
245 |
246 | this._createImage(src);
247 | break;
248 | }
249 | }
250 | }
--------------------------------------------------------------------------------