@aionbuilders/nabu 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +131 -0
  2. package/dist/behaviors/index.d.ts +1 -0
  3. package/dist/behaviors/index.js +1 -0
  4. package/dist/behaviors/text/RichText.svelte +33 -0
  5. package/dist/behaviors/text/RichText.svelte.d.ts +11 -0
  6. package/dist/behaviors/text/index.d.ts +3 -0
  7. package/dist/behaviors/text/index.js +3 -0
  8. package/dist/behaviors/text/rich-text.extension.d.ts +2 -0
  9. package/dist/behaviors/text/rich-text.extension.js +75 -0
  10. package/dist/behaviors/text/text.behavior.svelte.d.ts +103 -0
  11. package/dist/behaviors/text/text.behavior.svelte.js +346 -0
  12. package/dist/blocks/Block.svelte +18 -0
  13. package/dist/blocks/Block.svelte.d.ts +11 -0
  14. package/dist/blocks/Nabu.svelte +31 -0
  15. package/dist/blocks/Nabu.svelte.d.ts +12 -0
  16. package/dist/blocks/block.svelte.d.ts +143 -0
  17. package/dist/blocks/block.svelte.js +364 -0
  18. package/dist/blocks/container.utils.d.ts +28 -0
  19. package/dist/blocks/container.utils.js +114 -0
  20. package/dist/blocks/heading/Heading.svelte +42 -0
  21. package/dist/blocks/heading/Heading.svelte.d.ts +11 -0
  22. package/dist/blocks/heading/heading.svelte.d.ts +45 -0
  23. package/dist/blocks/heading/heading.svelte.js +94 -0
  24. package/dist/blocks/heading/hooks/onBeforeInput.hook.d.ts +3 -0
  25. package/dist/blocks/heading/hooks/onBeforeInput.hook.js +58 -0
  26. package/dist/blocks/heading/index.d.ts +7 -0
  27. package/dist/blocks/heading/index.js +41 -0
  28. package/dist/blocks/index.d.ts +10 -0
  29. package/dist/blocks/index.js +12 -0
  30. package/dist/blocks/list/List.svelte +25 -0
  31. package/dist/blocks/list/List.svelte.d.ts +11 -0
  32. package/dist/blocks/list/ListItem.svelte +45 -0
  33. package/dist/blocks/list/ListItem.svelte.d.ts +11 -0
  34. package/dist/blocks/list/index.d.ts +10 -0
  35. package/dist/blocks/list/index.js +41 -0
  36. package/dist/blocks/list/list-item.svelte.d.ts +50 -0
  37. package/dist/blocks/list/list-item.svelte.js +213 -0
  38. package/dist/blocks/list/list.behavior.svelte.d.ts +23 -0
  39. package/dist/blocks/list/list.behavior.svelte.js +61 -0
  40. package/dist/blocks/list/list.svelte.d.ts +39 -0
  41. package/dist/blocks/list/list.svelte.js +139 -0
  42. package/dist/blocks/megablock.svelte.d.ts +13 -0
  43. package/dist/blocks/megablock.svelte.js +64 -0
  44. package/dist/blocks/nabu.svelte.d.ts +121 -0
  45. package/dist/blocks/nabu.svelte.js +395 -0
  46. package/dist/blocks/paragraph/Paragraph.svelte +38 -0
  47. package/dist/blocks/paragraph/Paragraph.svelte.d.ts +11 -0
  48. package/dist/blocks/paragraph/index.d.ts +7 -0
  49. package/dist/blocks/paragraph/index.js +44 -0
  50. package/dist/blocks/paragraph/paragraph.svelte.d.ts +41 -0
  51. package/dist/blocks/paragraph/paragraph.svelte.js +86 -0
  52. package/dist/blocks/selection.svelte.d.ts +38 -0
  53. package/dist/blocks/selection.svelte.js +143 -0
  54. package/dist/index.d.ts +4 -0
  55. package/dist/index.js +5 -0
  56. package/dist/utils/extensions.d.ts +69 -0
  57. package/dist/utils/extensions.js +43 -0
  58. package/dist/utils/index.d.ts +2 -0
  59. package/dist/utils/index.js +2 -0
  60. package/dist/utils/selection.svelte.d.ts +219 -0
  61. package/dist/utils/selection.svelte.js +611 -0
  62. package/package.json +74 -0
