getCountOfVisibleCharsInViewport functionality
parent
01fe885adc
commit
996f0e8a1d
|
@ -1441,6 +1441,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];
|
||||
|
@ -1455,6 +1462,7 @@ function Ace2Inner() {
|
|||
const lineNode = lineEntry.lineNode;
|
||||
let n = lineNode;
|
||||
let after = false;
|
||||
// at [x, 0] of a line with line attributes
|
||||
if (charsLeft === 0) {
|
||||
const index = 0;
|
||||
return {
|
||||
|
|
|
@ -372,6 +372,7 @@ Scroll.prototype.movePage = function (direction) {
|
|||
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]);
|
||||
|
@ -413,72 +414,149 @@ Scroll.prototype.getFirstVisibleCharacter = function (direction, rep) {
|
|||
return modifiedRep;
|
||||
};
|
||||
|
||||
// line is a DOM Line
|
||||
// returned is the number of characters in that index that are currently visible
|
||||
// IE 120,240
|
||||
Scroll.prototype.getCountOfVisibleCharsInViewport = (line, viewport) => {
|
||||
const range = document.createRange();
|
||||
const chars = line.text.split(''); // split "abc" into ["a","b","c"]
|
||||
const parentElement = document.getElementById(line.domInfo.node.id).childNodes;
|
||||
const charNumber = [];
|
||||
// top.console.log(parentElement);
|
||||
for (const node of parentElement) {
|
||||
// each span..
|
||||
// top.console.log('span', node); // shows all nodes from the collection
|
||||
// top.console.log('span length', node.offsetTop); // shows all nodes from the collection
|
||||
|
||||
// each character
|
||||
/*
|
||||
let i = 0;
|
||||
console.log(node);
|
||||
if (!node || !node.childNodes) return;
|
||||
node = node.childNodes[0];
|
||||
if (!node) return; // temp patch to be removed.
|
||||
if (node.childNodes && node.childNodes[1].length === 0) return;
|
||||
console.log(node);
|
||||
console.log(node.wholeText.length);
|
||||
while (i < node.wholeText.length) {
|
||||
// top.console.log(i, node.textContent[i]);
|
||||
const range = document.createRange();
|
||||
let failed = false;
|
||||
try {
|
||||
range.setStart(node, i);
|
||||
} catch (e) {
|
||||
failed = true;
|
||||
console.log('fail', e);
|
||||
// console.log('node', node);
|
||||
}
|
||||
try {
|
||||
range.setEnd(node, i + 1);
|
||||
} catch (e) {
|
||||
failed = true;
|
||||
console.log('fail', e);
|
||||
console.log('node', node);
|
||||
}
|
||||
// console.log('range', range);
|
||||
let char;
|
||||
if (!failed) char = range.getClientRects();
|
||||
console.log(node);
|
||||
console.log('charr????', char);
|
||||
if (char) return;
|
||||
if (char && char.length && char[0]) {
|
||||
const topOffset = char[0].y;
|
||||
charNumber.push(topOffset);
|
||||
// is this element in view?
|
||||
console.log('topOffset', topOffset, 'viewport', viewport);
|
||||
if (topOffset > viewport.top) {
|
||||
console.log('can put rep here!', i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
top.console.log('charNumber', charNumber);
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
return; // TEMPJM CAKE remove once stable
|
||||
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];
|
||||
}
|
||||
return 1000;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
|
|
@ -21,17 +21,20 @@ describe('Page Up & Page Down', function () {
|
|||
it('scrolls up on key stroke', async function () {
|
||||
await helper.edit('Line 80', 80);
|
||||
await helper.waitForPromise(() => 81 === helper.caretLineNumber());
|
||||
// for some reason the page isn't inline with the edit
|
||||
helper.padOuter$('#outerdocbody').parent().scrollTop(1000);
|
||||
// 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());
|
||||
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());
|
||||
await helper.waitForPromise(
|
||||
() => helper.padOuter$('#outerdocbody').parent().scrollTop() < 1000
|
||||
);
|
||||
await helper.waitForPromise(() => intitialLineNumber > helper.caretLineNumber() &&
|
||||
lineOffset > helper.padOuter$('#outerdocbody').parent().scrollTop());
|
||||
});
|
||||
// scrolls down 3 times
|
||||
it('scrolls down on key stroke', async function () {
|
||||
|
@ -39,15 +42,21 @@ describe('Page Up & Page Down', function () {
|
|||
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());
|
||||
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());
|
||||
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());
|
||||
await helper.waitForPromise(() => currentLineNumber < helper.caretLineNumber() &&
|
||||
lineOffset < helper.padOuter$('#outerdocbody').parent().scrollTop());
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue