Skip to content

Commit

Permalink
refactor: better init method (#86)
Browse files Browse the repository at this point in the history
* refactor: better init method

* feat: upgrade documents
  • Loading branch information
vincentdchan authored Nov 24, 2023
1 parent f7fbce2 commit 220e621
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 137 deletions.
41 changes: 12 additions & 29 deletions packages/blocky-example/app/noTitle/noTitle.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { Component, ReactNode, createRef } from "react";
import { useRef } from "react";
import {
BlockyEditor,
makeReactToolbar,
makeImageBlockPlugin,
useBlockyController,
} from "blocky-react";
import { EditorController, IPlugin } from "blocky-core";
import ImagePlaceholder from "@pkg/components/imagePlaceholder";
import { makeCommandPanelPlugin } from "@pkg/app/plugins/commandPanel";
import { makeAtPanelPlugin } from "@pkg/app/plugins/atPanel";
import ToolbarMenu from "@pkg/app/toolbarMenu";
import { timer, Subject, takeUntil } from "rxjs";

function makeEditorPlugins(): IPlugin[] {
return [
Expand Down Expand Up @@ -44,35 +44,18 @@ function makeController(
});
}

class NoTitleEditor extends Component {
controller: EditorController;
containerRef = createRef<HTMLDivElement>();
dispose$ = new Subject<void>();
function NoTitleEditor() {
const containerRef = useRef<HTMLDivElement>(null);

constructor(props: any) {
super(props);
this.controller = makeController("user", () => this.containerRef.current!);
}
const controller = useBlockyController(() => {
return makeController("user", () => containerRef.current!);
}, []);

componentDidMount(): void {
timer(0)
.pipe(takeUntil(this.dispose$))
.subscribe(() => {
this.controller.focus();
});
}

componentWillUnmount(): void {
this.dispose$.next();
}

render(): ReactNode {
return (
<div ref={this.containerRef} style={{ width: "100%", display: "flex" }}>
<BlockyEditor controller={this.controller} />
</div>
);
}
return (
<div ref={containerRef} style={{ width: "100%", display: "flex" }}>
<BlockyEditor controller={controller} autoFocus />
</div>
);
}

export default NoTitleEditor;
36 changes: 18 additions & 18 deletions packages/blocky-example/docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,24 @@ When the user begins to type, the content will be passed to the widget by the me

As usual, there are two ways to implement a follower widget: using the raw API or using Preact.

### React

Use the method `makePreactFollowerWidget`.

```tsx
import { makeReactFollowerWidget } from "blocky-react";

editor.insertFollowerWidget(
makeReactFollowerWidget(({ controller, editingValue, closeWidget }) => (
<CommandPanel
controller={controller}
editingValue={editingValue}
closeWidget={closeWidget}
/>
))
);
```

### VanillaJS

Extend the class `FollowerWidget`.
Expand All @@ -178,21 +196,3 @@ export class MyFollowWidget extends FollowerWidget {

editor.insertFollowerWidget(new MyFollowWidget());
```

### Preact

Use the method `makePreactFollowerWidget`.

```tsx
import { makePreactFollowerWidget } from "blocky-preact";

editor.insertFollowerWidget(
makePreactFollowerWidget(({ controller, editingValue, closeWidget }) => (
<CommandPanel
controller={controller}
editingValue={editingValue}
closeWidget={closeWidget}
/>
))
);
```
25 changes: 1 addition & 24 deletions packages/blocky-example/docs/builtin-plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,32 +18,9 @@ interface TextBlockAttributes {
Builtin types:

- Checkbox
- Numbered
- Bulleted
- Normal
- Heading1
- Heading2
- Heading3

## Styled text plugin

Add styles of bold/italic/underline.

```typescript
import makeStyledTextPlugin from "blocky-core/dist/plugins/styledTextPlugin";
```

## Headings plugin

Add styles of h1/h2/h3.

```typescript
import makeHeadingsPlugin from "blocky-core/dist/plugins/headingsPlugin";
```

## Bullet list plugin

Add commands of bullet list.

```typescript
import makeBulletListPlugin from "blocky-core/dist/plugins/bulletListPlugin";
```
8 changes: 8 additions & 0 deletions packages/blocky-example/docs/faq.md
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
# FAQ

## Is the Blocky editor based on other editors?

No, the Blocky editor is written from scratch using the native DOM API.

## Can I use the Blocky editor with Vue/Angular?

In theory, yes. The Blocky editor provides a full VanillaJS API, and you can add your bindings to your favorite frameworks. However, official support for Vue and Angular is not currently in the plans.
99 changes: 54 additions & 45 deletions packages/blocky-example/docs/get-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,19 +71,26 @@ function makeController(): EditorController {
Pass the editor to the component.

```tsx
import {
BlockyEditor,
makeReactToolbar,
makeImageBlockPlugin,
useBlockyController,
} from "blocky-react";
import { EditorController } from "blocky-core";

class App extends Component {
private editorController: EditorController;
function App() {
const containerRef = useRef<HTMLDivElement>(null);

constructor(props: {}) {
super(props);
this.editorController = makeController();
}
const controller = useBlockyController(() => {
return makeController("user", () => containerRef.current!);
}, []);

render() {
return <BlockyEditor controller={this.editorController} />;
}
return (
<div ref={containerRef} style={{ width: "100%", display: "flex" }}>
<BlockyEditor controller={controller} autoFocus />
</div>
);
}
```

Expand All @@ -103,25 +110,50 @@ The data model in Blocky Editor is represented as an XML Document:

Example:

```xml
<document>
<head>
<Title />
</head>
<body>
<Text />
<Text />
<Image src="" />
</Text>
</body>
</document>
```json
{
"t": "document",
"title": {
"t": "title",
"textContent": { "t": "rich-text", "ops": [] }
},
"body": {
"t": "body",
"children": [
/** Content */
]
}
}
```

## Write a block
## Define a block

You can use the plugin mechanism to extend the editor with
your custom block.

### Define a block with React

Implementing a block in Preact is more easier.

```tsx
import { type Editor, type IPlugin } from "blocky-core";
import { makeReactBlock, DefaultBlockOutline } from "blocky-preact";

export function makeMyBlockPlugin(): IPlugin {
return {
name: "plugin-name",
blocks: [
makeReactBlock({
name: "BlockName",
component: () => (
<DefaultBlockOutline>Write the block in Preact</DefaultBlockOutline>
),
}),
],
};
}
```

### VanillaJS

To implement a block, you need to implement two interfaces.
Expand Down Expand Up @@ -188,29 +220,6 @@ export function makeMyBlockPlugin(): IPlugin {
}
```

### Write a block in React

Implementing a block in Preact is more easier.

```tsx
import { type Editor, type IPlugin } from "blocky-core";
import { makeReactBlock, DefaultBlockOutline } from "blocky-preact";

export function makeMyBlockPlugin(): IPlugin {
return {
name: "plugin-name",
blocks: [
makeReactBlock({
name: "BlockName",
component: () => (
<DefaultBlockOutline>Write the block in Preact</DefaultBlockOutline>
),
}),
],
};
}
```

### Add the plugin to the controller

```tsx
Expand Down
63 changes: 42 additions & 21 deletions packages/blocky-react/src/editor.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,26 @@
import { Component, createRef, type RefObject } from "react";
import { Editor, type EditorController, CursorState } from "blocky-core";
import React, { useEffect, useState, useRef } from "react";
import { Editor, EditorController, CursorState } from "blocky-core";

export function useBlockyController(
generator: () => EditorController,
deps?: React.DependencyList | undefined
): EditorController | null {
const [controller, setController] = useState<EditorController | null>(null);

useEffect(() => {
const controller = generator();
setController(controller);

return () => {
controller.dispose();
};
}, deps);

return controller;
}

export interface Props {
controller: EditorController;
controller: EditorController | null;

/**
* If this flag is false,
Expand All @@ -14,32 +32,35 @@ export interface Props {
autoFocus?: boolean;
}

export class BlockyEditor extends Component<Props> {
private editor: Editor | undefined;
private containerRef: RefObject<HTMLDivElement> = createRef();
export function BlockyEditor(props: Props) {
const { controller, autoFocus, ignoreInitEmpty } = props;
const containerRef = useRef<HTMLDivElement>(null);

override componentDidMount() {
const { controller, autoFocus } = this.props;
this.editor = Editor.fromController(this.containerRef.current!, controller);
const editor = this.editor;
if (this.props.ignoreInitEmpty !== true) {
useEffect(() => {
if (!controller) {
return;
}
const editor = Editor.fromController(containerRef.current!, controller);
if (ignoreInitEmpty !== true) {
editor.initFirstEmptyBlock();
}
editor.fullRender(() => {
if (autoFocus) {
controller.setCursorState(CursorState.collapse("title", 0));
if (controller.state.document.title) {
controller.setCursorState(CursorState.collapse("title", 0));
} else {
controller.focus();
}
}
});
}

override componentWillUnmount() {
this.editor?.dispose();
this.editor = undefined;
}
return () => {
editor.dispose();
};
}, [controller, autoFocus, ignoreInitEmpty]);

render() {
return (
<div className="blocky-editor-container" ref={this.containerRef}></div>
);
if (!controller) {
return null;
}
return <div className="blocky-editor-container" ref={containerRef}></div>;
}

1 comment on commit 220e621

@vercel
Copy link

@vercel vercel bot commented on 220e621 Nov 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.