@@ -0,0 +1,61 @@
1
+ import { MegaBlock } from "..";
2
+
3
+ /**
4
+ * @import {Block, NabuNode} from "..";
5
+ * @import {LoroText} from "loro-crdt";
6
+ */
7
+
8
+ /**
9
+ * @typedef {NabuNode<{type: "list", listType: "bullet" | "ordered"}>} ListNode
10
+ */
11
+
12
+ export class ListBehavior {
13
+ /** @param {Block} block */
14
+ constructor(block) {
15
+ this.block = block;
16
+ this.node = /** @type {ListNode} */ (block.node);
17
+ this.listType = /** @type {"bullet" | "ordered"} */ (this.node.data.get("listType") || "bullet");
18
+ this.node.data.subscribe(() => {
19
+ this.listType = /** @type {"bullet" | "ordered"} */ (this.node.data.get("listType") || "bullet");
20
+ });
21
+ }
22
+
23
+ /** @type {"bullet" | "ordered"} */
24
+ listType = $state("bullet");
25
+
26
+ /** @param {Block} block */
27
+ absorbs(block) {
28
+ const behavior = block?.behaviors.get("list");
29
+ if (!(behavior && behavior instanceof ListBehavior)) {
30
+ console.warn("Cannot merge: target block is not a list.");
31
+ return;
32
+ }
33
+ if (!(block instanceof MegaBlock)) {
34
+ console.warn("Cannot merge: target block is not a mega block.");
35
+ return;
36
+ }
37
+
38
+ if (behavior.listType !== this.listType) {
39
+ console.warn("Cannot merge lists of different types.");
40
+ return;
41
+ }
42
+
43
+ // 1. On fusionne les enfants de l'autre liste dans la nôtre
44
+ const otherChildren = block.node.children() || [];
45
+ otherChildren.forEach(child => {
46
+ console.warn("Merging child with id", child.id.toString(), "from list", block.id, "into list", this.block.id);
47
+ const data = /** @type {LoroText} */ (child.data.get("text"));
48
+ const targetIndex = this.block.node.children()?.length; // On ajoute à la fin de notre liste
49
+ if (targetIndex === undefined || targetIndex === null) {
50
+ console.error("Cannot merge: current block's node has no children array.");
51
+ return;
52
+ }
53
+ child.move(this.block.node, targetIndex);
54
+ });
55
+
56
+ // block.destroy();
57
+
58
+
59
+ return true;
60
+ }
61
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @import { Nabu, NabuNode } from "../nabu.svelte";
3
+ */
4
+ /**
5
+ * @typedef {NabuNode<{type: "list", listType: "bullet" | "ordered"}>} ListNode
6
+ */
7
+ export class List extends MegaBlock {
8
+ /** @param {Nabu} nabu @param {string} type @param {Object} [props={}] @param {string|null} [parentId=null] @param {number|null} [index=null] */
9
+ static create(nabu: Nabu, type: string, props?: Object, parentId?: string | null, index?: number | null): List;
10
+ /** @param {Nabu} nabu @param {ListNode} node */
11
+ constructor(nabu: Nabu, node: ListNode);
12
+ behavior: ListBehavior;
13
+ listType: "bullet" | "ordered";
14
+ component: import("svelte").Component<any, any, string> | import("svelte").Component<{
15
+ block: import("./list.svelte").List;
16
+ }, {}, "">;
17
+ root: boolean;
18
+ /** @param {List} otherList */
19
+ absorbs(otherList: List): true | undefined;
20
+ /**
21
+ * @param {Event | null} event
22
+ * @param {{from: import('./list-item.svelte.js').ListItem, offset: number, delta: import('loro-crdt').Delta<string>}} data
23
+ */
24
+ onSplit(event: Event | null, data: {
25
+ from: import("./list-item.svelte.js").ListItem;
26
+ offset: number;
27
+ delta: import("loro-crdt").Delta<string>;
28
+ }): {
29
+ block: import("..").Block;
30
+ };
31
+ }
32
+ export type ListNode = NabuNode<{
33
+ type: "list";
34
+ listType: "bullet" | "ordered";
35
+ }>;
36
+ import { MegaBlock } from "../megablock.svelte";
37
+ import { ListBehavior } from "./list.behavior.svelte";
38
+ import type { Nabu } from "../nabu.svelte";
39
+ import type { NabuNode } from "../nabu.svelte";
@@ -0,0 +1,139 @@
1
+ import { MegaBlock } from "../megablock.svelte";
2
+ import { ListBehavior } from "./list.behavior.svelte";
3
+ import ListComponent from "./List.svelte";
4
+
5
+ /**
6
+ * @import { Nabu, NabuNode } from "../nabu.svelte";
7
+ */
8
+
9
+ /**
10
+ * @typedef {NabuNode<{type: "list", listType: "bullet" | "ordered"}>} ListNode
11
+ */
12
+
13
+
14
+
15
+ export class List extends MegaBlock {
16
+ /** @param {Nabu} nabu @param {ListNode} node */
17
+ constructor(nabu, node) {
18
+ super(nabu, node);
19
+
20
+ this.behavior = new ListBehavior(this);
21
+ this.behaviors.set("list", this.behavior);
22
+
23
+ this.serializers.set('markdown', () =>
24
+ this.children
25
+ .map(child => child.serialize('markdown'))
26
+ .filter(Boolean)
27
+ .join('\n')
28
+ );
29
+ this.serializers.set('json', () => ({
30
+ id: this.id,
31
+ type: 'list',
32
+ props: { listType: this.listType },
33
+ children: this.children.map(child => child.serialize('json')).filter(Boolean)
34
+ }));
35
+ }
36
+
37
+ listType = $derived(this.behavior.listType);
38
+
39
+ component = $derived(this.nabu.components.get("list") || ListComponent);
40
+
41
+ root = $derived(!this.parent);
42
+
43
+
44
+ /** @param {List} otherList */
45
+ absorbs(otherList) {
46
+ return this.behavior.absorbs(otherList);
47
+ }
48
+
49
+ /**
50
+ * @param {Event | null} event
51
+ * @param {{from: import('./list-item.svelte.js').ListItem, offset: number, delta: import('loro-crdt').Delta<string>}} data
52
+ */
53
+ onSplit(event, data) {
54
+ const { from: sourceItem, offset, delta } = data;
55
+
56
+ // --- CAS 1 : L'item est vide (Demande de sortie de liste) ---
57
+ if (sourceItem.text.length === 0) {
58
+ const currentIndex = sourceItem.node.index();
59
+ const siblings = this.node.children();
60
+ const followers = siblings.slice(currentIndex + 1);
61
+
62
+ // 1. On identifie le point d'insertion (juste après la liste actuelle)
63
+ const parentNode = this.node.parent();
64
+ const grandParentId = parentNode?.id.toString() || null;
65
+ const myIndexInGrandParent = this.node.index();
66
+
67
+ // 2. On insère le paragraphe de sortie juste après cette liste
68
+ const newParagraph = this.nabu.insert("paragraph", {}, grandParentId, myIndexInGrandParent + 1);
69
+
70
+ // 3. Si on était au milieu de la liste, on crée une nouvelle liste après le paragraphe pour les "followers"
71
+ if (followers.length > 0) {
72
+ const newList = this.nabu.insert("list", { listType: this.listType }, grandParentId, myIndexInGrandParent + 2);
73
+ for (const follower of followers) {
74
+ this.nabu.tree.move(follower.id.toString(), newList.node.id.toString());
75
+ }
76
+ }
77
+
78
+ // 4. On détruit l'item vide actuel
79
+ sourceItem.destroy();
80
+
81
+ // 5. Si la liste d'origine est devenue vide, on la supprime aussi
82
+ if (currentIndex === 0 && followers.length === 0) {
83
+ this.destroy();
84
+ }
85
+
86
+ this.nabu.commit();
87
+
88
+ setTimeout(() => {
89
+ this.nabu.selection.setCursor(newParagraph, 0);
90
+ }, 0);
91
+
92
+ return { block: newParagraph };
93
+ }
94
+
95
+ // --- CAS 2 : Comportement normal (Créer un nouvel item en dessous) ---
96
+
97
+ // 1. On efface la fin du texte dans l'item source
98
+ sourceItem.delete({from: offset, to: -1});
99
+
100
+ // 2. On trouve l'index de l'item source dans la liste
101
+ const currentIndex = sourceItem.node.index();
102
+
103
+
104
+ // 3. On demande à Nabu d'insérer un nouveau list-item au même niveau
105
+ const newItem = this.nabu.insert(
106
+ "list-item",
107
+ { delta },
108
+ this.node.id.toString(), // Le parent est la liste actuelle
109
+ currentIndex + 1
110
+ );
111
+
112
+ // 3.1. On transfère TOUS les enfants du sourceItem vers le newItem
113
+ const sourceChildren = sourceItem.node.children();
114
+ if (sourceChildren && sourceChildren.length > 0) {
115
+ for (const childNode of sourceChildren) {
116
+ // @ts-ignore - Le type string fonctionne avec move()
117
+ this.nabu.tree.move(childNode.id.toString(), newItem.node.id.toString());
118
+ }
119
+ }
120
+
121
+ this.nabu.commit();
122
+
123
+ // 4. On replace le curseur au début du nouvel item
124
+ setTimeout(() => {
125
+ this.nabu.selection.setCursor(newItem, 0);
126
+ }, 0);
127
+
128
+ return { block: newItem };
129
+ }
130
+
131
+ /** @param {Nabu} nabu @param {string} type @param {Object} [props={}] @param {string|null} [parentId=null] @param {number|null} [index=null] */
132
+ static create(nabu, type, props = {}, parentId = null, index = null) {
133
+ const node = nabu.tree.createNode(parentId || undefined, index || undefined);
134
+ node.data.set("type", "list");
135
+ node.data.set("listType", props.listType || "bullet");
136
+ const block = new List(nabu, node);
137
+ return block;
138
+ }
139
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @import { Nabu, NabuNode } from "./nabu.svelte";
3
+ */
4
+ export class MegaBlock extends Block {
5
+ /** @type {Block[]} */
6
+ children: Block[];
7
+ updateChildren(): void;
8
+ /** @param {Block[]} children @param {number | null} [index] */
9
+ adoptChildren(children: Block[], index?: number | null): {
10
+ skipped: boolean | Block[];
11
+ };
12
+ }
13
+ import { Block } from "./block.svelte";
@@ -0,0 +1,64 @@
1
+ import { Block } from "./block.svelte";
2
+ import { handleContainerBeforeInput } from "./container.utils.js";
3
+
4
+ /**
5
+ * @import { Nabu, NabuNode } from "./nabu.svelte";
6
+ */
7
+
8
+ export class MegaBlock extends Block {
9
+
10
+ /** @param {Nabu} nabu @param {NabuNode} node */
11
+ constructor(nabu, node) {
12
+ super(nabu, node);
13
+ this.updateChildren();
14
+ }
15
+
16
+ /** @type {Block[]} */
17
+ children = $state([]);
18
+
19
+ updateChildren() {
20
+ const childrenNodes = this.node.children();
21
+ if (!childrenNodes) return;
22
+ this.children = childrenNodes.map((childNode, i) => {
23
+ const id = childNode.id.toString();
24
+ let block = this.nabu.blocks.get(id);
25
+ const currentType = childNode.data.get("type");
26
+
27
+ if (!block || block.type !== currentType) {
28
+ if (block) {
29
+ this.nabu.blocks.delete(id);
30
+ this.nabu.blocksByType.get(block.type)?.delete(block);
31
+ }
32
+ block = Block.load(this.nabu, childNode);
33
+ }
34
+ block.parent = this;
35
+ block.index = i;
36
+ return block;
37
+ });
38
+ }
39
+
40
+
41
+ /** @param {Block[]} children @param {number | null} [index] */
42
+ adoptChildren(children, index = null) {
43
+ /** @type {Block[]} */
44
+ let skipped = [];
45
+ children.reverse().forEach(child => {
46
+ const childNode = child.node;
47
+ if (!childNode) return;
48
+ if (false) {
49
+ //TODO: conditions de skip, ex: si le block est déjà dans la mega block, ou si c'est un block parent de la mega block, etc.
50
+ console.warn("Skipping child with id", child.id, "because ...");
51
+ return skipped.push(child);
52
+ }
53
+ this.nabu.tree.move(childNode.id, this.node.id, index);
54
+ })
55
+
56
+ return {skipped: skipped.length ? skipped.reverse() : false};
57
+ }
58
+
59
+
60
+ /** @param {InputEvent} event */
61
+ beforeinput(event) {
62
+ return handleContainerBeforeInput(this, this.nabu, event);
63
+ }
64
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * @import {Component} from "svelte";
3
+ * @import { LoroTreeNode, LoroTree } from "loro-crdt";
4
+ * @import {Extension} from '../utils/extensions.js';
5
+ */
6
+ /**
7
+ * @template {Object<string, any>} [T={}]
8
+ * @typedef {LoroTreeNode<{type: string} & T>} NabuNode
9
+ */
10
+ /**
11
+ * @typedef {Object} NabuInit
12
+ * @property {Extension[]} [extensions]
13
+ * @property {Uint8Array<ArrayBufferLike>} [snapshot]
14
+ */
15
+ export class Nabu {
16
+ static BREAK: symbol;
17
+ static CONTINUE: symbol;
18
+ /** @param {NabuInit} init */
19
+ constructor(init?: NabuInit);
20
+ /** @type {LoroDoc} */
21
+ doc: LoroDoc;
22
+ selection: NabuSelection;
23
+ tree: LoroTree<Record<string, NabuNode<{}>>>;
24
+ content: import("loro-crdt").LoroMap<Record<string, unknown>>;
25
+ undoManager: UndoManager;
26
+ extensions: Extension[];
27
+ BREAK: symbol;
28
+ CONTINUE: symbol;
29
+ /** @type {SvelteMap<string, typeof Block>} */
30
+ registry: SvelteMap<string, typeof Block>;
31
+ /** @type {SvelteMap<string, Component>} */
32
+ components: SvelteMap<string, Component>;
33
+ /** @type {SvelteMap<string, Block>} */
34
+ blocks: SvelteMap<string, Block>;
35
+ /** @type {SvelteMap<string, SvelteSet<Block>>} */
36
+ blocksByType: SvelteMap<string, SvelteSet<Block>>;
37
+ /** @type {SvelteMap<string, any>} */
38
+ systems: SvelteMap<string, any>;
39
+ hooks: Map<any, any>;
40
+ /**
41
+ * Root-level serializers. Each function receives the Nabu instance and returns the serialized document.
42
+ * @type {Map<string, (nabu: Nabu) => any>}
43
+ */
44
+ serializers: Map<string, (nabu: Nabu) => any>;
45
+ /** @type {Block[]} */
46
+ children: Block[];
47
+ get isEmpty(): boolean;
48
+ /** @param {string} format */
49
+ serialize(format: string): any;
50
+ init(): void;
51
+ /** Rafraîchit les blocs racines de l'éditeur */
52
+ updateRoots(): void;
53
+ trigger(hookName: any, ...args: any[]): void;
54
+ commit(): void;
55
+ /**
56
+ * Undo the last operation
57
+ */
58
+ undo(): void;
59
+ /**
60
+ * Redo the last undone operation
61
+ */
62
+ redo(): void;
63
+ /** @param {{from: {offset: number, block: Block}, to: {offset: number, block: Block}}} [options={}] */
64
+ focus(options?: {
65
+ from: {
66
+ offset: number;
67
+ block: Block;
68
+ };
69
+ to: {
70
+ offset: number;
71
+ block: Block;
72
+ };
73
+ }): void;
74
+ /**
75
+ * Insère un nouveau bloc dans le document
76
+ * @param {string} type - Le type du bloc (ex: 'paragraph')
77
+ * @param {Object} [props={}] - Les propriétés initiales
78
+ * @param {string|null} [parentId=null] - ID du parent (null pour racine)
79
+ * @param {number|null} [index=null] - Position dans la liste des enfants
80
+ */
81
+ insert(type: string, props?: Object, parentId?: string | null, index?: number | null): Block;
82
+ /** @param {Block} block */
83
+ delete(block: Block): void;
84
+ /**
85
+ * @param {string} nodeId
86
+ * @returns
87
+ */
88
+ deleteNode(nodeId: string): void;
89
+ /**
90
+ * Route un événement vers le bon bloc en fonction de la sélection courante.
91
+ * @param {string} handlerName - Le nom de la méthode à appeler sur le bloc (ex: 'beforeinput', 'keydown')
92
+ * @param {Event} e - L'événement natif
93
+ * @param {string} [hookName] - Optionnel : Le nom du hook d'extension à vérifier en premier
94
+ */
95
+ dispatchEventToSelection(handlerName: string, e: Event, hookName?: string): void;
96
+ /** @param {InputEvent} e */
97
+ handleBeforeinput(e: InputEvent): void;
98
+ /** @param {KeyboardEvent} e */
99
+ handleKeydown(e: KeyboardEvent): void;
100
+ /** @param {InputEvent} e */
101
+ beforeinput(e: InputEvent): any;
102
+ }
103
+ export type NabuNode<T extends {
104
+ [x: string]: any;
105
+ } = {}> = LoroTreeNode<{
106
+ type: string;
107
+ } & T>;
108
+ export type NabuInit = {
109
+ extensions?: Extension[] | undefined;
110
+ snapshot?: Uint8Array<ArrayBufferLike> | undefined;
111
+ };
112
+ import { LoroDoc } from 'loro-crdt';
113
+ import { NabuSelection } from './selection.svelte';
114
+ import type { LoroTree } from "loro-crdt";
115
+ import { UndoManager } from 'loro-crdt';
116
+ import type { Extension } from '../utils/extensions.js';
117
+ import { SvelteMap } from 'svelte/reactivity';
118
+ import { Block } from './block.svelte';
119
+ import type { Component } from "svelte";
120
+ import { SvelteSet } from 'svelte/reactivity';
121
+ import type { LoroTreeNode } from "loro-crdt";