Compare commits
51 Commits
develop
...
page-down-
Author | SHA1 | Date |
---|---|---|
John McLear | 2f08608649 | |
John McLear | 14e24f5a3c | |
webzwo0i | 996f0e8a1d | |
John McLear | 01fe885adc | |
John McLear | 34c7fb0c46 | |
John McLear | a3e6a44643 | |
John McLear | 79630c7ab6 | |
John McLear | 6d8a28b993 | |
John McLear | 26c733e237 | |
John McLear | 2fff1c473f | |
John McLear | 170dd43e8b | |
John McLear | f3971c6c3e | |
John McLear | c91c227a86 | |
John McLear | d1222bb9a7 | |
John McLear | a75decf33c | |
John McLear | 1991c439f2 | |
John McLear | d41b5ec96d | |
John McLear | ea010c92d5 | |
John McLear | 81e4c14fc0 | |
John McLear | e48f2d0190 | |
John McLear | 8803c23a2d | |
John McLear | eba5827092 | |
John McLear | ffce4d32cb | |
John McLear | 81e50061dc | |
John McLear | 8e5c124f5e | |
John McLear | 66d3d6a02d | |
John McLear | d930a12e37 | |
John McLear | 8300909369 | |
John McLear | e4404d702e | |
John McLear | cdce13ca25 | |
John McLear | a30b120b0e | |
John McLear | e6f8356500 | |
John McLear | 75509ce96e | |
John McLear | 5a425d7e69 | |
John McLear | dda5e03f7e | |
John McLear | da26839ccb | |
John McLear | 6d1662a4da | |
John McLear | 1bd7bb94d1 | |
John McLear | f1a66cd25b | |
John McLear | 6c000472af | |
John McLear | 3eab0df189 | |
John McLear | 5cd4dc1af2 | |
webzwo0i | db26ea218c | |
John McLear | af6a699db4 | |
John McLear | 0fdc13a6ad | |
John McLear | 0b9962c6c6 | |
John McLear | 92450e071d | |
John McLear | a536b311e4 | |
John McLear | 0253b54803 | |
John McLear | d6075bd042 | |
John McLear | f6d9c7e705 |
|
@ -1,3 +1,4 @@
|
|||
'use strict';
|
||||
/**
|
||||
* This code is mostly from the old Etherpad. Please help us to comment this code.
|
||||
* This helps other people to understand this code better and helps them to improve it.
|
||||
|
@ -58,6 +59,7 @@ function Ace2Inner() {
|
|||
const DEBUG = false; // $$ build script replaces the string "var DEBUG=true;//$$" with "var DEBUG=false;"
|
||||
// changed to false
|
||||
let isSetUp = false;
|
||||
let lastPageUpOrDownEvent;
|
||||
|
||||
const THE_TAB = ' '; // 4
|
||||
const MAX_LIST_LEVEL = 16;
|
||||
|
@ -1440,6 +1442,13 @@ function Ace2Inner() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the node and index into this node that corresponds to a given line number and character
|
||||
* position.
|
||||
*
|
||||
* @params {[number, number]} lineAndChar an array of the form [row, col]
|
||||
* @returns {{node: HTMLElement, index: number, maxIndex: number}}
|
||||
*/
|
||||
function getPointForLineAndChar(lineAndChar) {
|
||||
const line = lineAndChar[0];
|
||||
let charsLeft = lineAndChar[1];
|
||||
|
@ -1454,8 +1463,9 @@ function Ace2Inner() {
|
|||
const lineNode = lineEntry.lineNode;
|
||||
let n = lineNode;
|
||||
let after = false;
|
||||
// at [x, 0] of a line with line attributes
|
||||
if (charsLeft === 0) {
|
||||
let index = 0;
|
||||
const index = 0;
|
||||
return {
|
||||
node: lineNode,
|
||||
index,
|
||||
|
@ -3047,53 +3057,185 @@ function Ace2Inner() {
|
|||
}
|
||||
if ((evt.which == 36 && evt.ctrlKey == true) && padShortcutEnabled.ctrlHome) { scroll.setScrollY(0); } // Control Home send to Y = 0
|
||||
if ((evt.which == 33 || evt.which == 34) && type == 'keydown' && !evt.ctrlKey) {
|
||||
evt.preventDefault(); // This is required, browsers will try to do normal default behavior on page up / down and the default behavior SUCKS
|
||||
|
||||
const oldVisibleLineRange = scroll.getVisibleLineRange(rep);
|
||||
let topOffset = rep.selStart[0] - oldVisibleLineRange[0];
|
||||
if (topOffset < 0) {
|
||||
topOffset = 0;
|
||||
}
|
||||
// This is required, browsers will try to do normal default behavior on page up / down
|
||||
// and the default behavior SUCKS
|
||||
evt.preventDefault();
|
||||
|
||||
const isPageDown = evt.which === 34;
|
||||
const isPageUp = evt.which === 33;
|
||||
let previousCharacterOffset;
|
||||
const isShiftKey = shiftKey;
|
||||
|
||||
scheduler.setTimeout(() => {
|
||||
const newVisibleLineRange = scroll.getVisibleLineRange(rep); // the visible lines IE 1,10
|
||||
const linesCount = rep.lines.length(); // total count of lines in pad IE 10
|
||||
const numberOfLinesInViewport = newVisibleLineRange[1] - newVisibleLineRange[0]; // How many lines are in the viewport right now?
|
||||
const pageUpOrDownTooSoon = () => {
|
||||
const delay = 250;
|
||||
|
||||
if (isPageUp && padShortcutEnabled.pageUp) {
|
||||
rep.selEnd[0] = rep.selEnd[0] - numberOfLinesInViewport; // move to the bottom line +1 in the viewport (essentially skipping over a page)
|
||||
rep.selStart[0] = rep.selStart[0] - numberOfLinesInViewport; // move to the bottom line +1 in the viewport (essentially skipping over a page)
|
||||
if (!lastPageUpOrDownEvent) {
|
||||
lastPageUpOrDownEvent = Date.now();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isPageDown && padShortcutEnabled.pageDown) { // if we hit page down
|
||||
if (rep.selEnd[0] >= oldVisibleLineRange[0]) { // If the new viewpoint position is actually further than where we are right now
|
||||
rep.selStart[0] = oldVisibleLineRange[1] - 1; // dont go further in the page down than what's visible IE go from 0 to 50 if 50 is visible on screen but dont go below that else we miss content
|
||||
rep.selEnd[0] = oldVisibleLineRange[1] - 1; // dont go further in the page down than what's visible IE go from 0 to 50 if 50 is visible on screen but dont go below that else we miss content
|
||||
const nextValidTime = lastPageUpOrDownEvent + delay;
|
||||
if (Date.now() >= nextValidTime) {
|
||||
lastPageUpOrDownEvent = Date.now();
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isPageUp) {
|
||||
// Approach #99991248928175 to solve this problem....
|
||||
|
||||
// debounce / throttle press and hold page key
|
||||
if (pageUpOrDownTooSoon()) return;
|
||||
|
||||
// only make history of x offset if it's not 0.
|
||||
if (rep.selStart[1] !== 0 && rep.selEnd[1] !== 0) {
|
||||
previousCharacterOffset = [
|
||||
rep.selStart[1],
|
||||
rep.selEnd[1],
|
||||
];
|
||||
} else {
|
||||
previousCharacterOffset = [0, 0];
|
||||
}
|
||||
|
||||
scroll.movePage('up');
|
||||
const modifiedRep = scroll.getFirstVisibleCharacter('up', rep);
|
||||
|
||||
// we set this to false if we're not trying to retain position.
|
||||
let retainPosition = true;
|
||||
|
||||
// if we're highlighting
|
||||
if (isShiftKey) {
|
||||
// if it's a backwards selection IE [5,0][0,0]
|
||||
if (rep.selFocusAtStart) {
|
||||
rep.selStart[0] = modifiedRep.selStart[0];
|
||||
rep.selStart[1] = modifiedRep.selStart[1];
|
||||
}
|
||||
// if it's a forward selection IE [0,0][5,0]
|
||||
if (!rep.selFocusAtStart) {
|
||||
rep.selEnd[0] = rep.selStart[0];
|
||||
rep.selEnd[1] = rep.selStart[1];
|
||||
rep.selStart[0] = modifiedRep.selStart[0];
|
||||
rep.selStart[1] = modifiedRep.selStart[1];
|
||||
|
||||
rep.selFocusAtStart = true;
|
||||
}
|
||||
}
|
||||
|
||||
// ensure min and max
|
||||
if (rep.selEnd[0] < 0) {
|
||||
rep.selEnd[0] = 0;
|
||||
}
|
||||
if (rep.selStart[0] < 0) {
|
||||
rep.selStart[0] = 0;
|
||||
}
|
||||
if (rep.selEnd[0] >= linesCount) {
|
||||
rep.selEnd[0] = linesCount - 1;
|
||||
}
|
||||
updateBrowserSelectionFromRep();
|
||||
const myselection = document.getSelection(); // get the current caret selection, can't use rep. here because that only gives us the start position not the current
|
||||
let caretOffsetTop = myselection.focusNode.parentNode.offsetTop || myselection.focusNode.offsetTop; // get the carets selection offset in px IE 214
|
||||
// if we're not pressing and holding shift, destroy the selection
|
||||
if (!shiftKey) {
|
||||
rep.selStart[0] = modifiedRep.selStart[0];
|
||||
rep.selStart[1] = modifiedRep.selStart[1];
|
||||
rep.selEnd[0] = modifiedRep.selEnd[0];
|
||||
rep.selEnd[1] = modifiedRep.selEnd[1];
|
||||
|
||||
// sometimes the first selection is -1 which causes problems (Especially with ep_page_view)
|
||||
// so use focusNode.offsetTop value.
|
||||
if (caretOffsetTop === -1) caretOffsetTop = myselection.focusNode.offsetTop;
|
||||
scroll.setScrollY(caretOffsetTop); // set the scrollY offset of the viewport on the document
|
||||
}, 200);
|
||||
// if the previousCharacterOffset is the same as this time, go to Y 0
|
||||
if (previousCharacterOffset[0] === rep.selStart[1]) {
|
||||
rep.selStart[1] = 0;
|
||||
retainPosition = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (retainPosition && rep.selFocusAtStart) {
|
||||
// top.console.log("RETAINING ON UP");
|
||||
if (previousCharacterOffset[1] > 0) {
|
||||
const lengthOfLine = rep.lines.atIndex(rep.selEnd[0]).width - 1;
|
||||
if (lengthOfLine >= previousCharacterOffset[1]) {
|
||||
rep.selStart[1] = previousCharacterOffset[1];
|
||||
if (!isShiftKey) rep.selEnd[1] = previousCharacterOffset[1];
|
||||
} else {
|
||||
// top.console.log("line isn't long enough..")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// END OF PAGE UP
|
||||
if (isPageDown) {
|
||||
// debounce / throttle press and hold page key
|
||||
if (pageUpOrDownTooSoon()) return;
|
||||
|
||||
// Bottom of document - do nothing if we are at the very end
|
||||
// JM TODO: Check if Linemarker modifies width..
|
||||
const originalPosition = scroll._getViewPortTopBottom();
|
||||
|
||||
scroll.movePage('down');
|
||||
|
||||
const hasMoved = originalPosition.top !== scroll._getViewPortTopBottom().top;
|
||||
// top.console.log("hasMoved", hasMoved)
|
||||
const modifiedRep = scroll.getFirstVisibleCharacter('down', rep);
|
||||
// only make history of x offset if it's not 0.
|
||||
if (rep.selStart[1] !== 0 && rep.selEnd[1] !== 0) {
|
||||
previousCharacterOffset = [
|
||||
rep.selStart[1],
|
||||
rep.selEnd[1],
|
||||
];
|
||||
} else {
|
||||
previousCharacterOffset = [0, 0];
|
||||
}
|
||||
|
||||
// we set this to false if we're not trying to retain position.
|
||||
let retainPosition = true;
|
||||
|
||||
// if it's a backwards selection IE [5,0][0,0]
|
||||
if (hasMoved && isShiftKey && rep.selFocusAtStart) {
|
||||
rep.selEnd[0] = modifiedRep.selStart[0];
|
||||
rep.selEnd[1] = modifiedRep.selStart[1];
|
||||
}
|
||||
|
||||
// if it's a forward selection IE [0,0][5,0]
|
||||
if (hasMoved && isShiftKey && !rep.selFocusAtStart) {
|
||||
rep.selEnd[0] = modifiedRep.selEnd[0];
|
||||
rep.selEnd[1] = modifiedRep.selEnd[1];
|
||||
}
|
||||
|
||||
// if we're not pressing and holding shift, destroy the selection
|
||||
if (!isShiftKey && hasMoved) {
|
||||
rep.selStart[0] = modifiedRep.selStart[0];
|
||||
rep.selStart[1] = modifiedRep.selStart[1];
|
||||
rep.selEnd[0] = modifiedRep.selEnd[0];
|
||||
rep.selEnd[1] = modifiedRep.selEnd[1];
|
||||
}
|
||||
|
||||
// move to last character WITH selection
|
||||
if (!hasMoved && isShiftKey && rep.selFocusAtStart) {
|
||||
// we're at the bottom so select the last bit of content.
|
||||
rep.selStart[0] = rep.selEnd[0];
|
||||
rep.selStart[1] = rep.selEnd[1];
|
||||
rep.selEnd[0] = rep.lines.length() - 1;
|
||||
rep.selEnd[1] = rep.lines.atIndex(rep.selStart[0]).length;
|
||||
retainPosition = false;
|
||||
rep.selFocusAtStart = false;
|
||||
}
|
||||
|
||||
// destroy selection if we're at the end
|
||||
if (!hasMoved && isShiftKey && !rep.selFocusAtStart) {
|
||||
// we're at the bottom so select the last bit of content.
|
||||
rep.selEnd[0] = rep.lines.length() - 1;
|
||||
rep.selEnd[1] = rep.lines.atIndex(rep.selStart[0]).length;
|
||||
retainPosition = false;
|
||||
}
|
||||
|
||||
// move to last character without selection
|
||||
if (!hasMoved && !isShiftKey) {
|
||||
rep.selStart[0] = rep.lines.length() - 1;
|
||||
rep.selStart[1] = rep.lines.atIndex(rep.lines.length() - 1).length;
|
||||
rep.selEnd[0] = rep.lines.length() - 1;
|
||||
rep.selEnd[1] = rep.lines.atIndex(rep.lines.length() - 1).length;
|
||||
retainPosition = false;
|
||||
}
|
||||
|
||||
if (retainPosition && !rep.selFocusAtStart) {
|
||||
if (previousCharacterOffset[1] > 0) {
|
||||
const lengthOfLine = rep.lines.atIndex(rep.selEnd[0]).width - 1;
|
||||
if (lengthOfLine >= previousCharacterOffset[1]) {
|
||||
if (!isShiftKey) rep.selStart[1] = previousCharacterOffset[1];
|
||||
rep.selEnd[1] = previousCharacterOffset[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
updateBrowserSelectionFromRep();
|
||||
}
|
||||
|
||||
// scroll to viewport when user presses arrow keys and caret is out of the viewport
|
||||
|
@ -3225,7 +3367,7 @@ function Ace2Inner() {
|
|||
// each of which has node (a magicdom node), index, and maxIndex. If the node
|
||||
// is a text node, maxIndex is the length of the text; else maxIndex is 1.
|
||||
// index is between 0 and maxIndex, inclusive.
|
||||
var browserSelection = window.getSelection();
|
||||
const browserSelection = window.getSelection();
|
||||
if (!browserSelection || browserSelection.type === 'None' ||
|
||||
browserSelection.rangeCount === 0) {
|
||||
return null;
|
||||
|
@ -3284,7 +3426,7 @@ function Ace2Inner() {
|
|||
};
|
||||
}
|
||||
}
|
||||
var selection = {};
|
||||
const selection = {};
|
||||
selection.startPoint = pointFromRangeBound(range.startContainer, range.startOffset);
|
||||
selection.endPoint = pointFromRangeBound(range.endContainer, range.endOffset);
|
||||
selection.focusAtStart = (((range.startContainer != range.endContainer) || (range.startOffset != range.endOffset)) && browserSelection.anchorNode && (browserSelection.anchorNode == range.endContainer) && (browserSelection.anchorOffset == range.endOffset));
|
||||
|
@ -3373,7 +3515,7 @@ function Ace2Inner() {
|
|||
browserSelection.collapse(end.container, end.offset);
|
||||
browserSelection.extend(start.container, start.offset);
|
||||
} else {
|
||||
var range = doc.createRange();
|
||||
const range = doc.createRange();
|
||||
range.setStart(start.container, start.offset);
|
||||
range.setEnd(end.container, end.offset);
|
||||
browserSelection.removeAllRanges();
|
||||
|
|
|
@ -273,29 +273,20 @@ Scroll.prototype._triggerScrollWithAnimation =
|
|||
// needed to be completely in view. If the value is greater than 0 and less than or equal to 1,
|
||||
// besides of scrolling the minimum needed to be visible, it scrolls additionally
|
||||
// (viewport height * scrollAmountWhenFocusLineIsOutOfViewport) pixels
|
||||
Scroll.prototype.scrollNodeVerticallyIntoView = function (rep, innerHeight) {
|
||||
Scroll.prototype.scrollNodeVerticallyIntoView = function (rep, innerHeight, isPageUp, isPageDown) {
|
||||
const viewport = this._getViewPortTopBottom();
|
||||
|
||||
// when the selection changes outside of the viewport the browser automatically scrolls the line
|
||||
// to inside of the viewport. Tested on IE, Firefox, Chrome in releases from 2015 until now
|
||||
// So, when the line scrolled gets outside of the viewport we let the browser handle it.
|
||||
const linePosition = caretPosition.getPosition();
|
||||
if (linePosition) {
|
||||
if (isPageUp || isPageDown) {
|
||||
// redraw entire page into view putting rep.selStart[0] at top left
|
||||
const distanceOfTopOfViewport = linePosition.top - viewport.top;
|
||||
const distanceOfBottomOfViewport = viewport.bottom - linePosition.bottom;
|
||||
const caretIsAboveOfViewport = distanceOfTopOfViewport < 0;
|
||||
const caretIsBelowOfViewport = distanceOfBottomOfViewport < 0;
|
||||
if (caretIsAboveOfViewport) {
|
||||
const pixelsToScroll =
|
||||
distanceOfTopOfViewport - this._getPixelsRelativeToPercentageOfViewport(innerHeight, true);
|
||||
this._scrollYPage(pixelsToScroll);
|
||||
} else if (caretIsBelowOfViewport) {
|
||||
const pixelsToScroll = -distanceOfBottomOfViewport +
|
||||
this._getPixelsRelativeToPercentageOfViewport(innerHeight);
|
||||
this._scrollYPage(pixelsToScroll);
|
||||
} else {
|
||||
this.scrollWhenCaretIsInTheLastLineOfViewportWhenNecessary(rep, true, innerHeight);
|
||||
}
|
||||
const pixelsToScroll =
|
||||
distanceOfTopOfViewport - this._getPixelsRelativeToPercentageOfViewport(innerHeight, true);
|
||||
this._scrollYPage(pixelsToScroll - linePosition.height);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -326,22 +317,245 @@ Scroll.prototype._arrowUpWasPressedInTheFirstLineOfTheViewport = function (arrow
|
|||
|
||||
Scroll.prototype.getVisibleLineRange = function (rep) {
|
||||
const viewport = this._getViewPortTopBottom();
|
||||
// console.log("viewport top/bottom: %o", viewport);
|
||||
const obj = {};
|
||||
const self = this;
|
||||
const start = rep.lines.search((e) => self._getLineEntryTopBottom(e, obj).bottom > viewport.top);
|
||||
// return the first line that the top position is greater or equal than
|
||||
// the viewport. That is the first line that is below the viewport bottom.
|
||||
// So the line that is in the bottom of the viewport is the very previous one.
|
||||
let end = rep.lines.search((e) => self._getLineEntryTopBottom(e, obj).top >= viewport.bottom);
|
||||
let end = rep.lines.search((e) => self._getLineEntryTopBottom(e, obj).bottom >= viewport.bottom);
|
||||
if (end < start) end = start; // unlikely
|
||||
// top.console.log(start+","+(end -1));
|
||||
return [start, end - 1];
|
||||
};
|
||||
|
||||
Scroll.prototype.getPartiallyVisibleLineRange = function (rep) {
|
||||
const viewport = this._getViewPortTopBottom();
|
||||
const obj = {};
|
||||
const self = this;
|
||||
const start = rep.lines.search((e) => self._getLineEntryTopBottom(e, obj).top > viewport.top);
|
||||
let end = rep.lines.search((e) => self._getLineEntryTopBottom(e, obj).top >= viewport.bottom);
|
||||
if (end < start) end = start; // unlikely
|
||||
return [start, end - 1];
|
||||
};
|
||||
|
||||
|
||||
Scroll.prototype.getVisibleCharRange = function (rep) {
|
||||
const lineRange = this.getVisibleLineRange(rep);
|
||||
// top.console.log('char range', 0, rep.lines.offsetOfIndex(lineRange[0]));
|
||||
// top.console.log('char range', 1, rep.lines.offsetOfIndex(lineRange[1]));
|
||||
return [rep.lines.offsetOfIndex(lineRange[0]), rep.lines.offsetOfIndex(lineRange[1])];
|
||||
};
|
||||
|
||||
// moves viewport to next page
|
||||
Scroll.prototype.movePage = function (direction) {
|
||||
const viewport = this._getViewPortTopBottom();
|
||||
|
||||
// linePosition contains top and bottom, might be useful
|
||||
// if the buffer of a fixed value isn't working as intended
|
||||
const linePosition = caretPosition.getPosition();
|
||||
// we need to remember that lineoffset needs to be removed too..
|
||||
const offset = linePosition.bottom - viewport.top;
|
||||
const lineHeight = linePosition.top - linePosition.bottom;
|
||||
let pixelsToScroll = viewport.top - viewport.bottom + offset;
|
||||
if (direction === 'up') {
|
||||
// line height because it might be a very long line..
|
||||
pixelsToScroll = -Math.abs(pixelsToScroll - lineHeight);
|
||||
} else {
|
||||
pixelsToScroll = Math.abs(pixelsToScroll - lineHeight);
|
||||
}
|
||||
|
||||
this.outerWin.scrollBy(0, pixelsToScroll);
|
||||
return;
|
||||
};
|
||||
|
||||
Scroll.prototype.getFirstVisibleCharacter = function (direction, rep) {
|
||||
const viewport = this._getViewPortTopBottom();
|
||||
const editor = parent.document.getElementsByTagName('iframe');
|
||||
// TODO can we make a better guess here or do we need to iterate over every line?
|
||||
const lines = $(editor).contents().find('div');
|
||||
// const currentLine = $(editor).contents().find('#innerdocbody');
|
||||
const currentLine = rep.lines.atIndex(rep.selEnd[0]);
|
||||
const modifiedRep = {};
|
||||
modifiedRep.selStart = [];
|
||||
modifiedRep.selEnd = [];
|
||||
let willGoToNextLine = false;
|
||||
// we have moved the viewport at this point, we want to know which
|
||||
// line is visible?
|
||||
$.each(lines, (index, line) => {
|
||||
// Line height important for supporting long lines that fill viewport.
|
||||
const lineBase = $(line).offset().top + $(line).height();
|
||||
|
||||
// is each line in the viewport?
|
||||
if (lineBase > viewport.top) {
|
||||
modifiedRep.selEnd[0] = index;
|
||||
modifiedRep.selStart[0] = index;
|
||||
modifiedRep.selEnd[1] = 0;
|
||||
modifiedRep.selStart[1] = 0;
|
||||
|
||||
// Important for supporting long lines.
|
||||
if (modifiedRep.selEnd[0] !== rep.selEnd[0]) willGoToNextLine = true;
|
||||
return false; // exit $.each because we found a lovely line :)
|
||||
}
|
||||
});
|
||||
|
||||
if (willGoToNextLine) return modifiedRep;
|
||||
|
||||
// oh dear, looks like the original line is still the first in the viewport..
|
||||
// we will need to move the rep X chars within that original position.
|
||||
modifiedRep.selStart[0] = rep.selStart[0];
|
||||
modifiedRep.selEnd[0] = rep.selEnd[0];
|
||||
|
||||
const numberOfVisibleChars = this.getCountOfVisibleCharsInViewport(currentLine, viewport);
|
||||
|
||||
// TODO, figure out how many chars are visible in line.
|
||||
modifiedRep.selStart[1] = rep.selStart[1] + numberOfVisibleChars || 0;
|
||||
modifiedRep.selEnd[1] = rep.selEnd[1] + numberOfVisibleChars || 0;
|
||||
return modifiedRep;
|
||||
};
|
||||
|
||||
/**
|
||||
* The fully visible characters of a DOM line.
|
||||
* If the whole line is visible, then all characters inside that line are visible, too.
|
||||
* It works by comparing the top and bottom of DOM line parts and the viewport.
|
||||
*
|
||||
* The returned array is of the form:
|
||||
* - first character visible: [0, x]
|
||||
* - first character not visible: [x, y] where x > 0
|
||||
* - last character visible: [x, y] where y == text length of the line
|
||||
* - last character not visible: [x, y] where y < text length of the line
|
||||
* - null, if no character of the line is visible
|
||||
*
|
||||
* Note that only whole lines count, ie in case of subscript/superscript or different font sizes
|
||||
* inside a visible line, the upper or lower most pixels of the union of all characters must
|
||||
* be visible. In other words: the first visible character of a line will always be the first
|
||||
* character of that line and this function won't return an array of characters, that are in the
|
||||
* middle of a line in the viewport (though it can return characters that are in the middle of a
|
||||
* DOM line)
|
||||
*
|
||||
*TODO rtl languages
|
||||
*
|
||||
*
|
||||
* @param {HTMLElement} line A DOM line that can be wrapped across multiple visible lines
|
||||
* @param {{top: number, bottom: number}} viewport
|
||||
* @returns {[number, number]|null} fully visible characters in the DOM line
|
||||
*/
|
||||
Scroll.prototype.getCountOfVisibleCharsInViewport = (line, viewport) => {
|
||||
const node = document.getElementById(line.domInfo.node.id);
|
||||
const nodeTop = node.offsetTop;
|
||||
const nodeHeight = node.offsetHeight;
|
||||
const nodeBottom = nodeTop + nodeHeight;
|
||||
const nodeLength = node.textContent.length;
|
||||
|
||||
// we can't compare viewport.bottom > nodeTop+lineHeight because that would not work on long lines
|
||||
const startVisible = viewport.top < nodeTop && viewport.bottom > nodeTop;
|
||||
const endVisible = viewport.bottom > nodeBottom && viewport.top < nodeBottom;
|
||||
|
||||
// the whole line is visible
|
||||
if (startVisible && endVisible) return [0, nodeLength];
|
||||
if (!startVisible && !endVisible) {
|
||||
if (nodeTop < viewport.top && nodeBottom > viewport.bottom) {
|
||||
return null;
|
||||
// TODO only some chars visible in the middle of very long line that fills the whole viewport
|
||||
} else {
|
||||
// no character is visible
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// if we are here we know that at least some pixel of the line are visible. If some pixel in a
|
||||
// non-wrapped line are not visible, the whole line is considered not visible.
|
||||
|
||||
// is the line wrapped at viewport top or bottom?
|
||||
let wrapAt;
|
||||
if (startVisible && !endVisible) {
|
||||
wrapAt = 'bottom';
|
||||
} else if (!startVisible && endVisible) {
|
||||
wrapAt = 'top';
|
||||
}
|
||||
const texts = [];
|
||||
textNodes(node, texts);
|
||||
|
||||
const range = document.createRange();
|
||||
if (wrapAt === 'top') {
|
||||
const lastNode = texts[texts.length - 1];
|
||||
range.setEnd(lastNode, lastNode.length - 1);
|
||||
|
||||
// text node we're working on
|
||||
let textIndex = 0;
|
||||
// characters in texts[textIndex]
|
||||
let charIndex = 0;
|
||||
// how many chars we need to skip to reach the first visible char
|
||||
let skippedChars = 0;
|
||||
// forward direction
|
||||
range.setStart(texts[textIndex], charIndex);
|
||||
|
||||
let bb = range.getBoundingClientRect();
|
||||
|
||||
while (bb.top < viewport.top) {
|
||||
if (texts[textIndex].length - 1 > charIndex) {
|
||||
// we are not at the end of this text node yet
|
||||
charIndex += 1;
|
||||
skippedChars += 1;
|
||||
} else if (texts.length - 1 > textIndex) {
|
||||
// we have more text nodes
|
||||
textIndex += 1;
|
||||
charIndex = 0;
|
||||
skippedChars += 1;
|
||||
} else {
|
||||
// all text nodes consumed, but none is fully visible
|
||||
return null;
|
||||
}
|
||||
range.setStart(texts[textIndex], charIndex);
|
||||
bb = range.getBoundingClientRect();
|
||||
}
|
||||
return [skippedChars, nodeLength - 1];
|
||||
}
|
||||
|
||||
if (wrapAt === 'bottom') {
|
||||
range.setStart(texts[0], 0);
|
||||
|
||||
// text node we're working on
|
||||
let textIndex = texts.length - 1;
|
||||
// character in texts[textIndex]
|
||||
let charIndex = texts[textIndex].length - 1;
|
||||
// how many chars we need to skip to reach the first visible char
|
||||
let skippedChars = 0;
|
||||
// backward direction
|
||||
range.setEnd(texts[textIndex], charIndex);
|
||||
while (range.getBoundingClientRect().bottom > viewport.bottom) {
|
||||
if (charIndex > 0) {
|
||||
// we are not at the beginning of the text node yet
|
||||
charIndex -= 1;
|
||||
skippedChars += 1;
|
||||
} else if (textIndex > 0) {
|
||||
// we have more text nodes
|
||||
textIndex -= 1;
|
||||
charIndex = texts[textIndex].length;
|
||||
skippedChars += 1;
|
||||
} else {
|
||||
// all text nodes consumed, but none is fully visible
|
||||
return null;
|
||||
}
|
||||
range.setEnd(texts[textIndex], charIndex);
|
||||
}
|
||||
return [0, nodeLength - skippedChars - 1];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Iterates over a node and returns all text node descendants
|
||||
*
|
||||
* @param {HTMLElement} node A DOM line
|
||||
*/
|
||||
function textNodes(node, texts) {
|
||||
node.childNodes.forEach((child) => {
|
||||
// lists somehow end up as a text node here, but they don't have a nodeValue
|
||||
if (child.nodeType === 3 && child.nodeValue !== '') {
|
||||
texts.push(child);
|
||||
} else {
|
||||
textNodes(child, texts);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
exports.init = (outerWin) => new Scroll(outerWin);
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
*/
|
||||
helper.spyOnSocketIO = function () {
|
||||
helper.contentWindow().pad.socket.on('message', (msg) => {
|
||||
if (msg.type == 'COLLABROOM') {
|
||||
if (msg.data.type == 'ACCEPT_COMMIT') {
|
||||
if (msg.type === 'COLLABROOM') {
|
||||
if (msg.data.type === 'ACCEPT_COMMIT') {
|
||||
helper.commits.push(msg);
|
||||
} else if (msg.data.type == 'USER_NEWINFO') {
|
||||
} else if (msg.data.type === 'USER_NEWINFO') {
|
||||
helper.userInfos.push(msg);
|
||||
} else if (msg.data.type == 'CHAT_MESSAGE') {
|
||||
} else if (msg.data.type === 'CHAT_MESSAGE') {
|
||||
helper.chatMessages.push(msg);
|
||||
}
|
||||
}
|
||||
|
@ -236,3 +236,101 @@ helper.clearPad = async () => {
|
|||
await helper.waitForPromise(helper.padIsEmpty);
|
||||
await helper.waitForPromise(() => helper.commits.length > commitsBefore);
|
||||
};
|
||||
|
||||
/**
|
||||
* Scrolls up in the editor
|
||||
* TODO: is `getSelection` always defined?
|
||||
*/
|
||||
helper.pageUp = async (opts) => {
|
||||
// the caret is in this node
|
||||
const caretNode = helper.padInner$.document.getSelection().anchorNode;
|
||||
const event = new helper.padInner$.Event(helper.evtType);
|
||||
event.which = 33;
|
||||
|
||||
if (opts) {
|
||||
if (opts.shift) {
|
||||
event.shiftKey = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (opts && opts.pressAndHold) {
|
||||
let i = 0;
|
||||
while (i < 100) {
|
||||
// TODO: This triggers the same 100 times, not press and hold..
|
||||
helper.padInner$('#innerdocbody').trigger(event);
|
||||
i++;
|
||||
}
|
||||
await helper.waitForPromise(() => ((helper
|
||||
.padInner$.document.getSelection().anchorNode !== caretNode) && (i === 100)));
|
||||
} else {
|
||||
helper.padInner$('#innerdocbody').trigger(event);
|
||||
// return as soon as the selection has changed
|
||||
await helper.waitForPromise(() => helper
|
||||
.padInner$.document.getSelection().anchorNode !== caretNode);
|
||||
}
|
||||
|
||||
// return as soon as the selection has changed
|
||||
};
|
||||
|
||||
/**
|
||||
* Scrolls down in the editor
|
||||
* TODO: is `getSelection` always defined?
|
||||
*/
|
||||
helper.pageDown = async (opts) => {
|
||||
// the caret is in this node
|
||||
const caretNode = helper.padInner$.document.getSelection().anchorNode;
|
||||
const event = new helper.padInner$.Event(helper.evtType);
|
||||
event.which = 34;
|
||||
|
||||
if (opts) {
|
||||
if (opts.shift) {
|
||||
event.shiftKey = true;
|
||||
}
|
||||
}
|
||||
if (opts && opts.pressAndHold) {
|
||||
let i = 0;
|
||||
while (i < 100) {
|
||||
// TODO: This triggers the same 100 times, not press and hold..
|
||||
helper.padInner$('#innerdocbody').trigger(event);
|
||||
i++;
|
||||
}
|
||||
await helper.waitForPromise(() => ((helper
|
||||
.padInner$.document.getSelection().anchorNode !== caretNode) && (i === 100)));
|
||||
} else {
|
||||
helper.padInner$('#innerdocbody').trigger(event);
|
||||
// return as soon as the selection has changed
|
||||
await helper.waitForPromise(() => helper
|
||||
.padInner$.document.getSelection().anchorNode !== caretNode);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the line number where the caret is currently in
|
||||
*
|
||||
* @returns {number} line number
|
||||
*/
|
||||
helper.caretLineNumber = () => {
|
||||
if (helper.padInner$.document.getSelection()) {
|
||||
let caretNode = helper.padInner$.document.getSelection().anchorNode;
|
||||
const bodyElement = helper.padInner$('body')[0];
|
||||
|
||||
// a text node does not have a classList method
|
||||
if (caretNode.nodeType === 3) {
|
||||
caretNode = caretNode.parentNode;
|
||||
}
|
||||
|
||||
// find the ace-line that contains the caret
|
||||
while (!caretNode.classList.contains('ace-line') && caretNode !== bodyElement) {
|
||||
caretNode = caretNode.parentNode;
|
||||
|
||||
// a text node does not have a classList method
|
||||
if (caretNode.nodeType === 3) {
|
||||
caretNode = caretNode.parentNode;
|
||||
}
|
||||
}
|
||||
|
||||
// find the index of that line in the array of all lines
|
||||
const lines = helper.linesDiv().map((line) => line[0]);
|
||||
return lines.indexOf(caretNode) + 1;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
'use strict';
|
||||
|
||||
describe('Page Up & Page Down', function () {
|
||||
beforeEach(function (cb) {
|
||||
helper.newPad({
|
||||
cb: async () => {
|
||||
await helper.clearPad();
|
||||
// 200 lines
|
||||
await helper.edit(
|
||||
'\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n' +
|
||||
'\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n' +
|
||||
'\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n' +
|
||||
'\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n' +
|
||||
'\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nhello');
|
||||
cb();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// scrolls up 2 times
|
||||
it('scrolls up on key stroke', async function () {
|
||||
await helper.edit('Line 80', 80);
|
||||
await helper.waitForPromise(() => 81 === helper.caretLineNumber());
|
||||
// because we don't send the edit via key events but using `sendkeys` the viewport is
|
||||
// not automatically scrolled. The line below puts the viewport top exactly to where
|
||||
// the caret is.
|
||||
let lineOffset = helper.linesDiv()[80][0].offsetTop;
|
||||
helper.padOuter$('#outerdocbody').parent().scrollTop(lineOffset);
|
||||
let intitialLineNumber = helper.caretLineNumber();
|
||||
helper.pageUp();
|
||||
await helper.waitForPromise(() => intitialLineNumber > helper.caretLineNumber() &&
|
||||
lineOffset > helper.padOuter$('#outerdocbody').parent().scrollTop());
|
||||
intitialLineNumber = helper.caretLineNumber();
|
||||
lineOffset = helper.padOuter$('#outerdocbody').parent().scrollTop();
|
||||
helper.pageUp();
|
||||
await helper.waitForPromise(() => intitialLineNumber > helper.caretLineNumber() &&
|
||||
lineOffset > helper.padOuter$('#outerdocbody').parent().scrollTop());
|
||||
});
|
||||
// scrolls down 3 times
|
||||
it('scrolls down on key stroke', async function () {
|
||||
// this places the caret in the first line
|
||||
await helper.edit('Line 1', 1);
|
||||
|
||||
let currentLineNumber = helper.caretLineNumber();
|
||||
let lineOffset = helper.padOuter$('#outerdocbody').parent().scrollTop();
|
||||
helper.pageDown();
|
||||
await helper.waitForPromise(() => currentLineNumber < helper.caretLineNumber() &&
|
||||
lineOffset < helper.padOuter$('#outerdocbody').parent().scrollTop());
|
||||
|
||||
currentLineNumber = helper.caretLineNumber();
|
||||
lineOffset = helper.padOuter$('#outerdocbody').parent().scrollTop();
|
||||
helper.pageDown();
|
||||
await helper.waitForPromise(() => currentLineNumber < helper.caretLineNumber() &&
|
||||
lineOffset < helper.padOuter$('#outerdocbody').parent().scrollTop());
|
||||
|
||||
currentLineNumber = helper.caretLineNumber();
|
||||
lineOffset = helper.padOuter$('#outerdocbody').parent().scrollTop();
|
||||
helper.pageDown();
|
||||
await helper.waitForPromise(() => currentLineNumber < helper.caretLineNumber() &&
|
||||
lineOffset < helper.padOuter$('#outerdocbody').parent().scrollTop());
|
||||
});
|
||||
});
|
|
@ -0,0 +1,55 @@
|
|||
'use strict';
|
||||
|
||||
describe('Line number integrity is kept between page up/down', function () {
|
||||
beforeEach(function (cb) {
|
||||
helper.newPad({
|
||||
cb: async () => {
|
||||
await helper.clearPad();
|
||||
// 200 lines
|
||||
await helper.edit(
|
||||
'\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n' +
|
||||
'\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n' +
|
||||
'\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n' +
|
||||
'\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n' +
|
||||
'\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n');
|
||||
cb();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const lineHistory = [];
|
||||
|
||||
it('Page down x times, then page up and see if lines match', async function () {
|
||||
// this places the caret in the first line
|
||||
await helper.edit('Line 1', 1);
|
||||
|
||||
const currentLineNumber = helper.caretLineNumber();
|
||||
lineHistory.push(helper.caretLineNumber());
|
||||
|
||||
helper.pageDown();
|
||||
await helper.waitForPromise(() => currentLineNumber < helper.caretLineNumber());
|
||||
lineHistory.push(helper.caretLineNumber());
|
||||
|
||||
helper.pageDown();
|
||||
await helper.waitForPromise(() => currentLineNumber < helper.caretLineNumber());
|
||||
lineHistory.push(helper.caretLineNumber());
|
||||
|
||||
helper.pageDown();
|
||||
await helper.waitForPromise(() => currentLineNumber < helper.caretLineNumber());
|
||||
|
||||
let futureLineNumber = helper.caretLineNumber();
|
||||
helper.pageUp();
|
||||
await helper.waitForPromise(() => futureLineNumber > helper.caretLineNumber());
|
||||
if (helper.caretLineNumber() !== lineHistory[lineHistory.length - 1]) {
|
||||
throw new Error('Line History not being properly maintained on page up #1');
|
||||
}
|
||||
lineHistory.pop();
|
||||
|
||||
futureLineNumber = helper.caretLineNumber();
|
||||
helper.pageUp();
|
||||
await helper.waitForPromise(() => futureLineNumber > helper.caretLineNumber());
|
||||
if (helper.caretLineNumber() !== lineHistory[lineHistory.length - 1]) {
|
||||
throw new Error('Line History not being properly maintained on page up #2');
|
||||
}
|
||||
});
|
||||
});
|
|
@ -0,0 +1,193 @@
|
|||
'use strict';
|
||||
|
||||
describe(`Really long text line goes to character within text line if text line is last
|
||||
line in viewport if the second line is also incredibly long`, function () {
|
||||
beforeEach(function (cb) {
|
||||
helper.newPad({
|
||||
cb: async () => {
|
||||
await helper.clearPad();
|
||||
// 200 lines
|
||||
await helper.edit(
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
' \n ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'\n\n\n\n\n\n\n\n\n ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ' +
|
||||
'hello world hello world hello world hello world hello world hello world hello world ');
|
||||
cb();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('Pg down on long line keeps char on the same line but with large X offset', async function () {
|
||||
await helper.edit('xxx', 1); // caret is offset 6
|
||||
await helper.waitForPromise(() => {
|
||||
if ((helper.padInner$.document.getSelection().anchorOffset === 0) &&
|
||||
(helper.caretLineNumber() === 1)) {
|
||||
return true;
|
||||
} else {
|
||||
helper.pageUp();
|
||||
}
|
||||
});
|
||||
helper.pageDown();
|
||||
await helper.waitForPromise(() => {
|
||||
if ((helper.padInner$.document.getSelection().anchorOffset > 0) &&
|
||||
(helper.caretLineNumber() === 1)) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
let previousLineNumber;
|
||||
|
||||
helper.pageDown();
|
||||
await helper.waitForPromise(() => helper.caretLineNumber() >= previousLineNumber);
|
||||
previousLineNumber = helper.caretLineNumber();
|
||||
helper.pageDown();
|
||||
await helper.waitForPromise(() => helper.caretLineNumber() >= previousLineNumber);
|
||||
previousLineNumber = helper.caretLineNumber();
|
||||
helper.pageDown();
|
||||
await helper.waitForPromise(() => helper.caretLineNumber() >= previousLineNumber);
|
||||
previousLineNumber = helper.caretLineNumber();
|
||||
helper.pageDown();
|
||||
await helper.waitForPromise(() => helper.caretLineNumber() >= previousLineNumber);
|
||||
previousLineNumber = helper.caretLineNumber();
|
||||
helper.pageDown();
|
||||
await helper.waitForPromise(() => helper.caretLineNumber() >= previousLineNumber);
|
||||
previousLineNumber = helper.caretLineNumber();
|
||||
helper.pageDown();
|
||||
await helper.waitForPromise(() => helper.caretLineNumber() >= previousLineNumber);
|
||||
previousLineNumber = helper.caretLineNumber();
|
||||
|
||||
// we're at the bottom..
|
||||
helper.pageUp();
|
||||
// goes up within line but not pad
|
||||
await helper.waitForPromise(() => helper.caretLineNumber() === previousLineNumber);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,36 @@
|
|||
'use strict';
|
||||
|
||||
describe('Press and Hold Page Up/Down', function () {
|
||||
beforeEach(function (cb) {
|
||||
helper.newPad({
|
||||
cb: async () => {
|
||||
await helper.clearPad();
|
||||
// 200 lines
|
||||
await helper.edit(
|
||||
'\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n' +
|
||||
'\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n' +
|
||||
'\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n' +
|
||||
'\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n' +
|
||||
'\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n');
|
||||
cb();
|
||||
},
|
||||
});
|
||||
});
|
||||
it('page up press and hold to top', async function () {
|
||||
// by default page down when caret is at end of the document will leave it in the same place.
|
||||
// viewport based pageup/down changes that
|
||||
helper.pageUp({
|
||||
pressAndHold: true,
|
||||
});
|
||||
await helper.waitForPromise(() => helper.caretLineNumber() === 1);
|
||||
});
|
||||
it('page down press and hold to bottom', async function () {
|
||||
// by default page down when caret is at end of the document will leave it in the same place.
|
||||
// viewport based pageup/down changes that
|
||||
const initialLineNumber = helper.caretLineNumber();
|
||||
helper.pageDown({
|
||||
pressAndHold: true,
|
||||
});
|
||||
await helper.waitForPromise(() => helper.caretLineNumber() === initialLineNumber);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,130 @@
|
|||
'use strict';
|
||||
|
||||
describe('Shift Page Up/Down', function () {
|
||||
beforeEach(function (cb) {
|
||||
helper.newPad({
|
||||
cb: async () => {
|
||||
await helper.clearPad();
|
||||
// 200 lines
|
||||
await helper.edit(
|
||||
'\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n' +
|
||||
'\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n' +
|
||||
'\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n' +
|
||||
'\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n' +
|
||||
'\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n');
|
||||
cb();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('highlights lines on shift page down and releases them on page up', async function () {
|
||||
await helper.edit('xxx', 1); // caret is offset 6
|
||||
|
||||
helper.pageUp();
|
||||
helper.pageDown({
|
||||
shift: true,
|
||||
});
|
||||
await helper.waitForPromise(() => helper.padInner$.document.getSelection().type === 'Range');
|
||||
|
||||
helper.pageUp({
|
||||
shift: true,
|
||||
});
|
||||
await helper.waitForPromise(() => helper.padInner$.document.getSelection().type === 'Caret');
|
||||
});
|
||||
it('highlights lines on shift pg down and maintains first selection on pg up', async function () {
|
||||
await helper.edit('xxx', 1); // caret is offset 6
|
||||
|
||||
helper.pageUp();
|
||||
helper.pageDown({
|
||||
shift: true,
|
||||
});
|
||||
await helper.waitForPromise(() => helper.padInner$.document.getSelection().type === 'Range');
|
||||
|
||||
helper.pageDown({
|
||||
shift: true,
|
||||
});
|
||||
await helper.waitForPromise(() => helper.padInner$.document.getSelection().type === 'Range');
|
||||
|
||||
helper.pageUp({
|
||||
shift: true,
|
||||
});
|
||||
await helper.waitForPromise(() => helper.padInner$.document.getSelection().type === 'Range');
|
||||
});
|
||||
|
||||
it(`Highlights from end of document on pg up
|
||||
then releases them on shift pg down`, async function () {
|
||||
// TODO: JM NEEDS HELP: Why isn't this working? It works if you do the same in browser..
|
||||
|
||||
await helper.waitForPromise(() => helper.caretLineNumber() >= 201);
|
||||
// make sure we're at bottom
|
||||
helper.pageDown({
|
||||
pressAndHold: true,
|
||||
});
|
||||
await helper.waitForPromise(() => helper.caretLineNumber() >= 201);
|
||||
|
||||
helper.pageUp({
|
||||
shift: true,
|
||||
});
|
||||
await helper.waitForPromise(() => helper.padInner$.document.getSelection().type === 'Range');
|
||||
|
||||
helper.pageDown({
|
||||
shift: true,
|
||||
});
|
||||
await helper.waitForPromise(() => helper.padInner$.document.getSelection().type === 'Caret');
|
||||
throw new Error('I NEED HELPZ PLZ');
|
||||
});
|
||||
it(`highlights from end of document on pg up twice
|
||||
and retains on single pg down`, async function () {
|
||||
helper.pageUp({
|
||||
shift: true,
|
||||
});
|
||||
await helper.waitForPromise(() => helper.padInner$.document.getSelection().type === 'Range');
|
||||
|
||||
helper.pageUp({
|
||||
shift: true,
|
||||
});
|
||||
await helper.waitForPromise(() => helper.padInner$.document.getSelection().type === 'Range');
|
||||
|
||||
helper.pageDown({
|
||||
shift: true,
|
||||
});
|
||||
await helper.waitForPromise(() => helper.padInner$.document.getSelection().type === 'Range');
|
||||
});
|
||||
|
||||
it(`highlights from 3rd line on page up twice
|
||||
should keep highlight`, async function () {
|
||||
await helper.edit('xxx', 3); // caret is offset 6
|
||||
|
||||
helper.pageUp({
|
||||
shift: true,
|
||||
});
|
||||
await helper.waitForPromise(() => helper.padInner$.document.getSelection().type === 'Range');
|
||||
|
||||
helper.pageUp({
|
||||
shift: true,
|
||||
});
|
||||
await helper.waitForPromise(() => helper.padInner$.document.getSelection().type === 'Range');
|
||||
});
|
||||
xit(`highlights range forward then hit page up, selStart should be prior to initial
|
||||
selStart and selEnd should be the original selStart`, async function () {
|
||||
// TODO: JM Needs help, need a way to just select this line but it needs direction
|
||||
// {select} wont cut the mustard
|
||||
// selStartFocus is internal to rep
|
||||
});
|
||||
|
||||
xit(`highlights (a few lines) range forwards then hit page down, selStart should be initial
|
||||
selStart and selEnd further than original selEnd`, async function () {
|
||||
throw new Error('JM TO DO');
|
||||
});
|
||||
|
||||
xit(`highlights (a few lines) range backwards (rep.selFocusAtStart) then hit page up, selEnd
|
||||
should be initial selStart,
|
||||
selStart should be less than original selStart`, async function () {
|
||||
throw new Error('JM TO DO');
|
||||
});
|
||||
|
||||
xit(`highlights (a few lines) range backwards (rep.selFocusAtStart) then hit page down, selStart
|
||||
should be initial selEnd and selEnd further than original selEnd`, async function () {
|
||||
throw new Error('JM TO DO');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,43 @@
|
|||
'use strict';
|
||||
|
||||
describe('Page Up/Down Beginning and End position', function () {
|
||||
beforeEach(function (cb) {
|
||||
helper.newPad({
|
||||
cb: async () => {
|
||||
await helper.clearPad();
|
||||
// 200 lines
|
||||
await helper.edit(
|
||||
'\n\n\n\nhello');
|
||||
cb();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('scrolls to very end content on page down when viewport is at bottom', async function () {
|
||||
// this places the caret in the first line
|
||||
await helper.edit('Line 1', 1);
|
||||
|
||||
const currentLineNumber = helper.caretLineNumber();
|
||||
helper.pageDown();
|
||||
await helper.waitForPromise(() => currentLineNumber < helper.caretLineNumber());
|
||||
|
||||
// make sure caret is after hello
|
||||
const pos = helper.padInner$.document.getSelection();
|
||||
await helper.waitForPromise(() => pos.anchorOffset > 0);
|
||||
});
|
||||
|
||||
// scrolls down 3 times - caret should be AFTER "hello
|
||||
it(`scrolls to very beginning content on pg up when
|
||||
viewport is at bottom of document`, async function () {
|
||||
// this places the caret in the first line
|
||||
await helper.edit('Line 1', 1);
|
||||
|
||||
const currentLineNumber = helper.caretLineNumber();
|
||||
helper.pageUp();
|
||||
await helper.waitForPromise(() => currentLineNumber > helper.caretLineNumber());
|
||||
|
||||
// make sure caret is at 0 position
|
||||
const pos = helper.padInner$.document.getSelection();
|
||||
await helper.waitForPromise(() => pos.anchorOffset === 0);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,41 @@
|
|||
'use strict';
|
||||
|
||||
describe('Viewport based Page Up/Down', function () {
|
||||
beforeEach(function (cb) {
|
||||
helper.newPad({
|
||||
cb: async () => {
|
||||
await helper.clearPad();
|
||||
// 200 lines
|
||||
await helper.edit(
|
||||
'\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n' +
|
||||
'\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n' +
|
||||
'\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n' +
|
||||
'\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n' +
|
||||
'\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n');
|
||||
cb();
|
||||
},
|
||||
});
|
||||
});
|
||||
it('page up when top is at 100 and caret is at bottom', async function () {
|
||||
// by default page down when caret is at end of the document will leave it in the same place.
|
||||
// viewport based pageup/down changes that
|
||||
const initialLineNumber = helper.caretLineNumber();
|
||||
helper.pageDown();
|
||||
helper.padOuter$('#outerdocbody').parent().scrollTop(100);
|
||||
helper.pageUp();
|
||||
await helper.waitForPromise(() => helper.caretLineNumber() < initialLineNumber);
|
||||
});
|
||||
|
||||
it('page down when top is at 0 and caret is at bottom', async function () {
|
||||
// by default page down when caret is at end of the document will leave it in the same place.
|
||||
// viewport based pageup/down changes that
|
||||
const initialLineNumber = helper.caretLineNumber();
|
||||
helper.padOuter$('#outerdocbody').parent().scrollTop(0);
|
||||
await helper.waitForPromise(() => helper.padOuter$('#outerdocbody').parent().scrollTop() === 0);
|
||||
helper.pageUp(); // I think this might not be right..
|
||||
helper.pageDown();
|
||||
|
||||
await helper.waitForPromise(() => (helper
|
||||
.caretLineNumber() < initialLineNumber) && (helper.caretLineNumber() > 1));
|
||||
});
|
||||
});
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue