Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(playground): document tree rendering #52

Merged
merged 10 commits into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ reports/

# stryker temp files
.stryker-tmp
*.tsbuildinfo
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { BlockNode, createDataKey } from './index.js';
import { BlockChildType } from './types/index.js';
import { NODE_TYPE_HIDDEN_PROP } from './consts.js';
import { ValueNode } from '../ValueNode/index.js';

describe('BlockNode integration tests', () => {
it('should create ValueNode by primitive value', () => {
Expand Down Expand Up @@ -100,4 +101,29 @@ describe('BlockNode integration tests', () => {
],
});
});

describe('.data', () => {
it('should return the data associated with this block node', () => {
// Arrange
const initData = {
key: 'value',
};
const blockNode = new BlockNode({
name: 'blockNode',
data: initData,
});

// Act
const data = blockNode.data;

// Assert
expect(data).toHaveProperty('key');

const valueNode = (data as {key: ValueNode}).key;

expect(valueNode).toBeInstanceOf(ValueNode);
expect(valueNode.serialized)
.toEqual(initData.key);
});
});
});
46 changes: 46 additions & 0 deletions packages/model/src/entities/BlockNode/BlockNode.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,52 @@ describe('BlockNode', () => {
});
});

describe('.name', () => {
it('should return a name of a tool that created a BlockNode', () => {
const blockNodeName = createBlockToolName('paragraph');

const blockNode = new BlockNode({
name: blockNodeName,
data: {},
parent: {} as EditorDocument,
});

expect(blockNode.name)
.toEqual(blockNodeName);
});
});

describe('.tunes', () => {
it('should return an object with tunes associated with the BlockNode', () => {
const blockTunesNames = [
'align' as BlockTuneName,
'font-size' as BlockTuneName,
'font-weight' as BlockTuneName,
];

const blockTunes = blockTunesNames.reduce((acc, name) => ({
...acc,
[name]: {},
}), {});

const blockNode = new BlockNode({
name: createBlockToolName('paragraph'),
data: {},
parent: {} as EditorDocument,
tunes: blockTunes,
});

const tunes = Object.entries(blockNode.tunes);

tunes.forEach(([name, tune]) => {
expect(name)
.toEqual(createBlockTuneName(name));
expect(tune)
.toBeInstanceOf(BlockTune);
});
});
});

