Skip to content
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
12 changes: 7 additions & 5 deletions packages/super-editor/src/components/ProseMirror.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const props = defineProps({
const editorElem = ref(null);

const onCommentsLoaded = ({ comments }) => {
console.log({ comments });
// console.log({ comments });
// Let super doc know we have comments
emit('comments-loaded', comments);
};
Expand All @@ -49,11 +49,13 @@ const initEditor = () => {
onCommentsLoaded,
});

editor.on('transaction', ({ transaction }) => {
console.log({ transaction });
editor.on('create', ({ editor }) => {
emit('editor-ready', props.documentId, editor);
});

editor.on('update', ({ editor, transaction }) => {
console.log({ editor, transaction });
});

emit('editor-ready', props.documentId, editor);
};

onMounted(() => {
Expand Down
1 change: 1 addition & 0 deletions packages/super-editor/src/core/CommandService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export class CommandService {};
195 changes: 171 additions & 24 deletions packages/super-editor/src/core/Editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,41 +10,98 @@ import {
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 { buildKeymap } from './shortcuts/buildKeymap.js';
import { DocxSchema } from './schema/DocxSchema.js';
import { SuperConverter } from './SuperConverter.js';
import { EventEmitter } from './EventEmitter.js';
import { initComments as initCommentsExt } from '@extensions/comments/comments';
import { initComments } from '@extensions/Comments/comments.js';
import { createDocument } from './helpers/createDocument.js';
import { createStyleTag } from './utilities/createStyleTag.js';
import { style } from './style.js';

export class Editor extends EventEmitter {
#commandService;

extensionService;

extensionStorage = {};

schema;

view;

#css;

options = {
element: document.createElement('div'),
content: '',
editorProps: {},
documentId: null,
injectCSS: true,
extensions: [],
editable: true,
editorProps: {},
parseOptions: {},
coreExtensionOptions: {},
enableInputRules: true,
enablePasteRules: true,
enableCoreExtensions: true,
onBeforeCreate: () => null,
onCreate: () => null,
onUpdate: () => null,
onSelectionUpdate: () => null,
onTransaction: () => null,
onFocus: () => null,
onBlur: () => null,
onDestroy: () => null,
onContentError: ({ error }) => { throw error },
onCommentsLoaded: () => null,
}

constructor(options) {
super();

this.setOptions(options);
this.#createExtensionService();
this.#createCommandService();
this.#createSchema();
this.#createConverter();

this.on('beforeCreate', this.options.onBeforeCreate);
this.emit('beforeCreate', { editor: this });
this.on('contentError', this.options.onContentError);

this.#createView();
this.#injectCSS()

this.on('create', this.options.onCreate);
this.on('update', this.options.onUpdate);
this.on('selectionUpdate', this.options.onSelectionUpdate);
this.on('transaction', this.options.onTransaction);
this.on('focus', this.options.onFocus);
this.on('blur', this.options.onBlur);
this.on('destroy', this.options.onDestroy);
this.on('comments-loaded', this.options.onCommentsLoaded);
this.initComments();

this.#loadComments();

window.setTimeout(() => {
if (this.isDestroyed) return;
this.emit('create', { editor: this });
}, 0);
}

get state() {
return this.view.state;
}

get store() {
return this.extensionStorage;
}

get isEditable() {
return this.options.editable && this.view && this.view.editable;
}

get isDestroyed() {
return !this.view?.docView;
}
Expand All @@ -53,7 +110,7 @@ export class Editor extends EventEmitter {
this.options = {
...this.options,
...options,
}
};

if (!this.view || !this.state || this.isDestroyed) {
return;
Expand All @@ -66,65 +123,149 @@ export class Editor extends EventEmitter {
this.view.updateState(this.state);
}

setEditable(editable, emitUpdate = true) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice

this.setOptions({ editable });

if (emitUpdate) {
this.emit('update', { editor: this, transaction: this.state.tr });
}
}

registerPlugin(plugin, handlePlugins) {
const plugins = typeof handlePlugins === 'function'
? handlePlugins(plugin, [...this.state.plugins])
: [...this.state.plugins, plugin];

const state = this.state.reconfigure({ plugins });

this.view.updateState(state);
}


unregisterPlugin(nameOrPluginKey) {
if (this.isDestroyed) {
return;
}

const name = typeof nameOrPluginKey === 'string'
? `${nameOrPluginKey}$`
: nameOrPluginKey.key;

const state = this.state.reconfigure({
plugins: this.state.plugins.filter((plugin) => !plugin.key.startsWith(name)),
});

this.view.updateState(state);
}

#injectCSS() {
if (this.options.injectCSS && document) {
this.#css = createStyleTag(style);
}
}

#createExtensionService() {}

#createCommandService() {}

#createConverter() {
this.converter = new SuperConverter({
docx: this.options.content,
debug: true,
});
}
// TODO

// Build schema from extensions?
#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();
}
doc = createDocument(
this.converter,
this.schema,
this.options.parseOptions,
);
} catch (err) {
console.error(err);

this.emit('contentError', {
editor: this,
error: err,
});

// Here we can try to create document again.
}

this.view = new EditorView(this.options.element, {
...this.options.editorProps,
dispatchTransaction: this.#dispatchTransaction.bind(this),
state: EditorState.create({
doc,
plugins: [ // TODO
history(),
buildKeymap(),
],
}),
});

const newState = this.state.reconfigure({
plugins: [ // Get plugins from extension service?
history(),
buildKeymap(),
],
});
this.view.updateState(newState);

// Create Node Views?

const dom = this.view.dom;
dom.editor = this;
}

#dispatchTransaction(transaction) {
if (this.view.isDestroyed) {
return;
}

const state = this.state.apply(transaction);
const selectionHasChanged = !this.state.selection.eq(state.selection);

this.view.updateState(state);
this.emit('transaction', {
editor: this,
transaction,
});

if (selectionHasChanged) {
this.emit('selectionUpdate', {
editor: this,
transaction
});
}

if (!transaction.docChanged) {
return;
}

this.emit('update', {
editor: this,
transaction,
});
}

initComments() {
const comments = initCommentsExt(
#loadComments() {
const comments = initComments(
this.view,
this.converter,
this.options.documentId,
);
this.emit('comments-loaded', { comments });
}


getJSON() {
return this.state.doc.toJSON();
}

save() {
console.debug('EDITOR CLASS SAVE - TODO', this.state.doc.toJSON());
// const converter = new SuperConverter();
Expand All @@ -133,4 +274,10 @@ export class Editor extends EventEmitter {
const xml = this.converter.schemaToXml({ doc: this.state.doc.toJSON() });
console.debug('XML', xml);
}

destroy() {
this.emit('destroy');
if (this.view) this.view.destroy();
this.removeAllListeners();
}
}
1 change: 1 addition & 0 deletions packages/super-editor/src/core/ExtensionService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export class ExtensionService {};
Empty file.
Empty file.
Empty file.
14 changes: 14 additions & 0 deletions packages/super-editor/src/core/helpers/createDocument.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

export function createDocument(
converter,
schema,
parseOptions,
) {
const documentData = converter.getSchema();
console.debug('\nSCHEMA', JSON.stringify(documentData, null, 2), '\n');

if (documentData) {
return schema.nodeFromJSON(documentData);
}
return schema.topNodeType.createAndFill();
}
Empty file.
Empty file.
Loading