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

refactor: better init method #86

Merged
merged 2 commits into from
Nov 24, 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
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>;
}
Loading