Added method for computing content nodes positions and getting text from range

This commit is contained in:
Dmitriy Shishkov 2021-08-03 14:00:22 +03:00
parent cc2e81e648
commit b7e3c1b2f5
No known key found for this signature in database
GPG Key ID: 14358F96FCDD8060
4 changed files with 162 additions and 20 deletions

View 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
View 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;
}

View File

@ -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
View 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;