restrictededitingnavigationcommand.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  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. /**
  6. * @module restricted-editing/restrictededitingnavigationcommand
  7. */
  8. import Command from '@ckeditor/ckeditor5-core/src/command';
  9. /**
  10. * The command that allows navigation across the exceptions in the edited document.
  11. *
  12. * @extends module:core/command~Command
  13. */
  14. export default class RestrictedEditingNavigationCommand extends Command {
  15. /**
  16. * Creates an instance of the command.
  17. *
  18. * @param {module:core/editor/editor~Editor} editor Editor instance.
  19. * @param {String} direction Direction the command works. Can be either `'forward'` or `'backward'`.
  20. */
  21. constructor( editor, direction ) {
  22. super( editor );
  23. /**
  24. * A direction of the command. Can be `'forward'` or `'backward'`.
  25. *
  26. * @readonly
  27. * @private
  28. * @member {String}
  29. */
  30. this._direction = direction;
  31. }
  32. /**
  33. * @inheritDoc
  34. */
  35. refresh() {
  36. this.isEnabled = this._checkEnabled();
  37. }
  38. /**
  39. * Executes the command.
  40. *
  41. * @fires execute
  42. */
  43. execute() {
  44. const position = getNearestExceptionRange( this.editor.model, this._direction );
  45. this.editor.model.change( writer => {
  46. writer.setSelection( position );
  47. } );
  48. }
  49. /**
  50. * Checks whether the command can be enabled in the current context.
  51. *
  52. * @private
  53. * @returns {Boolean} Whether the command should be enabled.
  54. */
  55. _checkEnabled() {
  56. return !!getNearestExceptionRange( this.editor.model, this._direction );
  57. }
  58. }
  59. // Returns the range of the exception marker closest to the last position of the
  60. // model selection.
  61. //
  62. // @param {module:engine/model/model~Model} model
  63. // @param {String} direction Either "forward" or "backward".
  64. // @returns {module:engine/model/range~Range|null}
  65. function getNearestExceptionRange( model, direction ) {
  66. const selection = model.document.selection;
  67. const selectionPosition = selection.getFirstPosition();
  68. const markerRanges = [];
  69. // Get all exception marker positions that start after/before the selection position.
  70. for ( const marker of model.markers.getMarkersGroup( 'restricted-editing-exception' ) ) {
  71. const markerRange = marker.getRange();
  72. // Checking parent because there two positions <paragraph>foo^</paragraph><paragraph>^bar</paragraph>
  73. // are touching but they will represent different markers.
  74. const isMarkerRangeTouching =
  75. selectionPosition.isTouching( markerRange.start ) && selectionPosition.hasSameParentAs( markerRange.start ) ||
  76. selectionPosition.isTouching( markerRange.end ) && selectionPosition.hasSameParentAs( markerRange.end );
  77. // <paragraph>foo <marker≥b[]ar</marker> baz</paragraph>
  78. // <paragraph>foo <marker≥b[ar</marker> ba]z</paragraph>
  79. // <paragraph>foo <marker≥bar</marker>[] baz</paragraph>
  80. // <paragraph>foo []<marker≥bar</marker> baz</paragraph>
  81. if ( markerRange.containsPosition( selectionPosition ) || isMarkerRangeTouching ) {
  82. continue;
  83. }
  84. if ( direction === 'forward' && markerRange.start.isAfter( selectionPosition ) ) {
  85. markerRanges.push( markerRange );
  86. } else if ( direction === 'backward' && markerRange.end.isBefore( selectionPosition ) ) {
  87. markerRanges.push( markerRange );
  88. }
  89. }
  90. if ( !markerRanges.length ) {
  91. return null;
  92. }
  93. // Get the marker closest to the selection position among many. To know that, we need to sort
  94. // them first.
  95. return markerRanges.sort( ( rangeA, rangeB ) => {
  96. if ( direction === 'forward' ) {
  97. return rangeA.start.isAfter( rangeB.start ) ? 1 : -1;
  98. } else {
  99. return rangeA.start.isBefore( rangeB.start ) ? 1 : -1;
  100. }
  101. } ).shift();
  102. }