Added page break methods
This commit is contained in:
parent
b7e3c1b2f5
commit
460d58d4d6
@ -4,20 +4,20 @@
|
|||||||
|
|
||||||
import { CacheInterface, HTMLPagination } from "../index";
|
import { CacheInterface, HTMLPagination } from "../index";
|
||||||
|
|
||||||
describe("HTMLPagination", () => {
|
let hp: HTMLPagination;
|
||||||
let hp: HTMLPagination;
|
|
||||||
|
|
||||||
const content = document.createElement("div");
|
const content = document.createElement("div");
|
||||||
const container = document.createElement("div");
|
const container = document.createElement("div");
|
||||||
class Cache extends CacheInterface {
|
class Cache extends CacheInterface {
|
||||||
g(key: string) {
|
g(key: string) {
|
||||||
return localStorage.getItem(key);
|
return localStorage.getItem(key);
|
||||||
}
|
|
||||||
s(key: string, value: string) {
|
|
||||||
localStorage.setItem(key, value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
s(key: string, value: string) {
|
||||||
|
localStorage.setItem(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("Text position stuff", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
content.innerHTML = "";
|
content.innerHTML = "";
|
||||||
content.innerHTML = "<div><p>aa</p><p>bb<span>cc</span>dd</p></div>";
|
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());
|
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]);
|
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(0)[0]).toBe(0);
|
||||||
expect(hp.getElementForPosition(1)[0]).toBe(0);
|
expect(hp.getElementForPosition(1)[0]).toBe(0);
|
||||||
expect(hp.getElementForPosition(2)[0]).toBe(2);
|
expect(hp.getElementForPosition(2)[0]).toBe(2);
|
||||||
expect(hp.getElementForPosition(3)[0]).toBe(2);
|
expect(hp.getElementForPosition(3)[0]).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Gets html content for range of text", () => {
|
it("gets html content for range of text", () => {
|
||||||
expect(hp.getFromRange(0, 8)).toEqual(
|
expect(hp.getContentFromRange(0, 8)).toEqual(
|
||||||
"<p>aa</p><p>bb<span>cc</span>dd</p>"
|
"<p>aa</p><p>bb<span>cc</span>dd</p>"
|
||||||
);
|
);
|
||||||
expect(hp.getFromRange(0, 3)).toEqual("<p>aa</p><p>b</p>");
|
expect(hp.getContentFromRange(0, 3)).toEqual("<p>aa</p><p>b</p>");
|
||||||
expect(hp.getFromRange(5, 7)).toEqual("<span>c</span>d");
|
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");
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
106
src/index.ts
106
src/index.ts
@ -1,12 +1,13 @@
|
|||||||
import { CacheInterface } from "./cache";
|
import { CacheInterface } from "./cache";
|
||||||
import { isElementNode, isTextNode, TextNode } from "./types";
|
import { isElementNode, isTextNode, TextNode } from "./types";
|
||||||
|
import { binSearch } from "./utils";
|
||||||
|
|
||||||
class HTMLPagination {
|
class HTMLPagination {
|
||||||
content: HTMLElement;
|
content: HTMLElement;
|
||||||
container: HTMLElement;
|
container: HTMLElement;
|
||||||
cache: CacheInterface;
|
cache: CacheInterface;
|
||||||
|
|
||||||
elementPositions: [number, TextNode][];
|
elementPositions: [number, Node][];
|
||||||
idPositions: Map<string, number>;
|
idPositions: Map<string, number>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -33,7 +34,7 @@ class HTMLPagination {
|
|||||||
const from = 0;
|
const from = 0;
|
||||||
const to = 1;
|
const to = 1;
|
||||||
|
|
||||||
return this.getFromRange(from, to);
|
return this.getContentFromRange(from, to);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -57,27 +58,106 @@ class HTMLPagination {
|
|||||||
recursive(0, this.content);
|
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
|
* Finds node inside which `pos` is located. Returns node positions and itself
|
||||||
*/
|
*/
|
||||||
getElementForPosition(pos: number): [number, Node] {
|
getElementForPosition(pos: number): [number, Node] {
|
||||||
let s = 0,
|
const elementIndex = this.getElementIndexForPosition(pos);
|
||||||
e = this.elementPositions.length - 1;
|
|
||||||
|
|
||||||
while (s <= e) {
|
return this.elementPositions[elementIndex];
|
||||||
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];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets `container` element content and return as string html content between `from` and `to`
|
* 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 = "";
|
this.container.innerHTML = "";
|
||||||
const range = new Range();
|
const range = new Range();
|
||||||
|
|
||||||
|
21
src/utils.ts
Normal file
21
src/utils.ts
Normal 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;
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user