diff --git a/src/__tests__/index.test.ts b/src/__tests__/index.test.ts
index 6e3ac66..aafab4a 100644
--- a/src/__tests__/index.test.ts
+++ b/src/__tests__/index.test.ts
@@ -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 = "
";
@@ -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(
"aa
bbccdd
"
);
- expect(hp.getFromRange(0, 3)).toEqual("aa
b
");
- expect(hp.getFromRange(5, 7)).toEqual("cd");
+ expect(hp.getContentFromRange(0, 3)).toEqual("aa
b
");
+ expect(hp.getContentFromRange(5, 7)).toEqual("cd");
});
});
+
+// 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");
+// });
+// });
diff --git a/src/index.ts b/src/index.ts
index f4965d0..5a26ffe 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -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;
/**
@@ -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();
diff --git a/src/utils.ts b/src/utils.ts
new file mode 100644
index 0000000..4138fa1
--- /dev/null
+++ b/src/utils.ts
@@ -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 = (
+ 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;
+};