describe('.updateTuneData()', () => {
afterEach(() => {
jest.clearAllMocks();
Expand Down
21 changes: 14 additions & 7 deletions packages/model/src/entities/BlockNode/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { TextNode } from '../inline-fragments/index.js';
import { get, has } from '../../utils/keypath.js';
import { NODE_TYPE_HIDDEN_PROP } from './consts.js';
import { mapObject } from '../../utils/mapObject.js';
import type { DeepReadonly } from '../../utils/DeepReadonly';
import { EventBus } from '../../utils/EventBus/EventBus.js';
import { EventType } from '../../utils/EventBus/types/EventType.js';
import {
Expand Down Expand Up @@ -97,18 +98,17 @@ export class BlockNode extends EventBus {
}

/**
* Getter to access BlockNode data
* Allows accessing Block name
*/
public get data(): Readonly<BlockNodeData> {
return this.#data;
public get name(): string {
return this.#name;
}


/**
* Getter to access BlockNode data
* Allows accessing Block data
*/
public get tunes(): Readonly<Record<string, BlockTune>> {
return this.#tunes;
public get data(): DeepReadonly<BlockNodeData> {
return this.#data;
}

/**
Expand All @@ -118,6 +118,13 @@ export class BlockNode extends EventBus {
return this.#parent;
}

/**
* Getter to access BlockNode data
*/
public get tunes(): Readonly<Record<string, BlockTune>> {
return this.#tunes;
}

/**
* Returns serialized object representing the BlockNode
*/
Expand Down
32 changes: 32 additions & 0 deletions packages/model/src/entities/EditorDocument/EditorDocument.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,38 @@ describe('EditorDocument', () => {
});
});

describe('.children', () => {
it('should return an array of the blocks of the document', () => {
// Arrange
const blocksCount = 3;
const document = new EditorDocument({
properties: {
readOnly: false,
},
});

for (let i = 0; i < blocksCount; i++) {
const blockData = {
name: 'header' as BlockToolName,
};

document.addBlock(blockData);
}

// Act
const actual = document.children;

// Assert
expect(actual)
.toHaveLength(blocksCount);

actual.forEach((block) => {
expect(block)
.toBeInstanceOf(BlockNode);
});
});
});

describe('.addBlock()', () => {
it('should add the block to the end of the document if index is not provided', () => {
const document = createEditorDocumentWithSomeBlocks();
Expand Down
8 changes: 8 additions & 0 deletions packages/model/src/entities/EditorDocument/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { InlineToolData, InlineToolName } from '../inline-fragments';
import { IoCContainer, TOOLS_REGISTRY } from '../../IoC/index.js';
import { ToolsRegistry } from '../../tools/index.js';
import type { BlockNodeSerialized } from '../BlockNode/types';
import type { DeepReadonly } from '../../utils/DeepReadonly';
import { EventBus } from '../../utils/EventBus/EventBus.js';
import { EventType } from '../../utils/EventBus/types/EventType.js';
import type {
Expand Down Expand Up @@ -60,6 +61,13 @@ export class EditorDocument extends EventBus {
this.#initialize(blocks);
}

/**
* Allows accessing Document child nodes
*/
public get children(): ReadonlyArray<DeepReadonly<BlockNode>> {
return this.#children;
}

/**
* Returns count of child BlockNodes of the EditorDocument.
*/
Expand Down
16 changes: 16 additions & 0 deletions packages/model/src/entities/ValueNode/ValueNode.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,20 @@ describe('ValueNode', () => {
expect(serializedValue).toHaveProperty(NODE_TYPE_HIDDEN_PROP, BlockChildType.Value);
});
});

describe('.value', () => {
it('should return the value associated with this value node', () => {
// Arrange
const longitude = 23.123;
const longitudeValueNode = new ValueNode({
value: longitude,
});

// Act
const serializedLongitude = longitudeValueNode.value;

// Assert
expect(serializedLongitude).toStrictEqual(longitude);
});
});
});
7 changes: 7 additions & 0 deletions packages/model/src/entities/ValueNode/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ export class ValueNode<ValueType = unknown> extends EventBus {

return value as ValueSerialized<ValueType>;
}

/**
* Returns the data associated with this value node.
*/
public get value(): Readonly<ValueType> {
return this.#value;
}
}

export type {
Expand Down
14 changes: 14 additions & 0 deletions packages/model/src/utils/DeepReadonly.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Make all properties in T readonly
*/
export type DeepReadonly<T> =
T extends (infer R)[] ? DeepReadonlyArray<R> :
T extends (...args: unknown[]) => unknown ? T :
T extends object ? DeepReadonlyObject<T> :
T;

interface DeepReadonlyArray<T> extends ReadonlyArray<DeepReadonly<T>> {}

type DeepReadonlyObject<T> = {
readonly [P in keyof T]: DeepReadonly<T[P]>;
};
4 changes: 3 additions & 1 deletion packages/model/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
"strict": true,
"skipLibCheck": true,
"experimentalDecorators": true,
"types": ["jest"]
"types": ["jest"],
"composite": true,
"rootDir": "src/"
},
"include": ["src/**/*"],
"exclude": [
Expand Down
4 changes: 4 additions & 0 deletions packages/playground/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,9 @@ module.exports = {
},
parser: 'vue-eslint-parser',
rules: {
'jsdoc/require-returns': 'off',
'jsdoc/require-param-type': 'off',
'vue/multi-word-component-names': 'off',

},
};
86 changes: 67 additions & 19 deletions packages/playground/src/App.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
<script setup lang="ts">
import { Node } from './components';
import { EditorDocument } from '@editorjs/model';

console.log('EditorDocument', new EditorDocument({

const document = new EditorDocument({
blocks: [
{
// id: 'zcKCF1S7X8',
Expand All @@ -15,7 +17,31 @@ console.log('EditorDocument', new EditorDocument({
// id: 'b6ji-DvaKb',
name: 'paragraph',
data: {
text: 'Hey. Meet the new Editor. On this page you can see it in action — try to edit this text. Source code of the page contains the example of connection and configuration.',
text: {
$t: 't',
value: 'Hey. Meet the new Editor. On this page you can see it in action — try to edit this text. Source code of the page contains the example of connection and configuration.',
fragments: [
{
range: [18, 24],
tool: 'link',
data: {
href: 'https://editorjs.io',
},
},
{
range: [26, 40],
tool: 'bold',
data: {
},
},
{
range: [34, 38],
tool: 'italic',
data: {
},
},
],
},
},
},
{
Expand Down Expand Up @@ -48,36 +74,58 @@ console.log('EditorDocument', new EditorDocument({
},
},
],
}));

});

console.log('document', document);
</script>

<template>
<div :class="$style.title">
<img
src="./assets/editorjs.svg"
alt="Editor.js logo"
>
Editor.js Document Playground
<div
:class="$style.container"
>
<div :class="$style.header">
<img
src="./assets/editorjs.svg"
alt="Editor.js logo"
>
Editor.js Document Playground
</div>
<div :class="$style.body">
<Node
:node="document"
/>
</div>
</div>
</template>

<style module>

.title {
.container {
height: 100%;
}

.body {
padding: 16px;
}

.header {
font-weight: 500;
padding: 8px 16px;
display: flex;
align-items: center;
position: sticky;
top: 0;
background: var(--background);
}

.title img {
height: 30px;
margin-right: 0.5em;
vertical-align: middle;
will-change: filter;
transition: filter 300ms;
.header img {
height: 20px;
margin-right: 0.6em;
}

.title img:hover {
filter: drop-shadow(0 0 20px #1CADFFaa);
.property {
font-family: var(--rounded-family);
font-size: 14px;
font-weight: 450;
}
</style>
Loading