Skip to content

Commit

Permalink
Merge pull request tinymce#1128 in TINYMCE/tinymce from TINY-2995 to …
Browse files Browse the repository at this point in the history
…master

* commit '3159858553586d8a5aef83d153ef2854218267e6':
  TINY-2995: added missing license header
  TINY-2995: added more tests
  TINY-2995: updated changelog
  TINY-2995: fixed typos in test
  TINY-2995: added cef override for home/end keys
  • Loading branch information
Johan Sörlin committed Mar 13, 2019
2 parents 62430fc + 3159858 commit 2d5a97a
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 10 deletions.
1 change: 1 addition & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Version 5.0.3 (TBD)
Fixed an issue where dialog collection items (eg: emojis, special chars) couldn't be selected with touch devices #TINY-3444
Fixed a type error introduced in 5.0.2 when calling editor.getContent() with nested bookmarks #TINY-3400
Fixed an issue that prevented default icons from being overridden #TINY-3449
Fixed an issue where home/end keys wouldn't move the caret correctly before/after content editable false inline elements. #TINY-2995
Version 5.0.2 (2019-03-05)
Added presentation and document presets to `htmlpanel` dialog component #TINY-2694
Added missing fixed_toolbar_container setting has been reimplemented in the Silver theme #TINY-2712
Expand Down
2 changes: 2 additions & 0 deletions src/core/main/ts/api/util/VK.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export default {
SPACEBAR: 32,
TAB: 9,
UP: 38,
END: 35,
HOME: 36,

modifierPressed (e: KeyboardEvent): boolean {
return e.shiftKey || e.ctrlKey || e.altKey || this.metaKeyPressed(e);
Expand Down
42 changes: 32 additions & 10 deletions src/core/main/ts/keyboard/CefNavigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ import { Fun, Arr } from '@ephox/katamari';
import InlineUtils from 'tinymce/core/keyboard/InlineUtils';
import Settings from '../api/Settings';
import { isBeforeContentEditableFalse, isAfterContentEditableFalse, isBeforeTable, isAfterTable } from '../caret/CaretPositionPredicates';
import { Editor } from '../api/Editor';
import { getPositionsUntilNextLine, getPositionsUntilPreviousLine } from '../caret/LineReader';

const isContentEditableFalse = NodeType.isContentEditableFalse;
const getSelectedNode = RangeNodes.getSelectedNode;

const moveToCeFalseHorizontally = (direction: HDirection, editor, getNextPosFn, range): Range => {
const moveToCeFalseHorizontally = (direction: HDirection, editor: Editor, getNextPosFn: (pos: CaretPosition) => CaretPosition, range: Range): Range => {
const forwards = direction === HDirection.Forwards;
const isBeforeContentEditableFalseFn = forwards ? isBeforeContentEditableFalse : isAfterContentEditableFalse;

Expand Down Expand Up @@ -60,7 +62,7 @@ const moveToCeFalseHorizontally = (direction: HDirection, editor, getNextPosFn,
const peekCaretPosition = getNextPosFn(nextCaretPosition);
if (peekCaretPosition && isBeforeContentEditableFalseFn(peekCaretPosition)) {
if (CaretUtils.isMoveInsideSameBlock(nextCaretPosition, peekCaretPosition)) {
return CefUtils.showCaret(direction, editor, peekCaretPosition.getNode(!forwards), forwards, true);
return CefUtils.showCaret(direction, editor, peekCaretPosition.getNode(!forwards) as Element, forwards, true);
}
}

Expand All @@ -71,7 +73,9 @@ const moveToCeFalseHorizontally = (direction: HDirection, editor, getNextPosFn,
return null;
};

const moveToCeFalseVertically = function (direction: LineWalker.VDirection, editor, walkerFn, range: Range) {
type WalkerFunction = (root: Element, pred: (clientRect: LineWalker.ClientRectLine) => boolean, pos: CaretPosition) => LineWalker.ClientRectLine[];

const moveToCeFalseVertically = (direction: LineWalker.VDirection, editor: Editor, walkerFn: WalkerFunction, range: Range) => {
let caretPosition, linePositions, nextLinePositions;
let closestNextLineRect, caretClientRect, clientX;
let dist1, dist2, contentEditableFalseNode;
Expand Down Expand Up @@ -121,7 +125,7 @@ const moveToCeFalseVertically = function (direction: LineWalker.VDirection, edit
}
};

const createTextBlock = (editor): Element => {
const createTextBlock = (editor: Editor): Element => {
const textBlock = editor.dom.create(Settings.getForcedRootBlock(editor));

if (!Env.ie || Env.ie >= 11) {
Expand All @@ -131,7 +135,7 @@ const createTextBlock = (editor): Element => {
return textBlock;
};

const exitPreBlock = (editor, direction: HDirection, range: Range): void => {
const exitPreBlock = (editor: Editor, direction: HDirection, range: Range): void => {
let pre, caretPos, newBlock;
const caretWalker = CaretWalker(editor.getBody());
const getNextVisualCaretPosition = Fun.curry(CaretUtils.getVisualCaretPosition, caretWalker.next);
Expand Down Expand Up @@ -164,7 +168,7 @@ const exitPreBlock = (editor, direction: HDirection, range: Range): void => {
}
};

const getHorizontalRange = (editor, forward: boolean): Range => {
const getHorizontalRange = (editor: Editor, forward: boolean): Range => {
const caretWalker = CaretWalker(editor.getBody());
const getNextVisualCaretPosition = Fun.curry(CaretUtils.getVisualCaretPosition, caretWalker.next);
const getPrevVisualCaretPosition = Fun.curry(CaretUtils.getVisualCaretPosition, caretWalker.prev);
Expand All @@ -186,7 +190,7 @@ const getHorizontalRange = (editor, forward: boolean): Range => {
return null;
};

const getVerticalRange = (editor, down: boolean): Range => {
const getVerticalRange = (editor: Editor, down: boolean): Range => {
let newRange;
const direction = down ? 1 : -1;
const walkerFn = down ? LineWalker.downUntil : LineWalker.upUntil;
Expand All @@ -205,7 +209,7 @@ const getVerticalRange = (editor, down: boolean): Range => {
return null;
};

const moveH = (editor, forward: boolean) => {
const moveH = (editor: Editor, forward: boolean) => {
return () => {
const newRng = getHorizontalRange(editor, forward);

Expand All @@ -218,7 +222,7 @@ const moveH = (editor, forward: boolean) => {
};
};

const moveV = (editor, down: boolean) => {
const moveV = (editor: Editor, down: boolean) => {
return () => {
const newRng = getVerticalRange(editor, down);

Expand All @@ -231,7 +235,25 @@ const moveV = (editor, down: boolean) => {
};
};

const isCefPosition = (forward: boolean) => (pos: CaretPosition) => forward ? isAfterContentEditableFalse(pos) : isBeforeContentEditableFalse(pos);

const moveToLineEndPoint = (editor: Editor, forward: boolean) => {
return () => {
const from = forward ? CaretPosition.fromRangeEnd(editor.selection.getRng()) : CaretPosition.fromRangeStart(editor.selection.getRng());
const result = forward ? getPositionsUntilNextLine(editor.getBody(), from) : getPositionsUntilPreviousLine(editor.getBody(), from);
const to = forward ? Arr.last(result.positions) : Arr.head(result.positions);
return to.filter(isCefPosition(forward)).fold(
Fun.constant(false),
(pos) => {
editor.selection.setRng(pos.toRange());
return true;
}
);
};
};

export {
moveH,
moveV
moveV,
moveToLineEndPoint
};
33 changes: 33 additions & 0 deletions src/core/main/ts/keyboard/HomeEndKeys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
* Licensed under the LGPL or a commercial license.
* For LGPL see License.txt in the project root for license information.
* For commercial licenses see https://www.tiny.cloud/
*/

import * as CefNavigation from './CefNavigation';
import MatchKeys from './MatchKeys';
import VK from '../api/util/VK';
import { Editor } from 'tinymce/core/api/Editor';
import { KeyboardEvent } from '@ephox/dom-globals';

const executeKeydownOverride = (editor: Editor, evt: KeyboardEvent) => {
MatchKeys.execute([
{ keyCode: VK.END, action: CefNavigation.moveToLineEndPoint(editor, true) },
{ keyCode: VK.HOME, action: CefNavigation.moveToLineEndPoint(editor, false) }
], evt).each((_) => {
evt.preventDefault();
});
};

const setup = (editor: Editor) => {
editor.on('keydown', (evt) => {
if (evt.isDefaultPrevented() === false) {
executeKeydownOverride(editor, evt);
}
});
};

export default {
setup
};
2 changes: 2 additions & 0 deletions src/core/main/ts/keyboard/KeyboardOverrides.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import SpaceKey from './SpaceKey';
import CaretContainerInput from 'tinymce/core/caret/CaretContainerInput';
import { Editor } from 'tinymce/core/api/Editor';
import * as InputKeys from './InputKeys';
import HomeEndKeys from './HomeEndKeys';

const setup = (editor: Editor): void => {
const caret = BoundarySelection.setupSelectedState(editor);
Expand All @@ -23,6 +24,7 @@ const setup = (editor: Editor): void => {
EnterKey.setup(editor);
SpaceKey.setup(editor);
InputKeys.setup(editor);
HomeEndKeys.setup(editor);
};

export default {
Expand Down
87 changes: 87 additions & 0 deletions src/core/test/ts/browser/keyboard/HomeEndKeysTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { GeneralSteps, Logger, Pipeline } from '@ephox/agar';
import { TinyActions, TinyApis, TinyLoader } from '@ephox/mcagar';
import Theme from 'tinymce/themes/silver/Theme';
import { UnitTest } from '@ephox/bedrock';
import VK from 'tinymce/core/api/util/VK';

UnitTest.asynctest('browser.tinymce.core.keyboard.HomeEndKeysTest', (success, failure) => {
Theme();

TinyLoader.setup((editor, onSuccess, onFailure) => {
const tinyApis = TinyApis(editor);
const tinyActions = TinyActions(editor);

Pipeline.async({}, [
tinyApis.sFocus,
Logger.t('Home key', GeneralSteps.sequence([
Logger.t('Home key should move caret before cef within the same block', GeneralSteps.sequence([
tinyApis.sSetContent('<p>123</p><p><span contenteditable="false">CEF</span>456</p>'),
tinyApis.sSetCursor([1, 1], 3),
tinyActions.sContentKeystroke(VK.HOME, { }),
tinyApis.sAssertSelection([1, 0], 0, [1, 0], 0)
])),
Logger.t('Home key should move caret from after cef to before cef', GeneralSteps.sequence([
tinyApis.sSetContent('<p><span contenteditable="false">CEF</span></p>'),
tinyApis.sSetCursor([0], 1),
tinyActions.sContentKeystroke(VK.HOME, { }),
tinyApis.sAssertSelection([0, 0], 0, [0, 0], 0)
])),
Logger.t('Home key should move caret to before cef from the start of range', GeneralSteps.sequence([
tinyApis.sSetContent('<p>123</p><p><span contenteditable="false">CEF</span>456<br>789</p>'),
tinyApis.sSetSelection([1, 1], 3, [1, 1], 3),
tinyActions.sContentKeystroke(VK.HOME, { }),
tinyApis.sAssertSelection([1, 0], 0, [1, 0], 0)
])),
Logger.t('Home key should not move caret before cef within the same block if there is a BR in between', GeneralSteps.sequence([
tinyApis.sSetContent('<p>123</p><p><span contenteditable="false">CEF</span><br>456</p>'),
tinyApis.sSetCursor([1, 2], 3),
tinyActions.sContentKeystroke(VK.HOME, { }),
tinyApis.sAssertSelection([1, 2], 3, [1, 2], 3)
])),
Logger.t('Home key should not move caret if there is no cef', GeneralSteps.sequence([
tinyApis.sSetContent('<p>123</p>'),
tinyApis.sSetCursor([0, 0], 1),
tinyActions.sContentKeystroke(VK.HOME, { }),
tinyApis.sAssertSelection([0, 0], 1, [0, 0], 1)
]))
])),
Logger.t('End key', GeneralSteps.sequence([
Logger.t('End key should move caret after cef within the same block', GeneralSteps.sequence([
tinyApis.sSetContent('<p>123<span contenteditable="false">CEF</span></p><p>456</p>'),
tinyApis.sSetCursor([0, 0], 0),
tinyActions.sContentKeystroke(VK.END, { }),
tinyApis.sAssertSelection([0, 2], 1, [0, 2], 1)
])),
Logger.t('End key should move caret from before cef to after cef', GeneralSteps.sequence([
tinyApis.sSetContent('<p><span contenteditable="false">CEF</span></p>'),
tinyApis.sSetCursor([0], 0),
tinyActions.sContentKeystroke(VK.END, { }),
tinyApis.sAssertSelection([0, 1], 1, [0, 1], 1)
])),
Logger.t('End key should move caret to after cef from the end of range', GeneralSteps.sequence([
tinyApis.sSetContent('<p>123<br>456<span contenteditable="false">CEF</span></p>'),
tinyApis.sSetSelection([0, 0], 0, [0, 2], 0),
tinyActions.sContentKeystroke(VK.END, { }),
tinyApis.sAssertSelection([0, 4], 1, [0, 4], 1)
])),
Logger.t('End key should not move caret after cef within the same block if there is a BR in between', GeneralSteps.sequence([
tinyApis.sSetContent('<p>123<br><span contenteditable="false">CEF</span></p><p>456</p>'),
tinyApis.sSetCursor([0, 0], 0),
tinyActions.sContentKeystroke(VK.END, { }),
tinyApis.sAssertSelection([0, 0], 0, [0, 0], 0)
])),
Logger.t('End key should not move caret if there is no cef', GeneralSteps.sequence([
tinyApis.sSetContent('<p>123</p>'),
tinyApis.sSetCursor([0, 0], 1),
tinyActions.sContentKeystroke(VK.END, { }),
tinyApis.sAssertSelection([0, 0], 1, [0, 0], 1)
]))
]))
], onSuccess, onFailure);
}, {
add_unload_trigger: false,
base_url: '/project/js/tinymce',
indent: false
}, success, failure);
}
);

0 comments on commit 2d5a97a

Please sign in to comment.