@ckeditor/ckeditor5-restricted-editing 47.1.0-alpha.2 → 47.2.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (313) hide show
  1. package/README.md +1 -1
  2. package/build/restricted-editing.js +2 -2
  3. package/build/translations/af.js +1 -1
  4. package/build/translations/ar.js +1 -1
  5. package/build/translations/ast.js +1 -1
  6. package/build/translations/az.js +1 -1
  7. package/build/translations/be.js +1 -1
  8. package/build/translations/bg.js +1 -1
  9. package/build/translations/bn.js +1 -1
  10. package/build/translations/bs.js +1 -1
  11. package/build/translations/ca.js +1 -1
  12. package/build/translations/cs.js +1 -1
  13. package/build/translations/da.js +1 -1
  14. package/build/translations/de-ch.js +1 -1
  15. package/build/translations/de.js +1 -1
  16. package/build/translations/el.js +1 -1
  17. package/build/translations/en-au.js +1 -1
  18. package/build/translations/en-gb.js +1 -1
  19. package/build/translations/eo.js +1 -1
  20. package/build/translations/es-co.js +1 -1
  21. package/build/translations/es.js +1 -1
  22. package/build/translations/et.js +1 -1
  23. package/build/translations/eu.js +1 -1
  24. package/build/translations/fa.js +1 -1
  25. package/build/translations/fi.js +1 -1
  26. package/build/translations/fr.js +1 -1
  27. package/build/translations/gl.js +1 -1
  28. package/build/translations/gu.js +1 -1
  29. package/build/translations/he.js +1 -1
  30. package/build/translations/hi.js +1 -1
  31. package/build/translations/hr.js +1 -1
  32. package/build/translations/hu.js +1 -1
  33. package/build/translations/hy.js +1 -1
  34. package/build/translations/id.js +1 -1
  35. package/build/translations/it.js +1 -1
  36. package/build/translations/ja.js +1 -1
  37. package/build/translations/jv.js +1 -1
  38. package/build/translations/kk.js +1 -1
  39. package/build/translations/km.js +1 -1
  40. package/build/translations/kn.js +1 -1
  41. package/build/translations/ko.js +1 -1
  42. package/build/translations/ku.js +1 -1
  43. package/build/translations/lt.js +1 -1
  44. package/build/translations/lv.js +1 -1
  45. package/build/translations/ms.js +1 -1
  46. package/build/translations/nb.js +1 -1
  47. package/build/translations/ne.js +1 -1
  48. package/build/translations/nl.js +1 -1
  49. package/build/translations/no.js +1 -1
  50. package/build/translations/oc.js +1 -1
  51. package/build/translations/pl.js +1 -1
  52. package/build/translations/pt-br.js +1 -1
  53. package/build/translations/pt.js +1 -1
  54. package/build/translations/ro.js +1 -1
  55. package/build/translations/ru.js +1 -1
  56. package/build/translations/si.js +1 -1
  57. package/build/translations/sk.js +1 -1
  58. package/build/translations/sl.js +1 -1
  59. package/build/translations/sq.js +1 -1
  60. package/build/translations/sr-latn.js +1 -1
  61. package/build/translations/sr.js +1 -1
  62. package/build/translations/sv.js +1 -1
  63. package/build/translations/th.js +1 -1
  64. package/build/translations/ti.js +1 -1
  65. package/build/translations/tk.js +1 -1
  66. package/build/translations/tr.js +1 -1
  67. package/build/translations/tt.js +1 -1
  68. package/build/translations/ug.js +1 -1
  69. package/build/translations/uk.js +1 -1
  70. package/build/translations/ur.js +1 -1
  71. package/build/translations/uz.js +1 -1
  72. package/build/translations/vi.js +1 -1
  73. package/build/translations/zh-cn.js +1 -1
  74. package/build/translations/zh.js +1 -1
  75. package/ckeditor5-metadata.json +20 -2
  76. package/dist/index.js +564 -105
  77. package/dist/index.js.map +1 -1
  78. package/dist/translations/af.js +1 -1
  79. package/dist/translations/af.umd.js +1 -1
  80. package/dist/translations/ar.js +1 -1
  81. package/dist/translations/ar.umd.js +1 -1
  82. package/dist/translations/ast.js +1 -1
  83. package/dist/translations/ast.umd.js +1 -1
  84. package/dist/translations/az.js +1 -1
  85. package/dist/translations/az.umd.js +1 -1
  86. package/dist/translations/be.js +1 -1
  87. package/dist/translations/be.umd.js +1 -1
  88. package/dist/translations/bg.js +1 -1
  89. package/dist/translations/bg.umd.js +1 -1
  90. package/dist/translations/bn.js +1 -1
  91. package/dist/translations/bn.umd.js +1 -1
  92. package/dist/translations/bs.js +1 -1
  93. package/dist/translations/bs.umd.js +1 -1
  94. package/dist/translations/ca.js +1 -1
  95. package/dist/translations/ca.umd.js +1 -1
  96. package/dist/translations/cs.js +1 -1
  97. package/dist/translations/cs.umd.js +1 -1
  98. package/dist/translations/da.js +1 -1
  99. package/dist/translations/da.umd.js +1 -1
  100. package/dist/translations/de-ch.js +1 -1
  101. package/dist/translations/de-ch.umd.js +1 -1
  102. package/dist/translations/de.js +1 -1
  103. package/dist/translations/de.umd.js +1 -1
  104. package/dist/translations/el.js +1 -1
  105. package/dist/translations/el.umd.js +1 -1
  106. package/dist/translations/en-au.js +1 -1
  107. package/dist/translations/en-au.umd.js +1 -1
  108. package/dist/translations/en-gb.js +1 -1
  109. package/dist/translations/en-gb.umd.js +1 -1
  110. package/dist/translations/en.js +1 -1
  111. package/dist/translations/en.umd.js +1 -1
  112. package/dist/translations/eo.js +1 -1
  113. package/dist/translations/eo.umd.js +1 -1
  114. package/dist/translations/es-co.js +1 -1
  115. package/dist/translations/es-co.umd.js +1 -1
  116. package/dist/translations/es.js +1 -1
  117. package/dist/translations/es.umd.js +1 -1
  118. package/dist/translations/et.js +1 -1
  119. package/dist/translations/et.umd.js +1 -1
  120. package/dist/translations/eu.js +1 -1
  121. package/dist/translations/eu.umd.js +1 -1
  122. package/dist/translations/fa.js +1 -1
  123. package/dist/translations/fa.umd.js +1 -1
  124. package/dist/translations/fi.js +1 -1
  125. package/dist/translations/fi.umd.js +1 -1
  126. package/dist/translations/fr.js +1 -1
  127. package/dist/translations/fr.umd.js +1 -1
  128. package/dist/translations/gl.js +1 -1
  129. package/dist/translations/gl.umd.js +1 -1
  130. package/dist/translations/gu.js +1 -1
  131. package/dist/translations/gu.umd.js +1 -1
  132. package/dist/translations/he.js +1 -1
  133. package/dist/translations/he.umd.js +1 -1
  134. package/dist/translations/hi.js +1 -1
  135. package/dist/translations/hi.umd.js +1 -1
  136. package/dist/translations/hr.js +1 -1
  137. package/dist/translations/hr.umd.js +1 -1
  138. package/dist/translations/hu.js +1 -1
  139. package/dist/translations/hu.umd.js +1 -1
  140. package/dist/translations/hy.js +1 -1
  141. package/dist/translations/hy.umd.js +1 -1
  142. package/dist/translations/id.js +1 -1
  143. package/dist/translations/id.umd.js +1 -1
  144. package/dist/translations/it.js +1 -1
  145. package/dist/translations/it.umd.js +1 -1
  146. package/dist/translations/ja.js +1 -1
  147. package/dist/translations/ja.umd.js +1 -1
  148. package/dist/translations/jv.js +1 -1
  149. package/dist/translations/jv.umd.js +1 -1
  150. package/dist/translations/kk.js +1 -1
  151. package/dist/translations/kk.umd.js +1 -1
  152. package/dist/translations/km.js +1 -1
  153. package/dist/translations/km.umd.js +1 -1
  154. package/dist/translations/kn.js +1 -1
  155. package/dist/translations/kn.umd.js +1 -1
  156. package/dist/translations/ko.js +1 -1
  157. package/dist/translations/ko.umd.js +1 -1
  158. package/dist/translations/ku.js +1 -1
  159. package/dist/translations/ku.umd.js +1 -1
  160. package/dist/translations/lt.js +1 -1
  161. package/dist/translations/lt.umd.js +1 -1
  162. package/dist/translations/lv.js +1 -1
  163. package/dist/translations/lv.umd.js +1 -1
  164. package/dist/translations/ms.js +1 -1
  165. package/dist/translations/ms.umd.js +1 -1
  166. package/dist/translations/nb.js +1 -1
  167. package/dist/translations/nb.umd.js +1 -1
  168. package/dist/translations/ne.js +1 -1
  169. package/dist/translations/ne.umd.js +1 -1
  170. package/dist/translations/nl.js +1 -1
  171. package/dist/translations/nl.umd.js +1 -1
  172. package/dist/translations/no.js +1 -1
  173. package/dist/translations/no.umd.js +1 -1
  174. package/dist/translations/oc.js +1 -1
  175. package/dist/translations/oc.umd.js +1 -1
  176. package/dist/translations/pl.js +1 -1
  177. package/dist/translations/pl.umd.js +1 -1
  178. package/dist/translations/pt-br.js +1 -1
  179. package/dist/translations/pt-br.umd.js +1 -1
  180. package/dist/translations/pt.js +1 -1
  181. package/dist/translations/pt.umd.js +1 -1
  182. package/dist/translations/ro.js +1 -1
  183. package/dist/translations/ro.umd.js +1 -1
  184. package/dist/translations/ru.js +1 -1
  185. package/dist/translations/ru.umd.js +1 -1
  186. package/dist/translations/si.js +1 -1
  187. package/dist/translations/si.umd.js +1 -1
  188. package/dist/translations/sk.js +1 -1
  189. package/dist/translations/sk.umd.js +1 -1
  190. package/dist/translations/sl.js +1 -1
  191. package/dist/translations/sl.umd.js +1 -1
  192. package/dist/translations/sq.js +1 -1
  193. package/dist/translations/sq.umd.js +1 -1
  194. package/dist/translations/sr-latn.js +1 -1
  195. package/dist/translations/sr-latn.umd.js +1 -1
  196. package/dist/translations/sr.js +1 -1
  197. package/dist/translations/sr.umd.js +1 -1
  198. package/dist/translations/sv.js +1 -1
  199. package/dist/translations/sv.umd.js +1 -1
  200. package/dist/translations/th.js +1 -1
  201. package/dist/translations/th.umd.js +1 -1
  202. package/dist/translations/ti.js +1 -1
  203. package/dist/translations/ti.umd.js +1 -1
  204. package/dist/translations/tk.js +1 -1
  205. package/dist/translations/tk.umd.js +1 -1
  206. package/dist/translations/tr.js +1 -1
  207. package/dist/translations/tr.umd.js +1 -1
  208. package/dist/translations/tt.js +1 -1
  209. package/dist/translations/tt.umd.js +1 -1
  210. package/dist/translations/ug.js +1 -1
  211. package/dist/translations/ug.umd.js +1 -1
  212. package/dist/translations/uk.js +1 -1
  213. package/dist/translations/uk.umd.js +1 -1
  214. package/dist/translations/ur.js +1 -1
  215. package/dist/translations/ur.umd.js +1 -1
  216. package/dist/translations/uz.js +1 -1
  217. package/dist/translations/uz.umd.js +1 -1
  218. package/dist/translations/vi.js +1 -1
  219. package/dist/translations/vi.umd.js +1 -1
  220. package/dist/translations/zh-cn.js +1 -1
  221. package/dist/translations/zh-cn.umd.js +1 -1
  222. package/dist/translations/zh.js +1 -1
  223. package/dist/translations/zh.umd.js +1 -1
  224. package/lang/contexts.json +4 -1
  225. package/lang/translations/af.po +16 -4
  226. package/lang/translations/ar.po +16 -4
  227. package/lang/translations/ast.po +16 -4
  228. package/lang/translations/az.po +16 -4
  229. package/lang/translations/be.po +16 -4
  230. package/lang/translations/bg.po +16 -4
  231. package/lang/translations/bn.po +16 -4
  232. package/lang/translations/bs.po +16 -4
  233. package/lang/translations/ca.po +16 -4
  234. package/lang/translations/cs.po +16 -4
  235. package/lang/translations/da.po +16 -4
  236. package/lang/translations/de-ch.po +16 -4
  237. package/lang/translations/de.po +16 -4
  238. package/lang/translations/el.po +16 -4
  239. package/lang/translations/en-au.po +16 -4
  240. package/lang/translations/en-gb.po +16 -4
  241. package/lang/translations/en.po +16 -4
  242. package/lang/translations/eo.po +16 -4
  243. package/lang/translations/es-co.po +16 -4
  244. package/lang/translations/es.po +16 -4
  245. package/lang/translations/et.po +16 -4
  246. package/lang/translations/eu.po +16 -4
  247. package/lang/translations/fa.po +16 -4
  248. package/lang/translations/fi.po +16 -4
  249. package/lang/translations/fr.po +16 -4
  250. package/lang/translations/gl.po +16 -4
  251. package/lang/translations/gu.po +16 -4
  252. package/lang/translations/he.po +16 -4
  253. package/lang/translations/hi.po +16 -4
  254. package/lang/translations/hr.po +16 -4
  255. package/lang/translations/hu.po +16 -4
  256. package/lang/translations/hy.po +16 -4
  257. package/lang/translations/id.po +16 -4
  258. package/lang/translations/it.po +16 -4
  259. package/lang/translations/ja.po +16 -4
  260. package/lang/translations/jv.po +16 -4
  261. package/lang/translations/kk.po +16 -4
  262. package/lang/translations/km.po +16 -4
  263. package/lang/translations/kn.po +16 -4
  264. package/lang/translations/ko.po +16 -4
  265. package/lang/translations/ku.po +16 -4
  266. package/lang/translations/lt.po +16 -4
  267. package/lang/translations/lv.po +16 -4
  268. package/lang/translations/ms.po +16 -4
  269. package/lang/translations/nb.po +16 -4
  270. package/lang/translations/ne.po +16 -4
  271. package/lang/translations/nl.po +16 -4
  272. package/lang/translations/no.po +16 -4
  273. package/lang/translations/oc.po +16 -4
  274. package/lang/translations/pl.po +16 -4
  275. package/lang/translations/pt-br.po +16 -4
  276. package/lang/translations/pt.po +16 -4
  277. package/lang/translations/ro.po +16 -4
  278. package/lang/translations/ru.po +16 -4
  279. package/lang/translations/si.po +16 -4
  280. package/lang/translations/sk.po +16 -4
  281. package/lang/translations/sl.po +16 -4
  282. package/lang/translations/sq.po +16 -4
  283. package/lang/translations/sr-latn.po +16 -4
  284. package/lang/translations/sr.po +16 -4
  285. package/lang/translations/sv.po +16 -4
  286. package/lang/translations/th.po +16 -4
  287. package/lang/translations/ti.po +16 -4
  288. package/lang/translations/tk.po +16 -4
  289. package/lang/translations/tr.po +16 -4
  290. package/lang/translations/tt.po +16 -4
  291. package/lang/translations/ug.po +16 -4
  292. package/lang/translations/uk.po +16 -4
  293. package/lang/translations/ur.po +16 -4
  294. package/lang/translations/uz.po +16 -4
  295. package/lang/translations/vi.po +16 -4
  296. package/lang/translations/zh-cn.po +16 -4
  297. package/lang/translations/zh.po +16 -4
  298. package/package.json +7 -7
  299. package/src/augmentation.d.ts +2 -1
  300. package/src/index.d.ts +1 -0
  301. package/src/index.js +1 -0
  302. package/src/restrictededitingconfig.d.ts +2 -2
  303. package/src/restrictededitingexceptionblockcommand.d.ts +57 -0
  304. package/src/restrictededitingexceptionblockcommand.js +203 -0
  305. package/src/restrictededitingmode/converters.d.ts +1 -0
  306. package/src/restrictededitingmode/converters.js +33 -6
  307. package/src/restrictededitingmode/utils.d.ts +8 -2
  308. package/src/restrictededitingmode/utils.js +16 -3
  309. package/src/restrictededitingmodeediting.d.ts +4 -0
  310. package/src/restrictededitingmodeediting.js +83 -35
  311. package/src/restrictededitingmodenavigationcommand.js +2 -1
  312. package/src/standardeditingmodeediting.js +135 -3
  313. package/src/standardeditingmodeui.js +75 -11
