Skip to content

Commit

Permalink
Feat/number list (#77)
Browse files Browse the repository at this point in the history
* feat: add number list plugin

* feat: test plugin registry

* feat: add text plugin logic

* fix(typo): data id

* feat: add comment

* feat: redo manager plugin

* feat: remove $on in common

* feat: show number list

* feat: handle enter of number list
  • Loading branch information
vincentdchan authored Nov 13, 2023
1 parent 3a0e8fc commit 7b0b397
Show file tree
Hide file tree
Showing 25 changed files with 757 additions and 200 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ The developers only need to develop their blocks with their favourite UI framewo
- Data(![npm](https://img.shields.io/npm/v/blocky-data)): The data structure of the editor. Can be used without browser environment.
- Core(![npm](https://img.shields.io/npm/v/blocky-core)): The core of the editor. Written in vanilla JS. It can be used standalone without any
UI frameworks.
- Gzipped size: ~36kb
- Gzipped size: ~40kb
- React(![npm](https://img.shields.io/npm/v/blocky-react)): Wrap the editor in [React](https://react.dev/). Provide the UI facilities such as
toolbar and banner.
- [Example](https://blocky-editor.dev/): The example to demonstrate how to use the editor.
Expand Down
13 changes: 0 additions & 13 deletions packages/blocky-common/src/dom/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,19 +105,6 @@ export function listenWindow<K extends keyof WindowEventMap>(
};
}

export function $on<T extends HTMLElement, K extends keyof HTMLElementEventMap>(
element: T,
eventName: K,
listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any
): IDisposable {
element.addEventListener(eventName, listener);
return {
dispose: () => {
element.removeEventListener(eventName, listener);
},
};
}

export class DivContainer implements IDisposable {
readonly container: HTMLDivElement;

Expand Down
2 changes: 2 additions & 0 deletions packages/blocky-core/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@

/README.md

/coverage
7 changes: 6 additions & 1 deletion packages/blocky-core/css/blocky-core.css
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,16 @@
min-width: 26px;
}

.blocky-bullet-content::before {
.blocky-bullet-content:before {
content: "•";
color: var(--blocky-primary-color);
}

.blocky-number-content:before {
content: var(--pseudoBefore--content);
color: var(--blocky-primary-color);
}

.blocky-collaborative-cursor-rect {
position: absolute;
z-index: -1;
Expand Down
4 changes: 3 additions & 1 deletion packages/blocky-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"scripts": {
"build": "tsc -p tsconfig.json && tsc-alias -p tsconfig.json",
"test": "vitest",
"test:unit": "vitest --run"
"test:unit": "vitest --run",
"coverage": "vitest run --coverage"
},
"homepage": "https://github.com/vincentdchan/blocky-editor",
"keywords": [
Expand All @@ -24,6 +25,7 @@
"@types/lodash-es": "^4.17.6",
"@types/node": "^17.0.41",
"blocky-data": "workspace:*",
"c8": "^8.0.1",
"jsdom": "^20.0.0",
"tsc-alias": "^1.6.9",
"tslib": "^2.4.0",
Expand Down
3 changes: 3 additions & 0 deletions packages/blocky-core/src/block/basic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
Changeset,
Delta,
} from "blocky-data";
import { Subject } from "rxjs";
import { type Editor } from "@pkg/view/editor";
import { type EditorController } from "@pkg/view/controller";

Expand Down Expand Up @@ -142,6 +143,7 @@ export interface IBlockDefinition {
*/
export class Block implements IDisposable {
#editor: Editor | undefined;
readonly dispose$ = new Subject<void>();

get childrenContainerDOM(): HTMLElement | null {
return null;
Expand Down Expand Up @@ -213,6 +215,7 @@ export class Block implements IDisposable {
findTextOffsetInBlock?(focusedNode: Node, offsetInNode: number): number;

dispose(): void {
this.dispose$.next();
this.#editor = undefined;
}
}
Expand Down
57 changes: 49 additions & 8 deletions packages/blocky-core/src/block/textBlock.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { isObject, isString } from "lodash-es";
import { elem, removeNode, $on } from "blocky-common/es/dom";
import { isNumber, isObject, isString } from "lodash-es";
import { elem, removeNode } from "blocky-common/es/dom";
import { removeLineBreaks, type Position } from "blocky-common/es";
import {
type BlockDidMountEvent,
Expand Down Expand Up @@ -27,6 +27,7 @@ import { HTMLConverter } from "@pkg/helper/htmlConverter";
import { EditorController } from "@pkg/view/controller";
import type { SpanStyle } from "@pkg/registry/spanRegistry";
import type { Embed } from "@pkg/registry/embedRegistry";
import { Subject, fromEvent, takeUntil } from "rxjs";

const TextContentClass = "blocky-block-text-content";

Expand All @@ -44,9 +45,11 @@ function textTypeCanIndent(textType: TextType): boolean {
}

class LeftPadRenderer {
readonly dispose$ = new Subject<void>();
constructor(readonly container: HTMLDivElement) {}
render() {}
dispose(): void {
this.dispose$.next();
removeNode(this.container);
}
}
Expand All @@ -72,7 +75,9 @@ class CheckboxRenderer extends LeftPadRenderer {
this.#centerElement.style.visibility = "hidden";
this.#checkboxContainer.style.boxShadow = `0px 0px 0px 1px gray`;

$on(this.#checkboxContainer, "click", this.#handleClick);
fromEvent(this.#checkboxContainer, "click")
.pipe(takeUntil(this.dispose$))
.subscribe(this.#handleClick);
}

#handleClick = () => {
Expand Down Expand Up @@ -207,6 +212,11 @@ export class TextBlock extends Block {
return getTextTypeForTextBlock(this.elementData as BlockDataElement);
}

private getNumber(): number | undefined {
const elem = this.elementData as BlockDataElement;
return elem.getAttribute<number | undefined>("num");
}

override getCursorHeight(): number {
const textType = this.getTextType();
switch (textType) {
Expand Down Expand Up @@ -543,6 +553,22 @@ export class TextBlock extends Block {
return new LeftPadRenderer(container);
}

#createNumberRenderer(): LeftPadRenderer {
const container = this.#createLeftPadContainer();

const num = this.getNumber();

const numberContent = elem("div", "blocky-number-content");

if (isNumber(num)) {
numberContent.style.setProperty("--pseudoBefore--content", `"${num}."`);
}

container.appendChild(numberContent);

return new LeftPadRenderer(container);
}

#createCheckboxRenderer(): LeftPadRenderer {
const container = this.#createLeftPadContainer();

Expand Down Expand Up @@ -575,6 +601,15 @@ export class TextBlock extends Block {
return;
}

case TextType.Numbered: {
this.#leftPadRenderer = this.#createNumberRenderer();
blockContainer.insertBefore(
this.#leftPadRenderer.container,
blockContainer.firstChild
);
return;
}

case TextType.Heading1:
case TextType.Heading2:
case TextType.Heading3: {
Expand Down Expand Up @@ -791,11 +826,13 @@ export class TextBlock extends Block {
#createEmbedDomByOp(op: Op, editor: Editor): Node {
const embedContainer = elem("span");
embedContainer.contentEditable = "false";
$on(embedContainer, "click", (evt: MouseEvent) => {
evt.preventDefault();
// TODO: upload the event
// restore the selection
});
fromEvent<MouseEvent>(embedContainer, "click")
.pipe(takeUntil(this.dispose$))
.subscribe((evt: MouseEvent) => {
evt.preventDefault();
// TODO: upload the event
// restore the selection
});
embedContainer.appendChild(this.#createNoWrapSpan());

const embed = elem("span");
Expand Down Expand Up @@ -931,6 +968,10 @@ export class TextBlock extends Block {
embed.dispose?.();
}
this.#embeds.clear();

this.#leftPadRenderer?.dispose();
this.#leftPadRenderer = null;

super.dispose();
}
}
Expand Down
31 changes: 18 additions & 13 deletions packages/blocky-core/src/plugins/bulletListPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ function makeBulletListPlugin(): IPlugin {
}
}
};
const handleEnter = (editor: Editor, e: KeyboardEvent) => {
const handleEnter = (editor: Editor) => (e: KeyboardEvent) => {
const { cursorState } = editor.state;
if (!cursorState) {
return;
Expand Down Expand Up @@ -90,14 +90,7 @@ function makeBulletListPlugin(): IPlugin {
* If the user presses a Backspace on the start of a bullet list,
* turn it back to a normal text.
*/
const handleKeydown = (editor: Editor) => (e: KeyboardEvent) => {
if (e.key === "Enter") {
handleEnter(editor, e);
return;
}
if (e.key !== "Backspace") {
return;
}
const handleBackspace = (editor: Editor) => (e: KeyboardEvent) => {
const { cursorState } = editor.state;
if (!cursorState) {
return;
Expand Down Expand Up @@ -130,14 +123,26 @@ function makeBulletListPlugin(): IPlugin {
return {
name: "bullet-list",
onInitialized(context: PluginContext) {
const { editor, dispose$ } = context;
editor.textInput
const editor = context.editor;
editor.textInput$
.pipe(
takeUntil(dispose$),
takeUntil(context.dispose$),
filter((evt) => evt.blockElement.t === TextBlock.Name)
)
.subscribe(handleTextInputEvent(editor));
editor.keyDown.pipe(takeUntil(dispose$)).subscribe(handleKeydown(editor));
editor.keyDown$
.pipe(
takeUntil(context.dispose$),
filter((evt) => evt.key === "Enter")
)
.subscribe(handleEnter(editor));

editor.keyDown$
.pipe(
takeUntil(context.dispose$),
filter((evt) => evt.key === "Backspace")
)
.subscribe(handleBackspace(editor));
},
};
}
Expand Down
113 changes: 58 additions & 55 deletions packages/blocky-core/src/plugins/codeTextPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,69 +48,72 @@ function makeCodeTextPlugin(): IPlugin {
],
onInitialized(context: PluginContext) {
const { editor, dispose$ } = context;
editor.keyDown.pipe(takeUntil(dispose$)).subscribe((e: KeyboardEvent) => {
if (isHotkey("mod+m", e)) {
e.preventDefault();
editor.controller.formatTextOnSelectedText({
code: true,
});
return;
}
if (editor.composing) {
return;
}
if (e.key === "`") {
if (isUndefined(codeTextDetector)) {
if (editor.state.cursorState) {
codeTextDetector = new CodeTextDetector(
editor.state.cursorState,
(start: CursorState, end: CursorState) => {
editor.controller.enqueueNextTick(() => {
const blockElement = editor.state.getBlockElementById(
start.id
)!;
if (blockElement.t !== TextBlock.Name) {
return;
}
const textModel = blockElement.getTextModel("textContent")!;
const fullString = textModel.toString();
const codeContent = fullString.slice(
start.offset + 1,
end.offset
);
new Changeset(editor.state)
.textEdit(blockElement, "textContent", () =>
new Delta()
.retain(start.offset)
.delete(end.offset + 1 - start.offset)
.insert(codeContent, {
code: true,
})
)
.setCursorState(
CursorState.collapse(start.id, end.offset - 1)
)
.apply();
});
editor.keyDown$
.pipe(takeUntil(dispose$))
.subscribe((e: KeyboardEvent) => {
if (isHotkey("mod+m", e)) {
e.preventDefault();
editor.controller.formatTextOnSelectedText({
code: true,
});
return;
}
if (editor.composing) {
return;
}
if (e.key === "`") {
if (isUndefined(codeTextDetector)) {
if (editor.state.cursorState) {
codeTextDetector = new CodeTextDetector(
editor.state.cursorState,
(start: CursorState, end: CursorState) => {
editor.controller.enqueueNextTick(() => {
const blockElement = editor.state.getBlockElementById(
start.id
)!;
if (blockElement.t !== TextBlock.Name) {
return;
}
const textModel =
blockElement.getTextModel("textContent")!;
const fullString = textModel.toString();
const codeContent = fullString.slice(
start.offset + 1,
end.offset
);
new Changeset(editor.state)
.textEdit(blockElement, "textContent", () =>
new Delta()
.retain(start.offset)
.delete(end.offset + 1 - start.offset)
.insert(codeContent, {
code: true,
})
)
.setCursorState(
CursorState.collapse(start.id, end.offset - 1)
)
.apply();
});
}
);
}
} else {
if (codeTextDetector) {
const test = codeTextDetector.emit(editor.state.cursorState);
if (!test) {
codeTextDetector = undefined;
}
);
}
}
} else {
if (codeTextDetector) {
const test = codeTextDetector.emit(editor.state.cursorState);
if (!test) {
if (!codeTextDetector.emitNonDot(editor.state.cursorState)) {
codeTextDetector = undefined;
}
}
}
} else {
if (codeTextDetector) {
if (!codeTextDetector.emitNonDot(editor.state.cursorState)) {
codeTextDetector = undefined;
}
}
}
});
});
},
};
}
Expand Down
Loading

1 comment on commit 7b0b397

@vercel
Copy link

@vercel vercel bot commented on 7b0b397 Nov 13, 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.