Skip to content

Commit

Permalink
Gets selection for the editor
Browse files Browse the repository at this point in the history
Gets all DOM attributes required for insertion/deletion
  • Loading branch information
dalyIsaac committed Jul 7, 2019
1 parent a456358 commit 43424f6
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 122 deletions.
4 changes: 2 additions & 2 deletions src/editor/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ export type StackItem<T extends StackItemBase = StackItemBase> = T | Element;
export const STRUCTURE_NODE_INDEX = "structure_node";
export const CONTENT_NODE_START_INDEX = "content_node_start";
export const CONTENT_NODE_END_INDEX = "content_node_end";
export const CONTENT_NODE_START_OFFSET = "content_node_start_offset";
export const CONTENT_NODE_END_OFFSET = "content_node_end_offset";
export const CONTENT_NODE_START_OFFSET = "content_node_offset_start";
export const CONTENT_NODE_END_OFFSET = "content_node_offset_end";

export type Style = Partial<{ [key in keyof CSSStyleDeclaration]: string }>;

Expand Down
121 changes: 54 additions & 67 deletions src/editor/selection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */

import getEditorSelection from "./selection";
import { STRUCTURE_NODE_INDEX } from "./render";
import {
STRUCTURE_NODE_INDEX,
CONTENT_NODE_START_INDEX,
CONTENT_NODE_START_OFFSET,
CONTENT_NODE_END_INDEX,
CONTENT_NODE_END_OFFSET,
} from "./render";

class NamedNodeMapMock {
private dict: { [name: string]: Attr } = {};
Expand All @@ -18,13 +24,17 @@ class NamedNodeMapMock {
}
}

