diff --git a/apps/playground/package.json b/apps/playground/package.json
index 8d4dcbf..736950d 100644
--- a/apps/playground/package.json
+++ b/apps/playground/package.json
@@ -10,6 +10,8 @@
"astro": "astro"
},
"dependencies": {
+ "@codemirror/state": "catalog:",
+ "@codemirror/view": "catalog:",
"astro": "^7.1.6"
},
"engines": {
diff --git a/apps/playground/src/pages/index.astro b/apps/playground/src/pages/index.astro
index 14ec1e7..29da09b 100644
--- a/apps/playground/src/pages/index.astro
+++ b/apps/playground/src/pages/index.astro
@@ -1,26 +1,57 @@
---
-import '../assets/global.css'
+import "../assets/global.css";
---
-
-
-
-
-
-
- Inkwell Playground
-
-
-
-
+
+
+
+
+
+
+ Inkwell Playground
+
+
+
+
\ No newline at end of file
+ function dummyKeymap(tag) {
+ return keymap.of([
+ {
+ key: "Enter",
+ run() {
+ console.log(this, tag);
+ return true;
+ },
+ },
+ ]);
+ }
+
+ const ChildExtension: InkwellExtension = {
+ name: "child",
+ addCodeMirrorExtensions: () => [],
+ };
+
+ const TestExtension: InkwellExtension = {
+ name: "test",
+ priority: 10,
+ addExtensions: () => [ChildExtension],
+ addCodeMirrorExtensions: ({ editor }) => {
+ return [dummyKeymap("a")];
+ },
+ };
+
+ const editor = new Editor({
+ element: document.getElementById("editor-mount")!,
+ content: "# This is the Inkwell playground\n\nFeel free to play around",
+ extensions: [TestExtension],
+ });
+
+ console.log(editor);
+
diff --git a/packages/core/src/editor/Editor.ts b/packages/core/src/editor/Editor.ts
index 96b3ae0..c9ac5ac 100644
--- a/packages/core/src/editor/Editor.ts
+++ b/packages/core/src/editor/Editor.ts
@@ -2,10 +2,12 @@ import { EditorState, Text } from "@codemirror/state";
import { EditorView } from "@codemirror/view";
import { EventEmitter } from "./EventEmitter.ts";
import type { EditorEvents, EditorOptions } from "./types.ts";
+import { ExtensionManager } from "../extensions/index.ts";
export class Editor extends EventEmitter {
private _te: TextEncoder = new TextEncoder();
private _td: TextDecoder = new TextDecoder();
+ private _extensionManager: ExtensionManager;
options: EditorOptions;
view: EditorView;
@@ -54,6 +56,7 @@ export class Editor extends EventEmitter {
super();
this.options = options;
const { element, content } = this.options;
+ this._extensionManager = new ExtensionManager(this, this.options?.extensions ?? []);
this.emit("beforeCreate", { editor: this });
@@ -64,6 +67,7 @@ export class Editor extends EventEmitter {
this.view = new EditorView({
state: initialState,
parent: element,
+ extensions: this._extensionManager.cmExtensions,
});
this.setupDOM();
diff --git a/packages/core/src/editor/index.ts b/packages/core/src/editor/index.ts
index a571e42..3443179 100644
--- a/packages/core/src/editor/index.ts
+++ b/packages/core/src/editor/index.ts
@@ -1 +1,3 @@
export * from "./Editor.ts";
+export * from "./EventEmitter.ts";
+export * from "./types.ts";
diff --git a/packages/core/src/editor/types.ts b/packages/core/src/editor/types.ts
index 5508206..8f31e5f 100644
--- a/packages/core/src/editor/types.ts
+++ b/packages/core/src/editor/types.ts
@@ -1,9 +1,11 @@
import type { EditorSelection, Transaction } from "@codemirror/state";
import type { Editor } from "./Editor.ts";
+import type { InkwellExtension } from "../extensions/types.ts";
export type EditorOptions = {
element: HTMLElement;
content?: string;
+ extensions?: InkwellExtension[];
};
export type EditorEvents = {
diff --git a/packages/core/src/extensions/ExtensionManager.ts b/packages/core/src/extensions/ExtensionManager.ts
new file mode 100644
index 0000000..e7261d7
--- /dev/null
+++ b/packages/core/src/extensions/ExtensionManager.ts
@@ -0,0 +1,75 @@
+import type { Extension as CMExtension } from "@codemirror/state";
+import type { Editor } from "../editor/Editor.ts";
+import type { InkwellExtension } from "./types.ts";
+
+export class ExtensionManager {
+ private editor: Editor;
+ private _extensions: InkwellExtension[] = [];
+ private _resolvedExtensions: InkwellExtension[] = [];
+ private _cmExtensions: CMExtension[] = [];
+
+ constructor(editor: Editor, extensions: InkwellExtension[]) {
+ this.editor = editor;
+ this._extensions = extensions;
+ this._resolvedExtensions = this.resolveExtensions();
+ this._cmExtensions = this.toCodeMirrorExtensions();
+ }
+
+ get extensions(): InkwellExtension[] {
+ return [...this._extensions];
+ }
+
+ get cmExtensions(): CMExtension[] {
+ return [...this._cmExtensions];
+ }
+
+ get resolvedExtensions(): InkwellExtension[] {
+ return [...this._resolvedExtensions];
+ }
+
+ /** The resolved extension array, including child extensions */
+ resolveExtensions(): InkwellExtension[] {
+ const resolved: InkwellExtension[] = [];
+ const visited = new Set();
+ const visitedNames = new Set();
+
+ // TODO: add name-guarding / deduping
+ const resolve = (ext: InkwellExtension) => {
+ if (visited.has(ext) || visitedNames.has(ext.name)) return;
+
+ visited.add(ext);
+ resolved.push(ext);
+ visitedNames.add(ext.name);
+
+ if (ext.addExtensions) {
+ const childExtensions = ext.addExtensions({ editor: this.editor });
+ for (const childExtension of childExtensions) {
+ resolve(childExtension);
+ }
+ }
+ };
+
+ for (const ext of this._extensions) {
+ resolve(ext);
+ }
+
+ return resolved;
+ }
+
+ public sortExtensionsByPrio(extensions: InkwellExtension[]): InkwellExtension[] {
+ return [...extensions].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
+ }
+
+ public toCodeMirrorExtensions(): CMExtension[] {
+ const cmExtensions: CMExtension[] = [];
+
+ for (const ext of this.sortExtensionsByPrio(this.resolvedExtensions)) {
+ if (ext.addCodeMirrorExtensions) {
+ const cmExts = ext.addCodeMirrorExtensions({ editor: this.editor });
+ cmExtensions.push(...cmExts);
+ }
+ }
+
+ return cmExtensions;
+ }
+}
diff --git a/packages/core/src/extensions/index.ts b/packages/core/src/extensions/index.ts
new file mode 100644
index 0000000..7e45f11
--- /dev/null
+++ b/packages/core/src/extensions/index.ts
@@ -0,0 +1,2 @@
+export * from "./types.ts";
+export * from "./ExtensionManager.ts";
diff --git a/packages/core/src/extensions/types.ts b/packages/core/src/extensions/types.ts
new file mode 100644
index 0000000..46ef2c5
--- /dev/null
+++ b/packages/core/src/extensions/types.ts
@@ -0,0 +1,39 @@
+import type { Editor } from "../editor/index.ts";
+import type { Extension as CMExtension } from "@codemirror/state";
+import type { EditorEvents } from "../editor/types.ts";
+
+export interface EditorContext {
+ editor: Editor;
+}
+
+// TODO: define the types for the extensions
+export type NodeConfig = {
+ name: string;
+};
+
+// TODO: define the types for the extensions
+export type MarkConfig = {
+ name: string;
+};
+
+export type ExtensionEventHandlers = {
+ [K in keyof EditorEvents]?: (ctx: EditorContext, payload: EditorEvents[K]) => void;
+};
+
+export type InkwellExtension = ExtensionEventHandlers & {
+ name: string;
+
+ /**
+ * The extension priority. Extensions with higher priority will be initialized first. Defaults to 0.
+ * @example 10
+ */
+ priority?: number;
+
+ addNodes?: (ctx: EditorContext) => NodeConfig[];
+ addMarks?: (ctx: EditorContext) => MarkConfig[];
+ addCodeMirrorExtensions?: (ctx: EditorContext) => CMExtension[];
+ addExtensions?: (ctx: EditorContext) => InkwellExtension[];
+
+ // TODO: define addKeybinds correctly
+ addKeybinds?: (ctx: EditorContext) => Record void>;
+};
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 95ae7c4..678f804 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -36,6 +36,12 @@ importers:
apps/playground:
dependencies:
+ '@codemirror/state':
+ specifier: 'catalog:'
+ version: 6.7.1
+ '@codemirror/view':
+ specifier: 'catalog:'
+ version: 6.43.7
astro:
specifier: ^7.1.6
version: 7.1.6(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3)(@types/node@26.1.2)(jiti@2.7.0)(typescript@7.0.2)(yaml@2.9.0)