Skip to content

Commit 8a5adef

Browse files
authored
The read-only mode (#1035)
(ノ◕ヮ◕)ノ*:・゚✧
1 parent 83131d6 commit 8a5adef

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+1294
-456
lines changed

dist/editor.js

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

docs/CHANGELOG.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
### 2.19
44

5+
- `New` - Read-only mode 🥳 [#837](https://github.com/codex-team/editor.js/issues/837)
56
- `New` - RTL mode added [#670](https://github.com/codex-team/editor.js/issues/670)
67
- `Fix` — Fix problem with types usage [#1183](https://github.com/codex-team/editor.js/issues/1183)
78
- `Fix` - Fixed issue with Spam clicking the "Click to tune" button duplicates the icons on FireFox. [#1273](https://github.com/codex-team/editor.js/issues/1273)
@@ -43,7 +44,6 @@
4344

4445
> *Breaking changes* `blocks.getBlockByIndex` method now returns BlockAPI object. To access old value, use BlockAPI.holder property
4546
46-
4747
### 2.17
4848

4949
- `Improvements` - Editor's [onchange callback](https://editorjs.io/configuration#editor-modifications-callback) now accepts an API as a parameter

example/assets/demo.css

+25
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,31 @@ body {
120120
text-decoration: none;
121121
}
122122

123+
.ce-example__statusbar {
124+
position: fixed;
125+
bottom: 10px;
126+
right: 10px;
127+
background: #fff;
128+
border-radius: 8px;
129+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.18);
130+
font-size: 12px;
131+
padding: 8px 15px;
132+
}
133+
134+
.ce-example__statusbar-button {
135+
display: inline-flex;
136+
margin-left: 10px;
137+
background: #4A9DF8;
138+
padding: 6px 12px;
139+
box-shadow: 0 7px 8px -4px rgba(137, 207, 255, 0.77);
140+
transition: all 150ms ease;
141+
cursor: pointer;
142+
border-radius: 31px;
143+
color: #fff;
144+
font-family: 'PT Mono', Menlo, Monaco, Consolas, Courier New, monospace;
145+
text-align: center;
146+
}
147+
123148
@media all and (max-width: 730px){
124149
.ce-example__header,
125150
.ce-example__content{

example/example-dev.html

+40-8
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,15 @@
3434
<div class="ce-example__button" id="saveButton">
3535
editor.save()
3636
</div>
37+
<div class="ce-example__statusbar">
38+
Readonly:
39+
<b id="readonly-state">
40+
Off
41+
</b>
42+
<div class="ce-example__statusbar-button" id="toggleReadOnlyButton">
43+
toggle
44+
</div>
45+
</div>
3746
</div>
3847
<div class="ce-example__output">
3948
<pre class="ce-example__output-content" id="output"></pre>
@@ -74,16 +83,16 @@
7483

7584
<!-- Initialization -->
7685
<script>
77-
/**
78-
* Saving button
79-
*/
80-
const saveButton = document.getElementById('saveButton');
81-
8286
/**
8387
* To initialize the Editor, create a new instance with configuration object
8488
* @see docs/installation.md for mode details
8589
*/
8690
var editor = new EditorJS({
91+
/**
92+
* Enable/Disable the read only mode
93+
*/
94+
readOnly: false,
95+
8796
/**
8897
* Wrapper of Editor
8998
*/
@@ -279,14 +288,37 @@
279288
},
280289
});
281290

291+
/**
292+
* Saving button
293+
*/
294+
const saveButton = document.getElementById('saveButton');
295+
296+
/**
297+
* Toggle read-only button
298+
*/
299+
const toggleReadOnlyButton = document.getElementById('toggleReadOnlyButton');
300+
const readOnlyIndicator = document.getElementById('readonly-state');
282301

283302
/**
284303
* Saving example
285304
*/
286305
saveButton.addEventListener('click', function () {
287-
editor.save().then((savedData) => {
288-
cPreview.show(savedData, document.getElementById("output"));
289-
});
306+
editor.save()
307+
.then((savedData) => {
308+
cPreview.show(savedData, document.getElementById("output"));
309+
})
310+
.catch((error) => {
311+
console.error('Saving error', error);
312+
});
313+
});
314+
315+
/**
316+
* Toggle read-only example
317+
*/
318+
toggleReadOnlyButton.addEventListener('click', async () => {
319+
const readOnlyState = await editor.readOnly.toggle();
320+
321+
readOnlyIndicator.textContent = readOnlyState ? 'On' : 'Off';
290322
});
291323
</script>
292324
</body>

example/tools/list

Submodule list updated from 497c043 to 439e5fc

src/components/__module.ts

+70-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@ import { EditorModules } from '../types-internal/editor-modules';
22
import { EditorConfig } from '../../types';
33
import { ModuleConfig } from '../types-internal/module-config';
44

5+
/**
6+
* The type <T> of the Module generic.
7+
* It describes the structure of nodes used in modules.
8+
*/
9+
export type ModuleNodes = object;
10+
511
/**
612
* @abstract
713
* @class Module
@@ -11,7 +17,13 @@ import { ModuleConfig } from '../types-internal/module-config';
1117
* @property {object} config - Editor user settings
1218
* @property {EditorModules} Editor - List of Editor modules
1319
*/
14-
export default class Module {
20+
export default class Module<T extends ModuleNodes = {}> {
21+
/**
22+
* Each module can provide some UI elements that will be stored in this property
23+
*/
24+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
25+
public nodes: T = {} as any;
26+
1527
/**
1628
* Editor modules list
1729
*
@@ -26,6 +38,50 @@ export default class Module {
2638
*/
2739
protected config: EditorConfig;
2840

41+
/**
42+
* This object provides methods to push into set of listeners that being dropped when read-only mode is enabled
43+
*/
44+
protected readOnlyMutableListeners = {
45+
/**
46+
* Assigns event listener on DOM element and pushes into special array that might be removed
47+
*
48+
* @param {EventTarget} element - DOM Element
49+
* @param {string} eventType - Event name
50+
* @param {Function} handler - Event handler
51+
* @param {boolean|AddEventListenerOptions} options - Listening options
52+
*/
53+
on: (
54+
element: EventTarget,
55+
eventType: string,
56+
handler: (event: Event) => void,
57+
options: boolean | AddEventListenerOptions = false
58+
): void => {
59+
const { Listeners } = this.Editor;
60+
61+
this.mutableListenerIds.push(
62+
Listeners.on(element, eventType, handler, options)
63+
);
64+
},
65+
66+
/**
67+
* Clears all mutable listeners
68+
*/
69+
clearAll: (): void => {
70+
const { Listeners } = this.Editor;
71+
72+
for (const id of this.mutableListenerIds) {
73+
Listeners.offById(id);
74+
}
75+
76+
this.mutableListenerIds = [];
77+
},
78+
};
79+
80+
/**
81+
* The set of listener identifiers which will be dropped in read-only mode
82+
*/
83+
private mutableListenerIds: string[] = [];
84+
2985
/**
3086
* @class
3187
* @param {EditorConfig} config - Editor's config
@@ -47,6 +103,19 @@ export default class Module {
47103
this.Editor = Editor;
48104
}
49105

106+
/**
107+
* Remove memorized nodes
108+
*/
109+
public removeAllNodes(): void {
110+
for (const key in this.nodes) {
111+
const node = this.nodes[key];
112+
113+
if (node instanceof HTMLElement) {
114+
node.remove();
115+
}
116+
}
117+
}
118+
50119
/**
51120
* Returns true if current direction is RTL (Right-To-Left)
52121
*/

src/components/block/api.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import { BlockAPI as BlockAPIInterface } from '../../../types/api';
1010
*
1111
* @param {Block} block - Block to expose
1212
*/
13-
function BlockAPI(block: Block): void {
13+
function BlockAPI(
14+
block: Block
15+
): void {
1416
const blockAPI: BlockAPIInterface = {
1517
/**
1618
* Tool name

src/components/block/index.ts

+12-4
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
import { SavedData } from '../../../types/data-formats';
1414
import $ from '../dom';
1515
import * as _ from '../utils';
16-
import ApiModule from '../modules/api';
16+
import ApiModules from '../modules/api';
1717
import BlockAPI from './api';
1818
import { ToolType } from '../modules/tools';
1919

@@ -49,7 +49,12 @@ interface BlockConstructorOptions {
4949
/**
5050
* Editor's API methods
5151
*/
52-
api: ApiModule;
52+
api: ApiModules;
53+
54+
/**
55+
* This flag indicates that the Block should be constructed in the read-only mode.
56+
*/
57+
readOnly: boolean;
5358
}
5459

5560
/**
@@ -146,7 +151,7 @@ export default class Block {
146151
/**
147152
* Editor`s API module
148153
*/
149-
private readonly api: ApiModule;
154+
private readonly api: ApiModules;
150155

151156
/**
152157
* Focused input index
@@ -197,14 +202,16 @@ export default class Block {
197202
* @param {BlockToolData} options.data - Tool's initial data
198203
* @param {BlockToolConstructable} options.Tool — Tool's class
199204
* @param {ToolSettings} options.settings - default tool's config
200-
* @param {ApiModule} options.api - Editor API module for pass it to the Block Tunes
205+
* @param {Module} options.api - Editor API module for pass it to the Block Tunes
206+
* @param {boolean} options.readOnly - Read-Only flag
201207
*/
202208
constructor({
203209
name,
204210
data,
205211
Tool,
206212
settings,
207213
api,
214+
readOnly,
208215
}: BlockConstructorOptions) {
209216
this.name = name;
210217
this.class = Tool;
@@ -220,6 +227,7 @@ export default class Block {
220227
config: this.config,
221228
api: this.api.getMethodsForTool(name, ToolType.Block),
222229
block: this.blockAPI,
230+
readOnly: readOnly,
223231
});
224232

225233
this.holder = this.compose();

src/components/core.ts

+16-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
import $ from './dom';
2-
// eslint-disable-next-line import/no-duplicates
32
import * as _ from './utils';
4-
// eslint-disable-next-line import/no-duplicates
5-
import { LogLevels } from './utils';
63
import { EditorConfig, OutputData, SanitizerConfig } from '../../types';
74
import { EditorModules } from '../types-internal/editor-modules';
85
import I18n from './i18n';
6+
import { CriticalError } from './errors/critical';
97

108
/**
119
* @typedef {Core} Core - editor core class
@@ -151,7 +149,7 @@ export default class Core {
151149
}
152150

153151
if (!this.config.logLevel) {
154-
this.config.logLevel = LogLevels.VERBOSE;
152+
this.config.logLevel = _.LogLevels.VERBOSE;
155153
}
156154

157155
_.setLogLevel(this.config.logLevel);
@@ -208,6 +206,9 @@ export default class Core {
208206
}
209207
}
210208

209+
this.config.readOnly = this.config.readOnly as boolean || false;
210+
this.config.i18n = {};
211+
211212
/**
212213
* Adjust i18n
213214
*/
@@ -285,13 +286,17 @@ export default class Core {
285286
public async start(): Promise<void> {
286287
const modulesToPrepare = [
287288
'Tools',
289+
'ReadOnly',
288290
'UI',
291+
'Toolbar',
292+
'InlineToolbar',
289293
'BlockManager',
290294
'Paste',
291295
'DragNDrop',
292296
'ModificationsObserver',
293297
'BlockSelection',
294298
'RectangleSelection',
299+
'CrossBlockSelection',
295300
];
296301

297302
await modulesToPrepare.reduce(
@@ -301,6 +306,13 @@ export default class Core {
301306
try {
302307
await this.moduleInstances[module].prepare();
303308
} catch (e) {
309+
/**
310+
* CriticalError's will not be caught
311+
* It is used when Editor is rendering in read-only mode with unsupported plugin
312+
*/
313+
if (e instanceof CriticalError) {
314+
throw new Error(e.message);
315+
}
304316
_.log(`Module ${module} was skipped because of %o`, 'warn', e);
305317
}
306318
// _.log(`Preparing ${module} module`, 'timeEnd');

0 commit comments

Comments
 (0)