diff --git a/packages/super-editor/src/components/ProseMirror.vue b/packages/super-editor/src/components/ProseMirror.vue
index eaa1a6c7e1..239472407b 100644
--- a/packages/super-editor/src/components/ProseMirror.vue
+++ b/packages/super-editor/src/components/ProseMirror.vue
@@ -1,13 +1,6 @@
diff --git a/packages/super-editor/src/core/Editor.js b/packages/super-editor/src/core/Editor.js
new file mode 100644
index 0000000000..ed5a4031b7
--- /dev/null
+++ b/packages/super-editor/src/core/Editor.js
@@ -0,0 +1,127 @@
+import {
+ MarkType,
+ Node as ProseMirrorNode,
+ NodeType,
+ Schema,
+} from 'prosemirror-model';
+import {
+ EditorState, Plugin, PluginKey, Transaction,
+} from 'prosemirror-state';
+import { EditorView } from 'prosemirror-view';
+import { history } from "prosemirror-history";
+
+import { buildKeymap } from '@core/shortcuts/buildKeymap';
+import { DocxSchema } from '@core/schema/DocxSchema';
+import { SuperConverter } from '@core/SuperConverter';
+import { EventEmitter } from './EventEmitter.js';
+import { initComments as initCommentsExt } from '@extensions/Comments/comments';
+
+export class Editor extends EventEmitter {
+ schema;
+
+ view;
+
+ options = {
+ element: document.createElement('div'),
+ content: '',
+ editorProps: {},
+ documentId: null,
+ onCommentsLoaded: () => null,
+ }
+
+ constructor(options) {
+ super();
+
+ this.setOptions(options);
+ this.#createSchema();
+ this.#createConverter();
+ this.#createView();
+
+ this.on('comments-loaded', this.options.onCommentsLoaded);
+ this.initComments();
+ }
+
+ get state() {
+ return this.view.state;
+ }
+
+ get isDestroyed() {
+ return !this.view?.docView;
+ }
+
+ setOptions(options) {
+ this.options = {
+ ...this.options,
+ ...options,
+ }
+
+ if (!this.view || !this.state || this.isDestroyed) {
+ return;
+ }
+
+ if (this.options.editorProps) {
+ this.view.setProps(this.options.editorProps);
+ }
+
+ this.view.updateState(this.state);
+ }
+
+ #createConverter() {
+ this.converter = new SuperConverter({
+ docx: this.options.content,
+ debug: true,
+ });
+ }
+
+ // TODO
+ #createSchema() {
+ this.schema = DocxSchema;
+ }
+
+ #createView() {
+ let doc;
+
+ try {
+ const docData = this.converter.getSchema();
+ // console.debug('\nSCHEMA', JSON.stringify(docData, null, 2), '\n');
+ if (docData) {
+ doc = this.schema.nodeFromJSON(docData);
+ } else {
+ doc = this.schema.topNodeType.createAndFill();
+ }
+ } catch (err) {
+ console.error(err);
+ }
+
+ this.view = new EditorView(this.options.element, {
+ ...this.options.editorProps,
+ dispatchTransaction: this.#dispatchTransaction.bind(this),
+ state: EditorState.create({
+ doc,
+ plugins: [ // TODO
+ history(),
+ buildKeymap(),
+ ],
+ }),
+ });
+ }
+
+ #dispatchTransaction(transaction) {
+ const state = this.state.apply(transaction);
+
+ this.view.updateState(state);
+ this.emit('transaction', {
+ editor: this,
+ transaction,
+ });
+ }
+
+ initComments() {
+ const comments = initCommentsExt(
+ this.view,
+ this.converter,
+ this.options.documentId,
+ );
+ this.emit('comments-loaded', { comments });
+ }
+}
diff --git a/packages/super-editor/src/core/EventEmitter.js b/packages/super-editor/src/core/EventEmitter.js
new file mode 100644
index 0000000000..dfdd278cd7
--- /dev/null
+++ b/packages/super-editor/src/core/EventEmitter.js
@@ -0,0 +1,32 @@
+export class EventEmitter {
+ #callbacks = new Map();
+
+ on(event, fn) {
+ const callbacks = this.#callbacks.get(event);
+ if (callbacks) callbacks.push(fn);
+ else this.#callbacks.set(event, [fn]);
+ }
+
+ emit(event, ...args) {
+ const callbacks = this.#callbacks.get(event);
+ if (!callbacks) return;
+ for (const fn of callbacks) {
+ fn(...args);
+ // fn.apply(this, args);
+ }
+ }
+
+ off(event, fn) {
+ const callbacks = this.#callbacks.get(event);
+ if (!callbacks) return;
+ if (fn) {
+ this.#callbacks.set(event, callbacks.filter((cb) => cb !== fn));
+ } else {
+ this.#callbacks.delete(event);
+ }
+ }
+
+ removeAllListeners() {
+ this.#callbacks = new Map();
+ }
+}
diff --git a/packages/super-editor/src/core/SuperConverter.js b/packages/super-editor/src/core/SuperConverter.js
index cc2b162e03..c521995eb0 100644
--- a/packages/super-editor/src/core/SuperConverter.js
+++ b/packages/super-editor/src/core/SuperConverter.js
@@ -7,7 +7,7 @@ import xmljs from 'xml-js';
* Will need to be updated as we find new docx tags.
*
*/
-class SuperConverter {
+export class SuperConverter {
static allowedElements = Object.freeze({
'w:document': 'doc',
@@ -197,9 +197,9 @@ class SuperConverter {
const currentLevel = currentLevelDef.elements.find(style => style.name === 'w:numFmt')
const listTypeDef = currentLevel.attributes['w:val'];
let listType;
- if (listTypeDef === 'bullet') listType = 'unorderedList';
+ if (listTypeDef === 'bullet') listType = 'bulletList';
else if (listTypeDef === 'decimal') listType = 'orderedList';
- else if (listTypeDef === 'lowerLetter') listType = 'unorderedList';
+ else if (listTypeDef === 'lowerLetter') listType = 'bulletList';
// Check for unknown list types, there will be some
if (!listType) console.debug('_getNodeListType: No list type:', listTypeDef, attributes)
@@ -425,5 +425,3 @@ class SuperConverter {
return tags;
}
}
-
-export default SuperConverter;
diff --git a/packages/super-editor/src/core/schema/DocxSchema.js b/packages/super-editor/src/core/schema/DocxSchema.js
index 885740f8b5..208f59500a 100644
--- a/packages/super-editor/src/core/schema/DocxSchema.js
+++ b/packages/super-editor/src/core/schema/DocxSchema.js
@@ -1,5 +1,7 @@
import { Schema } from "prosemirror-model"
+const olDOM = ["ol", 0], ulDOM = ["ul", 0], liDOM = ["li", 0];
+
/**
* Custom schema for docx files with prose mirror
* Reference: https://github.com/ProseMirror/prosemirror-schema-basic/blob/master/src/schema-basic.ts
@@ -7,32 +9,30 @@ import { Schema } from "prosemirror-model"
const DocxSchema = new Schema({
nodes: {
-
- /**
- * ❗️ TODO: Implement a custom node view for run nodes that are children of list types
- */
- unorderedList: {
- content: "text*",
- inline: false,
- group: "block",
- toDOM() { return ["ul", 0]; },
+ bulletList: {
+ content: "listItem+",
+ group: "block list",
parseDOM: [{ tag: "ul" }],
- attrs: {
- attributes: { default: {} },
- type: { default: null },
- },
+ toDOM() { return ulDOM; },
},
orderedList: {
- content: "text*",
- inline: false,
- group: "block",
- toDOM() { return ["ol", 0]; },
- parseDOM: [{ tag: "ol" }],
- attrs: {
- attributes: { default: {} },
- type: { default: null },
+ content: "listItem+",
+ group: "block list",
+ parseDOM: [{tag: "ol", getAttrs(dom) {
+ return {order: dom.hasAttribute("start") ? +dom.getAttribute("start") : 1};
+ }}],
+ toDOM(node) {
+ return node.attrs.order === 1 ? olDOM : ["ol", {start: node.attrs.order}, 0];
},
+ attrs: {order: {default: 1}},
+ },
+
+ listItem: {
+ content: "paragraph block*",
+ parseDOM: [{tag: "li"}],
+ toDOM() { return liDOM },
+ defining: true,
},
commentRangeStart: {
@@ -102,7 +102,7 @@ const DocxSchema = new Schema({
},
body: {
- content: "(paragraph+ | unorderedList*)",
+ content: "(paragraph+ | bulletList* | orderedList*)",
toDOM() { return ["body", 0]; },
attrs: {
attributes: { default: {} },
diff --git a/packages/super-editor/src/core/tests/super-converter/input-tests.js b/packages/super-editor/src/core/tests/super-converter/input-tests.js
index 9ee50aa15f..4ed39599b5 100644
--- a/packages/super-editor/src/core/tests/super-converter/input-tests.js
+++ b/packages/super-editor/src/core/tests/super-converter/input-tests.js
@@ -1,6 +1,6 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { readFileSync } from './helpers';
-import SuperConverter from '../../SuperConverter';
+import { SuperConverter } from '../../SuperConverter';
const showParserLogging = false;
diff --git a/packages/super-editor/src/core/tests/super-converter/output-tests.js b/packages/super-editor/src/core/tests/super-converter/output-tests.js
index 00f7f6c963..2014a071ac 100644
--- a/packages/super-editor/src/core/tests/super-converter/output-tests.js
+++ b/packages/super-editor/src/core/tests/super-converter/output-tests.js
@@ -1,6 +1,6 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { readFileSync } from './helpers';
-import SuperConverter from '../../SuperConverter';
+import { SuperConverter } from '../../SuperConverter';
import source1 from './output-tests-source';
const showParserLogging = false;
diff --git a/packages/super-editor/src/index.js b/packages/super-editor/src/index.js
index 948a6c62f3..fc133e10c2 100644
--- a/packages/super-editor/src/index.js
+++ b/packages/super-editor/src/index.js
@@ -1,4 +1,4 @@
-import SuperConverter from "@core/SuperConverter";
+import { SuperConverter } from "@core/SuperConverter";
import DocxZipper from '@core/DocxZipper';
import SuperEditor from '@components/SuperEditor.vue';
import BasicUpload from './dev/components/BasicUpload.vue';