Added page break methods

This commit is contained in:
Dmitriy Shishkov 2021-08-04 14:59:41 +03:00
parent b7e3c1b2f5
commit 460d58d4d6
No known key found for this signature in database
GPG Key ID: 14358F96FCDD8060
3 changed files with 151 additions and 30 deletions

View File

@ -4,20 +4,20 @@
import { CacheInterface, HTMLPagination } from "../index";
describe("HTMLPagination", () => {
let hp: HTMLPagination;
let hp: HTMLPagination;
const content = document.createElement("div");
const container = document.createElement("div");
class Cache extends CacheInterface {
g(key: string) {
return localStorage.getItem(key);
}
s(key: string, value: string) {
localStorage.setItem(key, value);
}
const content = document.createElement("div");
const container = document.createElement("div");
class Cache extends CacheInterface {
g(key: string) {
return localStorage.getItem(key);
}
s(key: string, value: string) {
localStorage.setItem(key, value);
}
}
describe("Text position stuff", () => {
beforeEach(() => {
content.innerHTML = "";
content.innerHTML = "<div><p>aa</p><p>bb<span>cc</span>dd</p></div>";
@ -27,22 +27,42 @@ describe("HTMLPagination", () => {
hp = new HTMLPagination(content, container, new Cache());
});
test("Computes positions for each text node", () => {
it("computes positions for each text node", () => {
expect(hp.elementPositions.map((el) => el[0])).toEqual([0, 2, 4, 6]);
});
test("Gets element by position", () => {
it("gets element by position", () => {
expect(hp.getElementForPosition(0)[0]).toBe(0);
expect(hp.getElementForPosition(1)[0]).toBe(0);
expect(hp.getElementForPosition(2)[0]).toBe(2);
expect(hp.getElementForPosition(3)[0]).toBe(2);
});
test("Gets html content for range of text", () => {
expect(hp.getFromRange(0, 8)).toEqual(
it("gets html content for range of text", () => {
expect(hp.getContentFromRange(0, 8)).toEqual(
"<p>aa</p><p>bb<span>cc</span>dd</p>"
);
expect(hp.getFromRange(0, 3)).toEqual("<p>aa</p><p>b</p>");
expect(hp.getFromRange(5, 7)).toEqual("<span>c</span>d");
expect(hp.getContentFromRange(0, 3)).toEqual("<p>aa</p><p>b</p>");
expect(hp.getContentFromRange(5, 7)).toEqual("<span>c</span>d");
});
});
// TODO: Add pagination tests using puppeteer
// let browser: puppeteer.Browser, page: puppeteer.Page;
// describe("Page splitting stuff", () => {
// beforeAll(async () => {
// browser = await puppeteer.launch({
// defaultViewport: {
// height: 150,
// width: 150,
// },
// });
// page = await browser.newPage();
// });
// it("Inserts page break to prevent scroll", async () => {
// await page.goto("localhost:5000");
// });
// });

View File

@ -1,12 +1,13 @@
import { CacheInterface } from "./cache";
import { isElementNode, isTextNode, TextNode } from "./types";
import { binSearch } from "./utils";
class HTMLPagination {
content: HTMLElement;
container: HTMLElement;
cache: CacheInterface;
elementPositions: [number, TextNode][];
elementPositions: [number, Node][];
idPositions: Map<string, number>;
/**
@ -33,7 +34,7 @@ class HTMLPagination {
const from = 0;
const to = 1;
return this.getFromRange(from, to);
return this.getContentFromRange(from, to);
}
/**
@ -57,27 +58,106 @@ class HTMLPagination {
recursive(0, this.content);
}
/**
* Finds position for next page break
* initialJump may be computed in a clever way
*/
getPageBreak(start: number, initialJump: number) {
let previousEnd = this.getMaxPosition();
let end = this.getNextSpaceForPosition(start + initialJump);
this.getContentFromRange(start, end);
while (!this.scrollNecessary() && end < this.getMaxPosition()) {
previousEnd = end;
end = this.getNextSpaceForPosition(end + 1);
this.getContentFromRange(start, end);
}
while (this.scrollNecessary() && end > start) {
previousEnd = end;
end = this.getPreviousSpaceForPosition(end - 1);
this.getContentFromRange(start, end);
}
if (start === end) return previousEnd;
else return end;
}
/**
* Gets next space or gap between elements for position
*/
getNextSpaceForPosition(startPos: number): number {
const nodeIndex = this.getElementIndexForPosition(startPos);
const [nodePosition, node] = this.elementPositions[nodeIndex];
let nodeOffset = startPos - nodePosition;
const str = node.nodeValue || "";
while (nodeOffset < str.length && str.charAt(nodeOffset) !== " ")
nodeOffset++;
if (nodeOffset === str.length) {
if (nodeIndex === this.elementPositions.length - 1)
return this.getMaxPosition();
else return this.elementPositions[nodeIndex + 1][0];
} else {
return this.elementPositions[nodeIndex][0] + nodeOffset;
}
}
/**
* Gets previous space or gap between elements for position
*/
getPreviousSpaceForPosition(startPos: number): number {
const nodeIndex = this.getElementIndexForPosition(startPos);
const [nodePosition, node] = this.elementPositions[nodeIndex];
let nodeOffset = startPos - nodePosition;
const str = node.nodeValue || "";
while (nodeOffset > 0 && str.charAt(nodeOffset) !== " ") nodeOffset--;
return this.elementPositions[nodeIndex][0] + nodeOffset;
}
/**
* Checks if container is overflowing with content
*/
scrollNecessary(): boolean {
return this.container.clientHeight < this.container.scrollHeight;
}
/**
* Returns end position of content
*/
getMaxPosition(): number {
const [offset, element] =
this.elementPositions[this.elementPositions.length - 1];
return offset + (element.nodeValue?.length || 0);
}
/**
* Wrapper for `binSearch` util to find index of element for position
*/
getElementIndexForPosition(pos: number): number {
return binSearch(
this.elementPositions,
pos,
(i) => this.elementPositions[i][0]
);
}
/**
* Finds node inside which `pos` is located. Returns node positions and itself
*/
getElementForPosition(pos: number): [number, Node] {
let s = 0,
e = this.elementPositions.length - 1;
const elementIndex = this.getElementIndexForPosition(pos);
while (s <= e) {
const c = (s + e) >> 1;
if (pos > this.elementPositions[c][0]) s = c + 1;
else if (pos < this.elementPositions[c][0]) e = c - 1;
else return this.elementPositions[c];
}
return this.elementPositions[s - 1];
return this.elementPositions[elementIndex];
}
/**
* Sets `container` element content and return as string html content between `from` and `to`
*/
getFromRange(from: number, to: number): string {
getContentFromRange(from: number, to: number): string {
this.container.innerHTML = "";
const range = new Range();

21
src/utils.ts Normal file
View File

@ -0,0 +1,21 @@
/**
* Binarycally searches element in array or last element lower than searched
* @param getter Function to convert array element to comparable with searched element
*/
export const binSearch = <T>(
arr: T[],
el: number,
getter: (i: number) => number
): number => {
let s = 0,
e = arr.length - 1;
while (s <= e) {
const c = (s + e) >> 1;
if (el > getter(c)) s = c + 1;
else if (el < getter(c)) e = c - 1;
else return c;
}
return s - 1;
};