restrictededitingnavigationcommand.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. /**
  2. * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  4. */
  5. import ModelTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/modeltesteditor';
  6. import { setData as setModelData, getData as getModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
  7. import RestrictedEditingNavigationCommand from '../src/restrictededitingnavigationcommand';
  8. describe( 'RestrictedEditingNavigationCommand', () => {
  9. let editor, forwardCommand, backwardCommand, model;
  10. beforeEach( () => {
  11. return ModelTestEditor
  12. .create()
  13. .then( newEditor => {
  14. editor = newEditor;
  15. model = editor.model;
  16. forwardCommand = new RestrictedEditingNavigationCommand( editor, 'forward' );
  17. backwardCommand = new RestrictedEditingNavigationCommand( editor, 'backward' );
  18. model.schema.register( 'paragraph', { inheritAllFrom: '$block' } );
  19. editor.model.schema.extend( '$text', { allowAttributes: [ 'restrictedEditingException' ] } );
  20. } );
  21. } );
  22. afterEach( () => {
  23. forwardCommand.destroy();
  24. backwardCommand.destroy();
  25. return editor.destroy();
  26. } );
  27. describe( 'forward command', () => {
  28. describe( 'isEnabled', () => {
  29. describe( 'collapsed selection', () => {
  30. it( 'should be true when there is a marker after the selection position', () => {
  31. setModelData( model, '<paragraph>[]foo bar baz</paragraph>' );
  32. const paragraph = model.document.getRoot().getChild( 0 );
  33. // <paragraph>[]foo <marker≥bar</marker> baz</paragraph>
  34. model.change( writer => {
  35. writer.addMarker( 'restricted-editing-exception:1', {
  36. range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ),
  37. usingOperation: true,
  38. affectsData: true
  39. } );
  40. } );
  41. expect( forwardCommand.isEnabled ).to.be.true;
  42. } );
  43. it( 'should be false when there is no marker after the selection position', () => {
  44. setModelData( model, '<paragraph>foo bar baz[]</paragraph>' );
  45. const paragraph = model.document.getRoot().getChild( 0 );
  46. // <paragraph>foo <marker≥bar</marker> baz[]</paragraph>
  47. model.change( writer => {
  48. writer.addMarker( 'restricted-editing-exception:1', {
  49. range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ),
  50. usingOperation: true,
  51. affectsData: true
  52. } );
  53. } );
  54. expect( forwardCommand.isEnabled ).to.be.false;
  55. } );
  56. it( 'should be false when the selection position is at a marker start and there is no more markers', () => {
  57. setModelData( model, '<paragraph>foo []bar baz</paragraph>' );
  58. const paragraph = model.document.getRoot().getChild( 0 );
  59. // <paragraph>foo []<marker≥bar</marker> baz</paragraph>
  60. model.change( writer => {
  61. writer.addMarker( 'restricted-editing-exception:1', {
  62. range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ),
  63. usingOperation: true,
  64. affectsData: true
  65. } );
  66. } );
  67. expect( forwardCommand.isEnabled ).to.be.false;
  68. } );
  69. it( 'should be false when the selection position is in a marker and there is no more markers', () => {
  70. setModelData( model, '<paragraph>foo b[]ar baz</paragraph>' );
  71. const paragraph = model.document.getRoot().getChild( 0 );
  72. // <paragraph>foo <marker≥b[]ar</marker> baz</paragraph>
  73. model.change( writer => {
  74. writer.addMarker( 'restricted-editing-exception:1', {
  75. range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ),
  76. usingOperation: true,
  77. affectsData: true
  78. } );
  79. } );
  80. expect( forwardCommand.isEnabled ).to.be.false;
  81. } );
  82. it( 'should be false when the selection position is at a marker end and there is no more markers', () => {
  83. setModelData( model, '<paragraph>foo bar[] baz</paragraph>' );
  84. const paragraph = model.document.getRoot().getChild( 0 );
  85. // <paragraph>foo <marker≥bar</marker>[] baz</paragraph>
  86. model.change( writer => {
  87. writer.addMarker( 'restricted-editing-exception:1', {
  88. range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ),
  89. usingOperation: true,
  90. affectsData: true
  91. } );
  92. } );
  93. expect( forwardCommand.isEnabled ).to.be.false;
  94. } );
  95. } );
  96. describe( 'expanded selection', () => {
  97. it( 'should be true when there is a marker after the first selection position', () => {
  98. setModelData( model, '<paragraph>[fo]o bar baz</paragraph>' );
  99. const paragraph = model.document.getRoot().getChild( 0 );
  100. // <paragraph>[fo]o <marker≥bar</marker> baz</paragraph>
  101. model.change( writer => {
  102. writer.addMarker( 'restricted-editing-exception:1', {
  103. range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ),
  104. usingOperation: true,
  105. affectsData: true
  106. } );
  107. } );
  108. expect( forwardCommand.isEnabled ).to.be.true;
  109. } );
  110. it( 'should be true when the selection overlaps the marker but the start position is before it', () => {
  111. setModelData( model, '<paragraph>[foo ba]r baz</paragraph>' );
  112. const paragraph = model.document.getRoot().getChild( 0 );
  113. // <paragraph>[foo <marker≥ba]r</marker> baz</paragraph>
  114. model.change( writer => {
  115. writer.addMarker( 'restricted-editing-exception:1', {
  116. range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ),
  117. usingOperation: true,
  118. affectsData: true
  119. } );
  120. } );
  121. expect( forwardCommand.isEnabled ).to.be.true;
  122. } );
  123. it( 'should be false when the selection overlaps the marker but the start position is after it', () => {
  124. setModelData( model, '<paragraph>foo ba[r baz]</paragraph>' );
  125. const paragraph = model.document.getRoot().getChild( 0 );
  126. // <paragraph>foo <marker≥ba[r</marker> baz]</paragraph>
  127. model.change( writer => {
  128. writer.addMarker( 'restricted-editing-exception:1', {
  129. range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ),
  130. usingOperation: true,
  131. affectsData: true
  132. } );
  133. } );
  134. expect( forwardCommand.isEnabled ).to.be.false;
  135. } );
  136. } );
  137. } );
  138. describe( 'execute()', () => {
  139. describe( 'collapsed selection', () => {
  140. it( 'should move to the next marker', () => {
  141. setModelData( model, '<paragraph>[]foo bar baz</paragraph>' );
  142. const paragraph = model.document.getRoot().getChild( 0 );
  143. // <paragraph>[]foo <marker≥bar</marker> baz</paragraph>
  144. model.change( writer => {
  145. writer.addMarker( 'restricted-editing-exception:1', {
  146. range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ),
  147. usingOperation: true,
  148. affectsData: true
  149. } );
  150. } );
  151. // <paragraph>[]foo <marker≥bar</marker> <marker≥baz</marker≥</paragraph>
  152. model.change( writer => {
  153. writer.addMarker( 'restricted-editing-exception:2', {
  154. range: writer.createRange( writer.createPositionAt( paragraph, 8 ), writer.createPositionAt( paragraph, 11 ) ),
  155. usingOperation: true,
  156. affectsData: true
  157. } );
  158. } );
  159. forwardCommand.execute();
  160. expect( getModelData( model ) ).to.equal( '<paragraph>foo []bar baz</paragraph>' );
  161. forwardCommand.execute();
  162. expect( getModelData( model ) ).to.equal( '<paragraph>foo bar []baz</paragraph>' );
  163. } );
  164. it( 'should move to the closest marker when created in a reverse order', () => {
  165. setModelData( model, '<paragraph>[]foo bar baz</paragraph>' );
  166. const paragraph = model.document.getRoot().getChild( 0 );
  167. // <paragraph>[]foo bar <marker≥baz</marker≥</paragraph>
  168. model.change( writer => {
  169. writer.addMarker( 'restricted-editing-exception:2', {
  170. range: writer.createRange( writer.createPositionAt( paragraph, 8 ), writer.createPositionAt( paragraph, 11 ) ),
  171. usingOperation: true,
  172. affectsData: true
  173. } );
  174. } );
  175. // <paragraph>[]foo <marker≥bar</marker> <marker≥baz</marker≥</paragraph>
  176. model.change( writer => {
  177. writer.addMarker( 'restricted-editing-exception:1', {
  178. range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ),
  179. usingOperation: true,
  180. affectsData: true
  181. } );
  182. } );
  183. forwardCommand.execute();
  184. expect( getModelData( model ) ).to.equal( '<paragraph>foo []bar baz</paragraph>' );
  185. forwardCommand.execute();
  186. expect( getModelData( model ) ).to.equal( '<paragraph>foo bar []baz</paragraph>' );
  187. } );
  188. } );
  189. describe( 'expanded selection', () => {
  190. it( 'should move to the next marker when the selection end overlaps the marker', () => {
  191. setModelData( model, '<paragraph>[foo b]ar baz</paragraph>' );
  192. const paragraph = model.document.getRoot().getChild( 0 );
  193. // <paragraph>[foo <marker≥b]ar</marker> baz</paragraph>
  194. model.change( writer => {
  195. writer.addMarker( 'restricted-editing-exception:1', {
  196. range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ),
  197. usingOperation: true,
  198. affectsData: true
  199. } );
  200. } );
  201. // <paragraph>[foo <marker≥b]ar</marker> <marker≥baz</marker≥</paragraph>
  202. model.change( writer => {
  203. writer.addMarker( 'restricted-editing-exception:2', {
  204. range: writer.createRange( writer.createPositionAt( paragraph, 8 ), writer.createPositionAt( paragraph, 11 ) ),
  205. usingOperation: true,
  206. affectsData: true
  207. } );
  208. } );
  209. forwardCommand.execute();
  210. expect( getModelData( model ) ).to.equal( '<paragraph>foo []bar baz</paragraph>' );
  211. forwardCommand.execute();
  212. expect( getModelData( model ) ).to.equal( '<paragraph>foo bar []baz</paragraph>' );
  213. } );
  214. it( 'should move to the next marker when the selection start overlaps the marker', () => {
  215. setModelData( model, '<paragraph>foo b[ar b]az</paragraph>' );
  216. const paragraph = model.document.getRoot().getChild( 0 );
  217. // <paragraph>foo <marker≥b[ar</marker> b]az</paragraph>
  218. model.change( writer => {
  219. writer.addMarker( 'restricted-editing-exception:1', {
  220. range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ),
  221. usingOperation: true,
  222. affectsData: true
  223. } );
  224. } );
  225. // <paragraph>foo <marker≥b[ar</marker> <marker≥b]az</marker≥</paragraph>
  226. model.change( writer => {
  227. writer.addMarker( 'restricted-editing-exception:2', {
  228. range: writer.createRange( writer.createPositionAt( paragraph, 8 ), writer.createPositionAt( paragraph, 11 ) ),
  229. usingOperation: true,
  230. affectsData: true
  231. } );
  232. } );
  233. forwardCommand.execute();
  234. expect( getModelData( model ) ).to.equal( '<paragraph>foo bar []baz</paragraph>' );
  235. } );
  236. } );
  237. } );
  238. } );
  239. describe( 'backward command', () => {
  240. describe( 'isEnabled', () => {
  241. describe( 'collapsed selection', () => {
  242. it( 'should be true when there is a marker before the selection position', () => {
  243. setModelData( model, '<paragraph>foo bar baz[]</paragraph>' );
  244. const paragraph = model.document.getRoot().getChild( 0 );
  245. // <paragraph>foo <marker≥bar</marker> baz</paragraph>
  246. model.change( writer => {
  247. writer.addMarker( 'restricted-editing-exception:1', {
  248. range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ),
  249. usingOperation: true,
  250. affectsData: true
  251. } );
  252. } );
  253. expect( backwardCommand.isEnabled ).to.be.true;
  254. } );
  255. it( 'should be false when there is no marker before the selection position', () => {
  256. setModelData( model, '<paragraph>[]foo bar baz</paragraph>' );
  257. const paragraph = model.document.getRoot().getChild( 0 );
  258. // <paragraph>[]foo <marker≥bar</marker> baz</paragraph>
  259. model.change( writer => {
  260. writer.addMarker( 'restricted-editing-exception:1', {
  261. range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ),
  262. usingOperation: true,
  263. affectsData: true
  264. } );
  265. } );
  266. expect( backwardCommand.isEnabled ).to.be.false;
  267. } );
  268. it( 'should be false when the selection position is at a marker end and there is no more markers', () => {
  269. setModelData( model, '<paragraph>foo bar[] baz</paragraph>' );
  270. const paragraph = model.document.getRoot().getChild( 0 );
  271. // <paragraph>foo <marker≥bar</marker>[] baz</paragraph>
  272. model.change( writer => {
  273. writer.addMarker( 'restricted-editing-exception:1', {
  274. range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ),
  275. usingOperation: true,
  276. affectsData: true
  277. } );
  278. } );
  279. expect( backwardCommand.isEnabled ).to.be.false;
  280. } );
  281. it( 'should be false when the selection position is in a marker and there is no more markers', () => {
  282. setModelData( model, '<paragraph>foo b[]ar baz</paragraph>' );
  283. const paragraph = model.document.getRoot().getChild( 0 );
  284. // <paragraph>foo <marker≥b[]ar</marker> baz</paragraph>
  285. model.change( writer => {
  286. writer.addMarker( 'restricted-editing-exception:1', {
  287. range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ),
  288. usingOperation: true,
  289. affectsData: true
  290. } );
  291. } );
  292. expect( backwardCommand.isEnabled ).to.be.false;
  293. } );
  294. it( 'should be false when the selection position is at a marker start and there is no more markers', () => {
  295. setModelData( model, '<paragraph>foo []bar baz</paragraph>' );
  296. const paragraph = model.document.getRoot().getChild( 0 );
  297. // <paragraph>foo []<marker≥bar</marker> baz</paragraph>
  298. model.change( writer => {
  299. writer.addMarker( 'restricted-editing-exception:1', {
  300. range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ),
  301. usingOperation: true,
  302. affectsData: true
  303. } );
  304. } );
  305. expect( backwardCommand.isEnabled ).to.be.false;
  306. } );
  307. } );
  308. describe( 'expanded selection', () => {
  309. it( 'should be true when there is a marker before the first selection position', () => {
  310. setModelData( model, '<paragraph>foo bar b[az]</paragraph>' );
  311. const paragraph = model.document.getRoot().getChild( 0 );
  312. // <paragraph>[fo]o <marker≥bar</marker> b[az]</paragraph>
  313. model.change( writer => {
  314. writer.addMarker( 'restricted-editing-exception:1', {
  315. range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ),
  316. usingOperation: true,
  317. affectsData: true
  318. } );
  319. } );
  320. expect( backwardCommand.isEnabled ).to.be.true;
  321. } );
  322. it( 'should be false when the selection overlaps the marker but the start position is after it', () => {
  323. setModelData( model, '<paragraph>foo b[ar baz]</paragraph>' );
  324. const paragraph = model.document.getRoot().getChild( 0 );
  325. // <paragraph>foo <marker≥b[ar</marker> baz]</paragraph>
  326. model.change( writer => {
  327. writer.addMarker( 'restricted-editing-exception:1', {
  328. range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ),
  329. usingOperation: true,
  330. affectsData: true
  331. } );
  332. } );
  333. expect( backwardCommand.isEnabled ).to.be.false;
  334. } );
  335. it( 'should be false when the selection overlaps the marker but the after position is after it', () => {
  336. setModelData( model, '<paragraph>[foo b]ar baz</paragraph>' );
  337. const paragraph = model.document.getRoot().getChild( 0 );
  338. // <paragraph>[foo <marker≥b]ar</marker> baz</paragraph>
  339. model.change( writer => {
  340. writer.addMarker( 'restricted-editing-exception:1', {
  341. range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ),
  342. usingOperation: true,
  343. affectsData: true
  344. } );
  345. } );
  346. expect( backwardCommand.isEnabled ).to.be.false;
  347. } );
  348. } );
  349. } );
  350. describe( 'execute()', () => {
  351. describe( 'collapsed selection', () => {
  352. it( 'should move to the previous marker', () => {
  353. setModelData( model, '<paragraph>foo bar baz[]</paragraph>' );
  354. const paragraph = model.document.getRoot().getChild( 0 );
  355. // <paragraph>foo <marker≥bar</marker> baz[]</paragraph>
  356. model.change( writer => {
  357. writer.addMarker( 'restricted-editing-exception:1', {
  358. range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ),
  359. usingOperation: true,
  360. affectsData: true
  361. } );
  362. } );
  363. // <paragraph>foo <marker≥bar</marker> <marker≥baz</marker≥[]</paragraph>
  364. model.change( writer => {
  365. writer.addMarker( 'restricted-editing-exception:2', {
  366. range: writer.createRange( writer.createPositionAt( paragraph, 8 ), writer.createPositionAt( paragraph, 11 ) ),
  367. usingOperation: true,
  368. affectsData: true
  369. } );
  370. } );
  371. backwardCommand.execute();
  372. expect( getModelData( model ) ).to.equal( '<paragraph>foo []bar baz</paragraph>' );
  373. } );
  374. it( 'should move to the closest previous marker', () => {
  375. setModelData( model, '<paragraph>foo bar baz qux[]</paragraph>' );
  376. const paragraph = model.document.getRoot().getChild( 0 );
  377. // <paragraph>foo <marker≥bar</marker> baz qux[]</paragraph>
  378. model.change( writer => {
  379. writer.addMarker( 'restricted-editing-exception:1', {
  380. range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ),
  381. usingOperation: true,
  382. affectsData: true
  383. } );
  384. } );
  385. // <paragraph>foo <marker≥bar</marker> <marker≥baz</marker≥ qux[]</paragraph>
  386. model.change( writer => {
  387. writer.addMarker( 'restricted-editing-exception:2', {
  388. range: writer.createRange( writer.createPositionAt( paragraph, 8 ), writer.createPositionAt( paragraph, 11 ) ),
  389. usingOperation: true,
  390. affectsData: true
  391. } );
  392. } );
  393. backwardCommand.execute();
  394. expect( getModelData( model ) ).to.equal( '<paragraph>foo bar []baz qux</paragraph>' );
  395. backwardCommand.execute();
  396. expect( getModelData( model ) ).to.equal( '<paragraph>foo []bar baz qux</paragraph>' );
  397. } );
  398. it( 'should move to the closest previous marker when created in a reverse order', () => {
  399. setModelData( model, '<paragraph>foo bar baz qux[]</paragraph>' );
  400. const paragraph = model.document.getRoot().getChild( 0 );
  401. // <paragraph>foo bar <marker≥baz</marker≥ qux[]</paragraph>
  402. model.change( writer => {
  403. writer.addMarker( 'restricted-editing-exception:2', {
  404. range: writer.createRange( writer.createPositionAt( paragraph, 8 ), writer.createPositionAt( paragraph, 11 ) ),
  405. usingOperation: true,
  406. affectsData: true
  407. } );
  408. } );
  409. // <paragraph>foo <marker≥bar</marker> <marker≥baz</marker≥ qux[]</paragraph>
  410. model.change( writer => {
  411. writer.addMarker( 'restricted-editing-exception:1', {
  412. range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ),
  413. usingOperation: true,
  414. affectsData: true
  415. } );
  416. } );
  417. backwardCommand.execute();
  418. expect( getModelData( model ) ).to.equal( '<paragraph>foo bar []baz qux</paragraph>' );
  419. backwardCommand.execute();
  420. expect( getModelData( model ) ).to.equal( '<paragraph>foo []bar baz qux</paragraph>' );
  421. } );
  422. } );
  423. describe( 'expanded selection', () => {
  424. it( 'should move to the previous marker when the selection end overlaps the marker', () => {
  425. setModelData( model, '<paragraph>foo bar b[az]</paragraph>' );
  426. const paragraph = model.document.getRoot().getChild( 0 );
  427. // <paragraph>foo <marker≥bar</marker> b[az]</paragraph>
  428. model.change( writer => {
  429. writer.addMarker( 'restricted-editing-exception:1', {
  430. range: writer.createRange( writer.createPositionAt( paragraph, 4 ), writer.createPositionAt( paragraph, 7 ) ),
  431. usingOperation: true,
  432. affectsData: true
  433. } );
  434. } );
  435. // <paragraph>foo <marker≥bar</marker> <marker≥b[az</marker≥]</paragraph>
  436. model.change( writer => {
  437. writer.addMarker( 'restricted-editing-exception:2', {
  438. range: writer.createRange( writer.createPositionAt( paragraph, 8 ), writer.createPositionAt( paragraph, 11 ) ),
  439. usingOperation: true,
  440. affectsData: true
  441. } );
  442. } );
  443. backwardCommand.execute();
  444. expect( getModelData( model ) ).to.equal( '<paragraph>foo []bar baz</paragraph>' );
  445. } );
  446. } );
  447. } );
  448. } );
  449. } );