@@ -6,9 +6,10 @@
6
6
  * @module restricted-editing/restrictededitingmodeediting
7
7
  */
8
8
  import { Plugin } from 'ckeditor5/src/core.js';
9
+ import { getCode, parseKeystroke } from 'ckeditor5/src/utils.js';
9
10
  import { RestrictedEditingModeNavigationCommand } from './restrictededitingmodenavigationcommand.js';
11
+ import { getMarkerAtPosition, isSelectionInMarker, getExceptionRange } from './restrictededitingmode/utils.js';
10
12
  import { extendMarkerOnTypingPostFixer, resurrectCollapsedMarkerPostFixer, setupExceptionHighlighting, upcastHighlightToMarker } from './restrictededitingmode/converters.js';
11
- import { getMarkerAtPosition, isSelectionInMarker } from './restrictededitingmode/utils.js';
12
13
  const COMMAND_FORCE_DISABLE_ID = 'RestrictedEditingMode';
13
14
  /**
14
15
  * The restricted editing mode editing feature.
@@ -74,6 +75,7 @@ export class RestrictedEditingModeEditing extends Plugin {
74
75
  const editingView = editor.editing.view;
75
76
  const allowedCommands = editor.config.get('restrictedEditing.allowedCommands');
76
77
  allowedCommands.forEach(commandName => this._allowedInException.add(commandName));
78
+ this._setupSchema();
77
79
  this._setupConversion();
78
80
  this._setupCommandsToggling();
79
81
  this._setupRestrictions();
@@ -92,7 +94,7 @@ export class RestrictedEditingModeEditing extends Plugin {
92
94
  // Stop the event bubbling in the editor: no more callbacks will be executed for this keystroke.
93
95
  evt.stop();
94
96
  }, { context: '$capture' });
95
- editor.keystrokes.set('Ctrl+A', getSelectAllHandler(editor));
97
+ this.listenTo(editingView.document, 'keydown', getSelectAllHandler(editor), { priority: 'high' });
96
98
  editingView.change(writer => {
97
99
  for (const root of editingView.document.roots) {
98
100
  writer.addClass('ck-restricted-editing_mode_restricted', root);
@@ -123,6 +125,17 @@ export class RestrictedEditingModeEditing extends Plugin {
123
125
  command.clearForceDisabled(COMMAND_FORCE_DISABLE_ID);
124
126
  this._alwaysEnabled.add(commandName);
125
127
  }
128
+ /**
129
+ * Registers block exception wrapper in the schema.
130
+ */
131
+ _setupSchema() {
132
+ const schema = this.editor.model.schema;
133
+ schema.register('restrictedEditingException', {
134
+ allowWhere: '$block',
135
+ allowContentOf: '$container',
136
+ isLimit: true
137
+ });
138
+ }
126
139
  /**
127
140
  * Sets up the restricted mode editing conversion:
128
141
  *
@@ -144,16 +157,35 @@ export class RestrictedEditingModeEditing extends Plugin {
144
157
  },
145
158
  model: () => {
146
159
  markerNumber++; // Starting from restrictedEditingException:1 marker.
147
- return `restrictedEditingException:${markerNumber}`;
160
+ return `restrictedEditingException:inline:${markerNumber}`;
148
161
  }
149
162
  }));
163
+ editor.conversion.for('upcast').add(upcastHighlightToMarker({
164
+ view: {
165
+ name: 'div',
166
+ classes: 'restricted-editing-exception'
167
+ },
168
+ model: () => {
169
+ markerNumber++; // Starting from restrictedEditingException:1 marker.
170
+ return `restrictedEditingException:block:${markerNumber}`;
171
+ },
172
+ useWrapperElement: true
173
+ }));
174
+ // Block exception wrapper.
175
+ editor.conversion.for('downcast').elementToElement({
176
+ model: 'restrictedEditingException',
177
+ view: {
178
+ name: 'div',
179
+ classes: 'restricted-editing-exception'
180
+ }
181
+ });
150
182
  // Currently the marker helpers are tied to other use-cases and do not render a collapsed marker as highlight.
151
183
  // Also, markerToHighlight cannot convert marker on an inline object. It handles only text and widgets,
152
184
  // but it is not a case in the data pipeline. That's why there are 3 downcast converters for them:
153
185
  //
154
186
  // 1. The custom inline item (text or inline object) converter (but not the selection).
155
187
  editor.conversion.for('downcast').add(dispatcher => {
156
- dispatcher.on('addMarker:restrictedEditingException', (evt, data, conversionApi) => {
188
+ dispatcher.on('addMarker:restrictedEditingException:inline', (evt, data, conversionApi) => {
157
189
  // Only convert per-item conversion.
158
190
  if (!data.item) {
159
191
  return;
@@ -186,7 +218,7 @@ export class RestrictedEditingModeEditing extends Plugin {
186
218
  });
187
219
  // 2. The marker-to-highlight converter for the document selection.
188
220
  editor.conversion.for('downcast').markerToHighlight({
189
- model: 'restrictedEditingException',
221
+ model: 'restrictedEditingException:inline',
190
222
  // Use callback to return new object every time new marker instance is created - otherwise it will be seen as the same marker.
191
223
  view: () => {
192
224
  return {
@@ -199,7 +231,7 @@ export class RestrictedEditingModeEditing extends Plugin {
199
231
  // 3. And for collapsed marker we need to render it as an element.
200
232
  // Additionally, the editing pipeline should always display a collapsed marker.
201
233
  editor.conversion.for('editingDowncast').markerToElement({
202
- model: 'restrictedEditingException',
234
+ model: 'restrictedEditingException:inline',
203
235
  view: (markerData, { writer }) => {
204
236
  return writer.createUIElement('span', {
205
237
  class: 'restricted-editing-exception restricted-editing-exception_collapsed'
@@ -207,7 +239,7 @@ export class RestrictedEditingModeEditing extends Plugin {
207
239
  }
208
240
  });
209
241
  editor.conversion.for('dataDowncast').markerToElement({
210
- model: 'restrictedEditingException',
242
+ model: 'restrictedEditingException:inline',
211
243
  view: (markerData, { writer }) => {
212
244
  return writer.createEmptyElement('span', {
213
245
  class: 'restricted-editing-exception'
@@ -241,10 +273,29 @@ export class RestrictedEditingModeEditing extends Plugin {
241
273
  this.listenTo(insertTextCommand, 'execute', disallowInputExecForWrongRange(editor), { priority: 'high' });
242
274
  }
243
275
  // Block clipboard outside exception marker on paste and drop.
244
- this.listenTo(clipboard, 'contentInsertion', evt => {
276
+ this.listenTo(clipboard, 'contentInsertion', (evt, data) => {
245
277
  if (!isRangeInsideSingleMarker(editor, selection.getFirstRange())) {
246
278
  evt.stop();
247
279
  }
280
+ const marker = getMarkerAtPosition(editor, selection.focus);
281
+ // Reduce content pasted into inline exception to text nodes only. Also strip not allowed attributes.
282
+ if (marker && marker.name.startsWith('restrictedEditingException:inline:')) {
283
+ const allowedAttributes = editor.config.get('restrictedEditing.allowedAttributes');
284
+ model.change(writer => {
285
+ const content = writer.createDocumentFragment();
286
+ const textNodes = Array.from(writer.createRangeIn(data.content).getItems())
287
+ .filter(node => node.is('$textProxy'));
288
+ for (const item of textNodes) {
289
+ for (const attr of item.getAttributeKeys()) {
290
+ if (!allowedAttributes.includes(attr)) {
291
+ writer.removeAttribute(attr, item);
292
+ }
293
+ }
294
+ writer.append(item, content);
295
+ }
296
+ data.content = content;
297
+ });
298
+ }
248
299
  });
249
300
  // Block clipboard outside exception marker on cut.
250
301
  this.listenTo(viewDoc, 'clipboardOutput', (evt, data) => {
@@ -252,9 +303,12 @@ export class RestrictedEditingModeEditing extends Plugin {
252
303
  evt.stop();
253
304
  }
254
305
  }, { priority: 'high' });
255
- const allowedAttributes = editor.config.get('restrictedEditing.allowedAttributes');
256
- model.schema.addAttributeCheck(onlyAllowAttributesFromList(allowedAttributes));
257
- model.schema.addChildCheck(allowTextOnlyInClipboardHolder());
306
+ // Do not allow pasting/dropping block exception wrapper.
307
+ model.schema.addChildCheck(context => {
308
+ if (context.startsWith('$clipboardHolder')) {
309
+ return false;
310
+ }
311
+ }, 'restrictedEditingException');
258
312
  }
259
313
  /**
260
314
  * Sets up the command toggling which enables or disables commands based on the user selection.
@@ -279,7 +333,7 @@ export class RestrictedEditingModeEditing extends Plugin {
279
333
  }
280
334
  const marker = getMarkerAtPosition(editor, selection.focus);
281
335
  this._disableCommands();
282
- if (isSelectionInMarker(selection, marker)) {
336
+ if (isSelectionInMarker(selection, editor.model, marker)) {
283
337
  this._enableCommands(marker);
284
338
  }
285
339
  }
@@ -288,16 +342,19 @@ export class RestrictedEditingModeEditing extends Plugin {
288
342
  */
