Compare commits

...

51 Commits

Author SHA1 Message Date
John McLear 2f08608649 up movement test 2021-01-08 21:25:01 +00:00
John McLear 14e24f5a3c example of throttle / debounce to try 2021-01-08 15:20:09 +00:00
webzwo0i 996f0e8a1d getCountOfVisibleCharsInViewport functionality 2021-01-08 08:19:15 +01:00
John McLear 01fe885adc linted ace2_inner 2021-01-07 21:07:29 +00:00
John McLear 34c7fb0c46 lint scroll.js 2021-01-07 20:57:59 +00:00
John McLear a3e6a44643 lint methods.js 2021-01-07 20:56:50 +00:00
John McLear 79630c7ab6 linted tests 2021-01-07 20:49:48 +00:00
John McLear 6d8a28b993 shifted in seperate files 2021-01-07 20:48:42 +00:00
John McLear 26c733e237 better handling of line heights 2021-01-07 20:19:36 +00:00
John McLear 2fff1c473f change in buffer management 2021-01-07 20:09:54 +00:00
John McLear 170dd43e8b actual fix 2021-01-07 19:23:40 +00:00
John McLear f3971c6c3e fix 2021-01-07 19:22:01 +00:00
John McLear c91c227a86 placeholder for tests to finish up 2021-01-07 18:14:31 +00:00
John McLear d1222bb9a7 tidy 2021-01-07 16:36:47 +00:00
John McLear a75decf33c working shift page up / down 2021-01-07 16:08:41 +00:00
John McLear 1991c439f2 shift logic working but wtf is happening here?! 2021-01-04 16:25:41 +00:00
John McLear d41b5ec96d shift logic working but wtf is happening here?! 2021-01-04 16:25:37 +00:00
John McLear ea010c92d5 test coverage for press and hold page up/down 2021-01-04 11:31:29 +00:00
John McLear 81e4c14fc0 reverse reps are a brain truck 2021-01-03 12:36:04 +00:00
John McLear e48f2d0190 you meddling nitwit 2021-01-03 12:15:34 +00:00
John McLear 8803c23a2d fix tests and better dingdong 2021-01-03 11:51:35 +00:00
John McLear eba5827092 working x offset logic bugfix 2021-01-03 11:36:19 +00:00
John McLear ffce4d32cb working x offset logic 2021-01-03 11:33:12 +00:00
John McLear 81e50061dc oof struggling with char offset 2021-01-02 16:30:32 +00:00
John McLear 8e5c124f5e new approach #1721874828748974124124 2021-01-02 14:55:19 +00:00
John McLear 66d3d6a02d oof, dat bugz 2021-01-01 22:10:36 +00:00
John McLear d930a12e37 additional testing for long line issue 2021-01-01 21:46:58 +00:00
John McLear 8300909369 let tests run 2021-01-01 21:09:24 +00:00
John McLear e4404d702e resolve issue with line history not being kept 2021-01-01 21:05:13 +00:00
John McLear cdce13ca25 still very broken but getting more test coverage at least -_- 2020-12-31 11:27:04 +00:00
John McLear a30b120b0e more page up down tests this time including shift spuport 2020-12-31 10:30:10 +00:00
John McLear e6f8356500 begin adding in shift support 2020-12-30 22:32:51 +00:00
John McLear 75509ce96e uncomment other tests, testing work mostly complete 2020-12-30 19:25:30 +00:00
John McLear 5a425d7e69 include opts in pageup/pagedown method to support shift key and include the test 2020-12-30 19:19:35 +00:00
John McLear dda5e03f7e more testS 2020-12-30 17:36:01 +00:00
John McLear da26839ccb introducing more tests 2020-12-29 23:44:43 +00:00
John McLear 6d1662a4da introducing more tests 2020-12-29 22:58:16 +00:00
John McLear 1bd7bb94d1 whoops 2020-12-29 22:30:16 +00:00
John McLear f1a66cd25b whoops 2020-12-29 22:29:26 +00:00
John McLear 6c000472af character offset broken logic 2020-12-29 21:32:25 +00:00
John McLear 3eab0df189 begin adding in support for long lines 2020-12-29 18:56:04 +00:00
John McLear 5cd4dc1af2 keep previous caret x location on pageup/down 2020-12-29 16:55:51 +00:00
webzwo0i db26ea218c page up/down: add test coverage 2020-12-29 15:53:36 +00:00
John McLear af6a699db4 up down better ux 2020-12-28 18:29:01 +00:00
John McLear 0fdc13a6ad some wierdness, not sure what 2020-12-28 18:20:08 +00:00
John McLear 0b9962c6c6 working going to end of the line and beginning of first 2020-12-28 14:12:24 +00:00
John McLear 92450e071d much better ux 2020-12-28 09:34:58 +00:00
John McLear a536b311e4 much better ux 2020-12-28 09:28:55 +00:00
John McLear 0253b54803 working page down and semi working page up 2020-12-28 09:12:45 +00:00
John McLear d6075bd042 working basic functionality 2020-12-28 08:40:53 +00:00
John McLear f6d9c7e705 just some thoughts on approaches 2020-12-27 23:39:20 +00:00
11 changed files with 14928 additions and 63 deletions

View File

@ -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();

View File

@ -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);

View File

@ -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;
}
};

View File

@ -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());
});
});

View File

@ -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');
}
});
});

View File

@ -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);
});
});

View File

@ -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);
});
});

View File

@ -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');
});
});

View File

@ -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);
});
});

View File

@ -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));
});
});

13851
var/pageup.db Normal file

File diff suppressed because one or more lines are too long