Added method for computing content nodes positions and getting text from range
This commit is contained in:
parent
cc2e81e648
commit
b7e3c1b2f5
48
src/__tests__/index.test.ts
Normal file
48
src/__tests__/index.test.ts
Normal file
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
|
||||
import { CacheInterface, HTMLPagination } from "../index";
|
||||
|
||||
describe("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);
|
||||
}
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
content.innerHTML = "";
|
||||
content.innerHTML = "<div><p>aa</p><p>bb<span>cc</span>dd</p></div>";
|
||||
|
||||
container.innerHTML = "";
|
||||
|
||||
hp = new HTMLPagination(content, container, new Cache());
|
||||
});
|
||||
|
||||
test("Computes positions for each text node", () => {
|
||||
expect(hp.elementPositions.map((el) => el[0])).toEqual([0, 2, 4, 6]);
|
||||
});
|
||||
|
||||
test("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(
|
||||
"<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");
|
||||
});
|
||||
});
|
9
src/cache.ts
Normal file
9
src/cache.ts
Normal file
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Interface to any storage which supports key-value storage
|
||||
*/
|
||||
export abstract class CacheInterface {
|
||||
/** Get */
|
||||
abstract g(key: string): string | null;
|
||||
/** Set */
|
||||
abstract s(key: string, value: string): void;
|
||||
}
|
118
src/index.ts
118
src/index.ts
@ -1,22 +1,100 @@
|
||||
export declare abstract class CacheInterface {
|
||||
abstract g(key: string): string;
|
||||
abstract s(key: string, value: string): void;
|
||||
import { CacheInterface } from "./cache";
|
||||
import { isElementNode, isTextNode, TextNode } from "./types";
|
||||
|
||||
class HTMLPagination {
|
||||
content: HTMLElement;
|
||||
container: HTMLElement;
|
||||
cache: CacheInterface;
|
||||
|
||||
elementPositions: [number, TextNode][];
|
||||
idPositions: Map<string, number>;
|
||||
|
||||
/**
|
||||
* @param content HTML element with html content to display paginationly
|
||||
* @param container HTML element which will store content to display
|
||||
* @param cache Class implementing `g` and `s` methods for getting and setting elements of KV storage
|
||||
*/
|
||||
constructor(
|
||||
content: HTMLElement,
|
||||
container: HTMLElement,
|
||||
cache: CacheInterface
|
||||
) {
|
||||
this.content = content;
|
||||
this.container = container;
|
||||
this.cache = cache;
|
||||
|
||||
this.elementPositions = new Array();
|
||||
this.idPositions = new Map();
|
||||
|
||||
this.computeElementsPositions();
|
||||
}
|
||||
|
||||
getPage(n: number): string {
|
||||
const from = 0;
|
||||
const to = 1;
|
||||
|
||||
return this.getFromRange(from, to);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes html elements and text nodes positions. Must be run only on first setup
|
||||
*/
|
||||
computeElementsPositions(): void {
|
||||
const recursive = (currentPosition: number, root: Node): number => {
|
||||
if (isTextNode(root)) {
|
||||
this.elementPositions.push([currentPosition, root]);
|
||||
|
||||
return currentPosition + root.nodeValue.length;
|
||||
} else if (isElementNode(root)) {
|
||||
if (root.id !== null) this.idPositions.set(root.id, currentPosition);
|
||||
|
||||
return Array.from(root.childNodes).reduce(recursive, currentPosition);
|
||||
} else {
|
||||
return currentPosition;
|
||||
}
|
||||
};
|
||||
|
||||
recursive(0, this.content);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets `container` element content and return as string html content between `from` and `to`
|
||||
*/
|
||||
getFromRange(from: number, to: number): string {
|
||||
this.container.innerHTML = "";
|
||||
const range = new Range();
|
||||
|
||||
const [startPosition, startElement] = this.getElementForPosition(from);
|
||||
const startOffset = from - startPosition;
|
||||
range.setStart(startElement, startOffset);
|
||||
|
||||
const [endPosition, endElement] = this.getElementForPosition(to);
|
||||
const endOffset = to - endPosition;
|
||||
range.setEnd(endElement, endOffset);
|
||||
|
||||
// TODO: copy range with all its parent elements
|
||||
|
||||
this.container.appendChild(range.cloneContents());
|
||||
|
||||
return this.container.innerHTML;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to get page with specific number
|
||||
* @param n Page number
|
||||
*/
|
||||
declare function getPage(n: number): string;
|
||||
|
||||
/**
|
||||
* Function to prepare unific data and compose page content getting function
|
||||
* @param content HTML element with html content to display paginationly
|
||||
* @param container HTML element which will store content to display
|
||||
* @param cache Class implementing `g` and `s` methods for getting and setting elements of KV storage
|
||||
*/
|
||||
export declare function setup(
|
||||
content: HTMLElement,
|
||||
container: HTMLElement,
|
||||
cache: CacheInterface
|
||||
): typeof getPage;
|
||||
export { CacheInterface, HTMLPagination };
|
||||
|
7
src/types.ts
Normal file
7
src/types.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export type TextNode = Node & { nodeValue: string };
|
||||
|
||||
export const isTextNode = (el: Node): el is TextNode =>
|
||||
el.nodeType === Node.TEXT_NODE;
|
||||
|
||||
export const isElementNode = (el: Node): el is HTMLElement =>
|
||||
el.nodeType === Node.ELEMENT_NODE;
|
Loading…
x
Reference in New Issue
Block a user