interface ShowItems {
interface StructureNodeAttributes {
[STRUCTURE_NODE_INDEX]?: boolean;
[CONTENT_NODE_START_INDEX]?: boolean;
[CONTENT_NODE_START_OFFSET]?: boolean;
[CONTENT_NODE_END_INDEX]?: boolean;
[CONTENT_NODE_END_OFFSET]?: boolean;
}

const checkProperty = (
show: ShowItems | undefined,
name: keyof ShowItems,
show: StructureNodeAttributes | undefined,
name: keyof StructureNodeAttributes,
): boolean => {
if (show === undefined) {
return true;
Expand All @@ -35,22 +45,40 @@ const checkProperty = (
}
};

const createElement = (show?: ShowItems) => {
const attributes = new NamedNodeMapMock();
const createAttribute = (
map: NamedNodeMapMock,
name: string,
value: string,
) => {
const attr = document.createAttribute(name);
attr.value = value;
map.setNamedItem(attr);
};

const createElement = (show?: StructureNodeAttributes) => {
const map = new NamedNodeMapMock();

if (checkProperty(show, STRUCTURE_NODE_INDEX)) {
const structurenodeindex = document.createAttribute(STRUCTURE_NODE_INDEX);
structurenodeindex.value = "2";
attributes.setNamedItem(structurenodeindex);
createAttribute(map, STRUCTURE_NODE_INDEX, "2");
}

if (checkProperty(show, CONTENT_NODE_START_INDEX)) {
createAttribute(map, CONTENT_NODE_START_INDEX, "3");
}

if (checkProperty(show, CONTENT_NODE_START_OFFSET)) {
createAttribute(map, CONTENT_NODE_START_OFFSET, "4");
}

if (checkProperty(show, CONTENT_NODE_END_INDEX)) {
createAttribute(map, CONTENT_NODE_END_INDEX, "5");
}

// if (checkProperty(show, IS_BREAK)) {
// const isbreak = document.createAttribute(IS_BREAK);
// isbreak.value = "true";
// attributes.setNamedItem(isbreak);
// }
if (checkProperty(show, CONTENT_NODE_END_OFFSET)) {
createAttribute(map, CONTENT_NODE_END_OFFSET, "6");
}

return { attributes };
return { attributes: map };
};

describe("markdown selection", (): void => {
Expand Down Expand Up @@ -78,52 +106,6 @@ describe("markdown selection", (): void => {
expect(getEditorSelection(shadowRoot)).toEqual(null);
});

// test("selection with elements which have break indicators", (): void => {
// shadowRoot.getSelection = jest.fn().mockReturnValue({
// anchorNode: createElement(),
// anchorOffset: 10,
// focusNode: createElement(),
// focusOffset: 11,
// });
// expect(getEditorSelection(shadowRoot)).toEqual({
// end: {
// isBreak: true,
// localOffset: 11,
// nodeOffset: 10,
// structureNodeIndex: 2,
// },
// start: {
// isBreak: true,
// localOffset: 10,
// nodeOffset: 10,
// structureNodeIndex: 2,
// },
// });
// });

// test("selection with elements which don't have break indicators", (): void => {
// shadowRoot.getSelection = jest.fn().mockReturnValue({
// anchorNode: createElement({ isbreak: false }),
// anchorOffset: 10,
// focusNode: createElement({ isbreak: false }),
// focusOffset: 11,
// });
// expect(getEditorSelection(shadowRoot)).toEqual({
// end: {
// isBreak: false,
// localOffset: 11,
// nodeOffset: 10,
// structureNodeIndex: 2,
// },
// start: {
// isBreak: false,
// localOffset: 10,
// nodeOffset: 10,
// structureNodeIndex: 2,
// },
// });
// });

test("selection with elements which don't have structurenodeindex", (): void => {
shadowRoot.getSelection = jest.fn().mockReturnValue({
anchorNode: createElement({ [STRUCTURE_NODE_INDEX]: false }),
Expand Down Expand Up @@ -152,14 +134,19 @@ describe("markdown selection", (): void => {
focusOffset: 11,
});
expect(getEditorSelection(shadowRoot)).toEqual({
end: {
// isBreak: true,
localOffset: 11,
anchor: {
end: { nodeIndex: 5, nodeStartOffset: 6 },
localOffset: 10,
start: { nodeIndex: 3, nodeStartOffset: 4 },
structureNodeIndex: 2,
},
start: {
// isBreak: true,
localOffset: 10,
focus: {
end: {
nodeIndex: 5,
nodeStartOffset: 6,
},
localOffset: 11,
start: { nodeIndex: 3, nodeStartOffset: 4 },
structureNodeIndex: 2,
},
});
Expand Down
109 changes: 58 additions & 51 deletions src/editor/selection.ts
Original file line number Diff line number Diff line change
@@ -1,66 +1,74 @@
import { STRUCTURE_NODE_INDEX } from "./render";

import {
STRUCTURE_NODE_INDEX,
CONTENT_NODE_START_INDEX,
CONTENT_NODE_START_OFFSET,
CONTENT_NODE_END_OFFSET,
CONTENT_NODE_END_INDEX,
} from "./render";
import { ContentBoundary } from "../page/contentTree/tree";

/**
* Describes a `StructureNode` in relation to its `RedBlackTree`, and its
* constituent `ContentNode`s.
*/
interface StructureNodeProperties {
structureNodeIndex: number;
// isBreak: boolean;
start: ContentBoundary;
end: ContentBoundary;
}

/**
* Describes a boundary for selection within the context of a `StructureNode`.
*/
interface SelectionBoundary extends StructureNodeProperties {
localOffset: number;
}

/**
* Describes the current editor selection.
*/
export interface EditorSelection {
start: SelectionBoundary;
end: SelectionBoundary;
anchor: SelectionBoundary;
focus: SelectionBoundary;
}

function isElement(item: Node | Element): item is Element {
return (item as Element).attributes !== undefined;
}

function getStructureNodeIndex(map: NamedNodeMap): number | null {
const attr = map.getNamedItem(STRUCTURE_NODE_INDEX);
function getIntegerAttribute(name: string, map: NamedNodeMap): number {
const attr = map.getNamedItem(name);
if (attr === null) {
return null;
throw new TypeError(`Could not find attribute "${name}"`);
}
return parseInt(attr.value);
}

// function getIsBreak(map: NamedNodeMap): boolean {
// const attr = map.getNamedItem(IS_BREAK);
// if (attr === null) {
// return false;
// }
// return Boolean(attr.value);
// }

function getStructureNodeProperties(
const getStructureNodeProperties = (
map: NamedNodeMap,
): StructureNodeProperties | null {
const structureNodeIndex = getStructureNodeIndex(map);
// const isbreak = getIsBreak(map);

if (structureNodeIndex === null) {
return null;
}

return {
// isBreak: isbreak,
structureNodeIndex: structureNodeIndex,
};
}

function getAttributes(
): StructureNodeProperties => ({
end: {
nodeIndex: getIntegerAttribute(CONTENT_NODE_END_INDEX, map),
nodeStartOffset: getIntegerAttribute(CONTENT_NODE_END_OFFSET, map),
},
start: {
nodeIndex: getIntegerAttribute(CONTENT_NODE_START_INDEX, map),
nodeStartOffset: getIntegerAttribute(CONTENT_NODE_START_OFFSET, map),
},
structureNodeIndex: getIntegerAttribute(STRUCTURE_NODE_INDEX, map),
});

function getNodeAttributes(
node: Node | Element | null,
): StructureNodeProperties | null {
): StructureNodeProperties {
if (node === null) {
return null;
throw new TypeError("The given node was null");
} else if (isElement(node)) {
return getStructureNodeProperties(node.attributes);
}

if (node.parentElement === null) {
return null;
throw new TypeError("The given node did not have a parent element");
} else {
return getStructureNodeProperties(node.parentElement.attributes);
}
Expand All @@ -76,22 +84,21 @@ export default function getEditorSelection(

const { anchorNode, anchorOffset, focusNode, focusOffset } = selection;

const anchorAttr = getAttributes(anchorNode);
const focusAttr = getAttributes(focusNode);

if (anchorAttr === null || focusAttr === null) {
try {
const anchorAttr = getNodeAttributes(anchorNode);
const focusAttr = getNodeAttributes(focusNode);

return {
anchor: {
...anchorAttr,
localOffset: anchorOffset,
},
focus: {
...focusAttr,
localOffset: focusOffset,
},
};
} catch (error) {
return null;
}

const start = {
...anchorAttr,
localOffset: anchorOffset,
};

const end = {
...focusAttr,
localOffset: focusOffset,
};

return { end, start };
}
9 changes: 7 additions & 2 deletions src/page/contentTree/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,19 @@ export const SENTINEL_CONTENT: ContentNode = {
start: { column: 0, line: 0 },
};

/**
* A boundary of a `StructureNode`, in regards to its constituent
* `ContentNode`s.
*/
export interface ContentBoundary {
/**
* The index of the node inside the array.
* The index of the `ContentNode` inside the `RedBlackTree`.
*/
nodeIndex: number;

/**
* The offset of the node against the start of the content.
* The offset of the boundary of the `StructureNode` in regards to the
* `ContentNode`.
*/
nodeStartOffset: number;
}
Expand Down

0 comments on commit 43424f6

Please sign in to comment.