289
343
  _enableCommands(marker) {
290
344
  const editor = this.editor;
345
+ const selection = editor.model.document.selection;
291
346
  for (const [commandName, command] of editor.commands) {
292
347
  if (!command.affectsData || this._alwaysEnabled.has(commandName)) {
293
348
  continue;
294
349
  }
295
350
  // Enable ony those commands that are allowed in the exception marker.
296
- if (!this._allowedInException.has(commandName)) {
351
+ // In block exceptions all commands are enabled.
352
+ if (!marker.name.startsWith('restrictedEditingException:block:') &&
353
+ !this._allowedInException.has(commandName)) {
297
354
  continue;
298
355
  }
299
356
  // Do not enable 'delete' and 'deleteForward' commands on the exception marker boundaries.
300
- if (isDeleteCommandOnMarkerBoundaries(commandName, editor.model.document.selection, marker.getRange())) {
357
+ if (isDeleteCommandOnMarkerBoundaries(commandName, selection, getExceptionRange(marker, editor.model))) {
301
358
  continue;
302
359
  }
303
360
  command.clearForceDisabled(COMMAND_FORCE_DISABLE_ID);
@@ -320,7 +377,10 @@ export class RestrictedEditingModeEditing extends Plugin {
320
377
  * Helper for handling Ctrl+A keydown behaviour.
321
378
  */
322
379
  function getSelectAllHandler(editor) {
323
- return (_, cancel) => {
380
+ return (eventInfo, domEventData) => {
381
+ if (getCode(domEventData) != parseKeystroke('Ctrl+A')) {
382
+ return;
383
+ }
324
384
  const model = editor.model;
325
385
  const selection = editor.model.document.selection;
326
386
  const marker = getMarkerAtPosition(editor, selection.focus);
@@ -331,11 +391,13 @@ function getSelectAllHandler(editor) {
331
391
  //
332
392
  // Note: Second Ctrl+A press is also blocked and it won't select the entire text in the editor.
333
393
  const selectionRange = selection.getFirstRange();
334
- const markerRange = marker.getRange();
394
+ const markerRange = getExceptionRange(marker, editor.model);
335
395
  if (markerRange.containsRange(selectionRange, true) || selection.isCollapsed) {
336
- cancel();
396
+ eventInfo.stop();
397
+ domEventData.preventDefault();
398
+ domEventData.stopPropagation();
337
399
  model.change(writer => {
338
- writer.setSelection(marker.getRange());
400
+ writer.setSelection(markerRange);
339
401
  });
340
402
  }
341
403
  };
@@ -348,11 +410,11 @@ function getSelectAllHandler(editor) {
348
410
  * - is on marker end - "deleteForward" - to prevent removing content after marker
349
411
  */
350
412
  function isDeleteCommandOnMarkerBoundaries(commandName, selection, markerRange) {
351
- if (commandName == 'delete' && markerRange.start.isEqual(selection.focus)) {
413
+ if (commandName == 'delete' && selection.isCollapsed && markerRange.start.isTouching(selection.focus)) {
352
414
  return true;
353
415
  }
354
416
  // Only for collapsed selection - non-collapsed selection that extends over a marker is handled elsewhere.
355
- if (commandName == 'deleteForward' && selection.isCollapsed && markerRange.end.isEqual(selection.focus)) {
417
+ if (commandName == 'deleteForward' && selection.isCollapsed && markerRange.end.isTouching(selection.focus)) {
356
418
  return true;
357
419
  }
358
420
  return false;
@@ -378,7 +440,7 @@ function restrictDeleteContent(editor) {
378
440
  return;
379
441
  }
380
442
  // Shrink the selection to the range inside exception marker.
381
- const allowedToDelete = marker.getRange().getIntersection(selection.getFirstRange());
443
+ const allowedToDelete = getExceptionRange(marker, editor.model).getIntersection(selection.getFirstRange());
382
444
  // Some features uses selection passed to model.deleteContent() to set the selection afterwards. For this we need to properly modify
383
445
  // either the document selection using change block...
384
446
  if (selection.is('documentSelection')) {
@@ -448,17 +510,3 @@ function ensureNewMarkerIsFlatPostFixer(editor) {
448
510
  return changeApplied;
449
511
  };
450
512
  }
451
- function onlyAllowAttributesFromList(allowedAttributes) {
452
- return (context, attributeName) => {
453
- if (context.startsWith('$clipboardHolder')) {
454
- return allowedAttributes.includes(attributeName);
455
- }
456
- };
457
- }
458
- function allowTextOnlyInClipboardHolder() {
459
- return (context, childDefinition) => {
460
- if (context.startsWith('$clipboardHolder')) {
461
- return childDefinition.name === '$text';
462
- }
463
- };
464
- }
@@ -3,6 +3,7 @@
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
4
4
  */
5
5
  import { Command } from 'ckeditor5/src/core.js';
6
+ import { getExceptionRange } from './restrictededitingmode/utils.js';
6
7
  /**
7
8
  * The command that allows navigation across the exceptions in the edited document.
8
9
  */
@@ -61,7 +62,7 @@ function getNearestExceptionRange(model, direction) {
61
62
  const markerRanges = [];
62
63
  // Get all exception marker positions that start after/before the selection position.
63
64
  for (const marker of model.markers.getMarkersGroup('restrictedEditingException')) {
64
- const markerRange = marker.getRange();
65
+ const markerRange = getExceptionRange(marker, model);
65
66
  // Checking parent because there two positions <paragraph>foo^</paragraph><paragraph>^bar</paragraph>
66
67
  // are touching but they will represent different markers.
67
68
  const isMarkerRangeTouching = selectionPosition.isTouching(markerRange.start) && selectionPosition.hasSameParentAs(markerRange.start) ||
@@ -6,7 +6,9 @@
6
6
  * @module restricted-editing/standardeditingmodeediting
7
7
  */
8
8
  import { Plugin } from 'ckeditor5/src/core.js';
9
+ import { Matcher } from 'ckeditor5/src/engine.js';
9
10
  import { RestrictedEditingExceptionCommand } from './restrictededitingexceptioncommand.js';
11
+ import { RestrictedEditingExceptionBlockCommand } from './restrictededitingexceptionblockcommand.js';
10
12
  /**
11
13
  * The standard editing mode editing feature.
12
14
  *
@@ -32,15 +34,113 @@ export class StandardEditingModeEditing extends Plugin {
32
34
  */
33
35
  init() {
34
36
  const editor = this.editor;
35
- editor.model.schema.extend('$text', { allowAttributes: ['restrictedEditingException'] });
36
- editor.conversion.for('upcast').elementToAttribute({
37
+ const schema = editor.model.schema;
38
+ schema.extend('$text', { allowAttributes: ['restrictedEditingException'] });
39
+ schema.register('restrictedEditingException', {
40
+ allowWhere: '$container',
41
+ allowContentOf: '$container'
42
+ });
43
+ // Don't allow nesting of block exceptions.
44
+ schema.addChildCheck(context => {
45
+ for (const item of context) {
46
+ if (item.name == 'restrictedEditingException') {
47
+ return false;
48
+ }
49
+ }
50
+ }, 'restrictedEditingException');
51
+ // Don't allow nesting inline exceptions inside block exceptions.
52
+ schema.addAttributeCheck(context => {
53
+ for (const item of context) {
54
+ if (item.name == 'restrictedEditingException') {
55
+ return false;
56
+ }
57
+ }
58
+ }, 'restrictedEditingException');
59
+ // Post-fixer to ensure proper structure.
60
+ editor.model.document.registerPostFixer(writer => {
61
+ const changes = editor.model.document.differ.getChanges();
62
+ const unwrap = new Set();
63
+ const remove = new Set();
64
+ const merge = new Set();
65
+ let changed = false;
66
+ for (const entry of changes) {
67
+ if (entry.type == 'insert') {
68
+ const range = writer.createRange(entry.position, entry.position.getShiftedBy(entry.length));
69
+ for (const child of range.getItems()) {
70
+ if (child.is('element', 'restrictedEditingException')) {
71
+ // Make sure that block exception is not nested or added in invalid place.
72
+ if (!schema.checkChild(writer.createPositionBefore(child), child)) {
73
+ unwrap.add(child);
74
+ }
75
+ else if (child.isEmpty) {
76
+ remove.add(child);
77
+ }
78
+ else {
79
+ merge.add(child);
80
+ }
81
+ }
82
+ else if (child.is('$textProxy') &&
83
+ child.hasAttribute('restrictedEditingException') &&
84
+ !schema.checkAttribute(child, 'restrictedEditingException')) {
85
+ writer.removeAttribute('restrictedEditingException', child);
86
+ changed = true;
87
+ }
88
+ }
89
+ }
90
+ else if (entry.type == 'remove') {
91
+ const parent = entry.position.parent;
92
+ if (parent.is('element', 'restrictedEditingException') && parent.isEmpty) {
93
+ remove.add(parent);
94
+ }
95
+ // Verify if some block exceptions are siblings now after element removed between.
96
+ for (const child of parent.getChildren()) {
97
+ if (child.is('element', 'restrictedEditingException')) {
98
+ merge.add(child);
99
+ }
100
+ }
101
+ }
102
+ }
103
+ for (const child of unwrap) {
104
+ writer.unwrap(child);
105
+ changed = true;
106
+ }
107
+ for (const child of remove) {
108
+ writer.remove(child);
109
+ changed = true;
110
+ }
111
+ for (const child of merge) {
112
+ if (child.root.rootName == '$graveyard') {
113
+ continue;
114
+ }
115
+ const nodeBefore = child.previousSibling;
116
+ const nodeAfter = child.nextSibling;
117
+ if (nodeBefore && nodeBefore.is('element', 'restrictedEditingException')) {
118
+ writer.merge(writer.createPositionBefore(child));
119
+ }
120
+ if (nodeAfter && nodeAfter.is('element', 'restrictedEditingException')) {
121
+ writer.merge(writer.createPositionAfter(child));
122
+ }
123
+ }
124
+ return changed;
125
+ });
126
+ editor.conversion.for('upcast')
127
+ .elementToAttribute({
37
128
  model: 'restrictedEditingException',
38
129
  view: {
39
130
  name: 'span',
40
131
  classes: 'restricted-editing-exception'
41
132
  }
133
+ })
134
+ .elementToElement({
135
+ model: 'restrictedEditingException',
136
+ view: {
137
+ name: 'div',
138
+ classes: 'restricted-editing-exception'
139
+ }
42
140
  });
43
- editor.conversion.for('downcast').attributeToElement({
141
+ registerFallbackUpcastConverter(editor);
142
+ editor.conversion.for('downcast')
143
+ .attributeToElement({
44
144
  model: 'restrictedEditingException',
45
145
  view: (modelAttributeValue, { writer }) => {
46
146
  if (modelAttributeValue) {
@@ -48,8 +148,16 @@ export class StandardEditingModeEditing extends Plugin {
48
148
  return writer.createAttributeElement('span', { class: 'restricted-editing-exception' }, { priority: -10 });
49
149
  }
50
150
  }
151
+ })
152
+ .elementToElement({
153
+ model: 'restrictedEditingException',
154
+ view: {
155
+ name: 'div',
156
+ classes: 'restricted-editing-exception'
157
+ }
51
158
  });
52
159
  editor.commands.add('restrictedEditingException', new RestrictedEditingExceptionCommand(editor));
160
+ editor.commands.add('restrictedEditingExceptionBlock', new RestrictedEditingExceptionBlockCommand(editor));
53
161
  editor.editing.view.change(writer => {
54
162
  for (const root of editor.editing.view.document.roots) {
55
163
  writer.addClass('ck-restricted-editing_mode_standard', root);
@@ -57,3 +165,27 @@ export class StandardEditingModeEditing extends Plugin {
57
165
  });
58
166
  }
59
167
  }
168
+ /**
169
+ * Fallback upcast converter for empty exception span inside a table cell.
170
+ */
171
+ function registerFallbackUpcastConverter(editor) {
172
+ const matcher = new Matcher({ name: 'span', classes: 'restricted-editing-exception' });
173
+ // See: https://github.com/ckeditor/ckeditor5/issues/16376.
174
+ editor.conversion.for('upcast').add(dispatcher => dispatcher.on('element:span', (evt, data, conversionApi) => {
175
+ const matcherResult = matcher.match(data.viewItem);
176
+ if (!matcherResult) {
177
+ return;
178
+ }
179
+ const match = matcherResult.match;
180
+ if (!conversionApi.consumable.test(data.viewItem, match)) {
181
+ return;
182
+ }
183
+ const modelText = conversionApi.writer.createText(' ', { restrictedEditingException: true });
184
+ if (!conversionApi.safeInsert(modelText, data.modelCursor)) {
185
+ return;
186
+ }
187
+ conversionApi.consumable.consume(data.viewItem, match);
188
+ data.modelRange = conversionApi.writer.createRange(data.modelCursor, data.modelCursor.getShiftedBy(modelText.offsetSize));
189
+ data.modelCursor = data.modelRange.end;
190
+ }, { priority: 'low' }));
191
+ }
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import { Plugin } from 'ckeditor5/src/core.js';
9
9
  import { IconContentUnlock } from 'ckeditor5/src/icons.js';
10
- import { ButtonView, MenuBarMenuListItemButtonView } from 'ckeditor5/src/ui.js';
10
+ import { ButtonView, MenuBarMenuListItemButtonView, createDropdown, addToolbarToDropdown } from 'ckeditor5/src/ui.js';
11
11
  /**
12
12
  * The standard editing mode UI feature.
13
13
  *
@@ -31,35 +31,99 @@ export class StandardEditingModeUI extends Plugin {
31
31
  */
32
32
  init() {
33
33
  const editor = this.editor;
34
- editor.ui.componentFactory.add('restrictedEditingException', () => {
35
- const button = this._createButton(ButtonView);
34
+ const componentFactory = editor.ui.componentFactory;
35
+ componentFactory.add('restrictedEditingException:dropdown', locale => {
36
+ const dropdownView = createDropdown(locale);
37
+ const t = locale.t;
38
+ const buttons = [
39
+ componentFactory.create('restrictedEditingException:inline'),
40
+ componentFactory.create('restrictedEditingException:block')
41
+ ];
42
+ for (const button of buttons) {
43
+ button.set({
44
+ withText: true,
45
+ tooltip: false
46
+ });
47
+ }
48
+ addToolbarToDropdown(dropdownView, buttons, {
49
+ enableActiveItemFocusOnDropdownOpen: true,
50
+ isVertical: true,
51
+ ariaLabel: t('Enable editing')
52
+ });
53
+ dropdownView.buttonView.set({
54
+ label: t('Enable editing'),
55
+ icon: IconContentUnlock,
56
+ tooltip: true
57
+ });
58
+ dropdownView.extendTemplate({
59
+ attributes: {
60
+ class: 'ck-restricted-editing-dropdown'
61
+ }
62
+ });
63
+ // Enable button if any of the buttons is enabled.
64
+ dropdownView.bind('isEnabled').toMany(buttons, 'isEnabled', (...areEnabled) => {
65
+ return areEnabled.some(isEnabled => isEnabled);
66
+ });
67
+ // Focus the editable after executing the command.
68
+ this.listenTo(dropdownView, 'execute', () => {
69
+ editor.editing.view.focus();
70
+ });
71
+ return dropdownView;
72
+ });
73
+ componentFactory.add('restrictedEditingException:inline', () => {
74
+ const button = this._createButton('restrictedEditingException', ButtonView);
75
+ button.set({
76
+ tooltip: true,
77
+ isToggleable: true
78
+ });
79
+ return button;
80
+ });
81
+ componentFactory.add('restrictedEditingException:block', () => {
82
+ const button = this._createButton('restrictedEditingExceptionBlock', ButtonView);
36
83
  button.set({
37
84
  tooltip: true,
38
85
  isToggleable: true
39
86
  });
40
87
  return button;
41
88
  });
42
- editor.ui.componentFactory.add('menuBar:restrictedEditingException', () => {
43
- return this._createButton(MenuBarMenuListItemButtonView);
89
+ componentFactory.add('menuBar:restrictedEditingException:inline', () => {
90
+ return this._createButton('restrictedEditingException', MenuBarMenuListItemButtonView);
91
+ });
92
+ componentFactory.add('menuBar:restrictedEditingException:block', () => {
93
+ return this._createButton('restrictedEditingExceptionBlock', MenuBarMenuListItemButtonView);
94
+ });
95
+ // Aliases for backward compatibility.
96
+ componentFactory.add('restrictedEditingException', () => {
97
+ return componentFactory.create('restrictedEditingException:inline');
98
+ });
99
+ componentFactory.add('menuBar:restrictedEditingException', () => {
100
+ return componentFactory.create('menuBar:restrictedEditingException:inline');
44
101
  });
45
102
  }
46
103
  /**
47
104
  * Creates a button for restricted editing exception command to use either in toolbar or in menu bar.
48
105
  */
49
- _createButton(ButtonClass) {
106
+ _createButton(commandName, ButtonClass) {
50
107
  const editor = this.editor;
51
108
  const locale = editor.locale;
52
- const command = this.editor.commands.get('restrictedEditingException');
109
+ const command = this.editor.commands.get(commandName);
53
110
  const view = new ButtonClass(locale);
54
111
  const t = locale.t;
55
112
  view.icon = IconContentUnlock;
56
113
  view.bind('isOn', 'isEnabled').to(command, 'value', 'isEnabled');
57
- view.bind('label').to(command, 'value', value => {
58
- return value ? t('Disable editing') : t('Enable editing');
59
- });
114
+ if (commandName == 'restrictedEditingExceptionBlock') {
115
+ view.bind('label').to(command, 'value', value => {
116
+ return value ? t('Disable block editing') : t('Enable block editing');
117
+ });
118
+ }
119
+ else {
120
+ view.bind('label').to(command, 'value', value => {
121
+ return value ? t('Disable inline editing') : t('Enable inline editing');
122
+ });
123
+ }
60
124
  // Execute the command.
61
125
  this.listenTo(view, 'execute', () => {
62
- editor.execute('restrictedEditingException');
126
+ editor.execute(commandName);
63
127
  editor.editing.view.focus();
64
128
  });
65
129
  return view;