From 1ded59045e2fd05d30e8ff4b842852189f80292a Mon Sep 17 00:00:00 2001
From: Giannis Kopanas <jkopanas@gmail.com>
Date: Sat, 14 Dec 2019 19:49:26 +0200
Subject: [PATCH] feat(schema): add Schema services

---
 editors/editoria/src/Editoria.js              |  3 +-
 wax-prosemirror-core/src/Application.js       |  6 ++
 .../src/Config/defaultConfig.js               |  4 +-
 wax-prosemirror-core/src/Wax.js               |  4 +-
 wax-prosemirror-plugins/index.js              | 21 ++---
 .../src/MenuService/Menu.js                   |  8 --
 .../PlaceholderService/PlaceholderService.js  | 86 +++++++++++++++++++
 .../src/SchemaService/Mark.js                 | 66 ++++++++++++++
 .../src/SchemaService/Node.js                 | 66 ++++++++++++++
 .../src/SchemaService/ParseRule.js            | 36 ++++++++
 .../src/SchemaService/Schema.js               | 57 ++++++++++++
 .../src/SchemaService/SchemaService.js        | 54 ++++++++++++
 wax-prosemirror-plugins/src/lib/Middleware.js | 33 +++++++
 wax-prosemirror-schema/src/editoria/nodes.js  |  3 -
 14 files changed, 417 insertions(+), 30 deletions(-)
 create mode 100644 wax-prosemirror-plugins/src/SchemaService/Mark.js
 create mode 100644 wax-prosemirror-plugins/src/SchemaService/Node.js
 create mode 100644 wax-prosemirror-plugins/src/SchemaService/ParseRule.js
 create mode 100644 wax-prosemirror-plugins/src/SchemaService/Schema.js
 create mode 100644 wax-prosemirror-plugins/src/SchemaService/SchemaService.js
 create mode 100644 wax-prosemirror-plugins/src/lib/Middleware.js

diff --git a/editors/editoria/src/Editoria.js b/editors/editoria/src/Editoria.js
index e68619682..daf0ea858 100644
--- a/editors/editoria/src/Editoria.js
+++ b/editors/editoria/src/Editoria.js
@@ -3,7 +3,7 @@ import styled, { createGlobalStyle } from "styled-components";
 import { EditoriaLayout } from "wax-prosemirror-layouts";
 import { Wax } from "wax-prosemirror-core";
 
-import { schema, keys, plugins, rules, services } from "./EditorConfig";
+import { schema, keys, plugins, services } from "./EditorConfig";
 import { config } from "./config";
 
 import text from "./text";
@@ -12,7 +12,6 @@ const options = {
   schema,
   plugins,
   keys,
-  rules,
   services
 };
 
diff --git a/wax-prosemirror-core/src/Application.js b/wax-prosemirror-core/src/Application.js
index 35642cf10..5c0d47a98 100644
--- a/wax-prosemirror-core/src/Application.js
+++ b/wax-prosemirror-core/src/Application.js
@@ -1,4 +1,5 @@
 import { Container } from "inversify";
+import { Schema } from "prosemirror-model";
 import "reflect-metadata";
 import Config from "./Config/Config";
 import defaultConfig from "./Config/defaultConfig";
@@ -47,6 +48,11 @@ export default class Application {
     return this.PmPlugins.getAll();
   }
 
+  getSchema() {
+    const schema = this.container.get("Schema");
+    return new Schema(schema.getSchema());
+  }
+
   static create(config) {
     /*
     Create Container
diff --git a/wax-prosemirror-core/src/Config/defaultConfig.js b/wax-prosemirror-core/src/Config/defaultConfig.js
index 7cb00a632..fd9d9d0ec 100644
--- a/wax-prosemirror-core/src/Config/defaultConfig.js
+++ b/wax-prosemirror-core/src/Config/defaultConfig.js
@@ -6,11 +6,13 @@ import {
   TextStyleService,
   PlaceholderService,
   ImageService,
-  RulesService
+  RulesService,
+  SchemaService
 } from "wax-prosemirror-plugins";
 
 export default {
   services: [
+    new SchemaService(),
     new LayoutService(),
     new MenuService(),
     new RedoUndoService(),
diff --git a/wax-prosemirror-core/src/Wax.js b/wax-prosemirror-core/src/Wax.js
index 0665cda4f..643621358 100644
--- a/wax-prosemirror-core/src/Wax.js
+++ b/wax-prosemirror-core/src/Wax.js
@@ -49,7 +49,7 @@ class Wax extends Component {
 
   componentWillMount() {
     const { value, onChange, options } = this.props;
-    const { schema, plugins, keys, rules } = options;
+    const { plugins, keys, rules } = options;
     const WaxOnchange = onChange ? onChange : value => true;
 
     // const WaxShortCuts = keys
@@ -64,6 +64,8 @@ class Wax extends Component {
       ...this.application.getPlugins()
     ]);
 
+    const schema = this.application.getSchema();
+
     this.WaxOptions = {
       schema,
       plugins: finalPlugins
diff --git a/wax-prosemirror-plugins/index.js b/wax-prosemirror-plugins/index.js
index 9e3aedf8b..21d36a20f 100644
--- a/wax-prosemirror-plugins/index.js
+++ b/wax-prosemirror-plugins/index.js
@@ -1,23 +1,14 @@
-export {
-  default as TrackChangePlugin
-} from "./src/trackChanges/TrackChangePlugin";
+export { default as TrackChangePlugin } from "./src/trackChanges/TrackChangePlugin";
 export { default as FindAndReplacePlugin } from "./src/FindAndReplacePlugin";
 export { default as OverlayPlugin } from "./src/overlay/OverlayPlugin";
 export { default as LinkService } from "./src/LinkService/LinkService";
 export { default as MenuService } from "./src/MenuService/MenuService";
-export {
-  default as RedoUndoService
-} from "./src/RedoUndoService/RedoUndoService";
-export {
-  default as AnnotationService
-} from "./src/AnnotationService/AnnotationService";
-export {
-  default as TextStyleService
-} from "./src/TextStyleService/TextStyleService";
-export {
-  default as PlaceholderService
-} from "./src/PlaceholderService/PlaceholderService";
+export { default as RedoUndoService } from "./src/RedoUndoService/RedoUndoService";
+export { default as AnnotationService } from "./src/AnnotationService/AnnotationService";
+export { default as TextStyleService } from "./src/TextStyleService/TextStyleService";
+export { default as PlaceholderService } from "./src/PlaceholderService/PlaceholderService";
 export { default as ImageService } from "./src/ImageService/ImageService";
 export { default as RulesService } from "./src/RulesService/RulesService";
+export { default as SchemaService } from "./src/SchemaService/SchemaService";
 
 export { default as Tool } from "./src/lib/Tools";
diff --git a/wax-prosemirror-plugins/src/MenuService/Menu.js b/wax-prosemirror-plugins/src/MenuService/Menu.js
index 1b3849b6e..68056165e 100644
--- a/wax-prosemirror-plugins/src/MenuService/Menu.js
+++ b/wax-prosemirror-plugins/src/MenuService/Menu.js
@@ -27,11 +27,3 @@ export default class Menu {
     return view => <MenuWrapper items={this.toolGroups} view={view} />;
   }
 }
-
-// {
-//   templateArea: "topBar",
-//   tools: [
-//     "redo-undo",
-//     { name: "Annotations", exclude: [], include: [] }
-//   ]
-// }
diff --git a/wax-prosemirror-plugins/src/PlaceholderService/PlaceholderService.js b/wax-prosemirror-plugins/src/PlaceholderService/PlaceholderService.js
index a3c08b71f..72f3b9ec8 100644
--- a/wax-prosemirror-plugins/src/PlaceholderService/PlaceholderService.js
+++ b/wax-prosemirror-plugins/src/PlaceholderService/PlaceholderService.js
@@ -2,10 +2,96 @@ import Service from "wax-prosemirror-core/src/services/Service";
 import placeholderPlugin from "./pmPlugins/placeholderPlugin";
 const PLUGIN_KEY = "imagePlaceHolder";
 
+const blockLevelToDOM = node => {
+  const attrs = node.attrs.track.length
+    ? {
+        class: node.attrs.class,
+        "data-track": JSON.stringify(node.attrs.track)
+      }
+    : { class: node.attrs.class };
+  return attrs;
+};
+
+const parseTracks = str => {
+  if (!str) {
+    return [];
+  }
+  let tracks;
+  try {
+    tracks = JSON.parse(str);
+  } catch (error) {
+    return [];
+  }
+  if (!Array.isArray(tracks)) {
+    return [];
+  }
+  return tracks.filter(
+    (
+      track // ensure required fields are present
+    ) =>
+      track.hasOwnProperty("user") &&
+      track.hasOwnProperty("username") &&
+      track.hasOwnProperty("date")
+  );
+};
+
 export default class PlaceholderService extends Service {
   name = "PlaceholderService";
 
   register() {
     this.app.PmPlugins.add(PLUGIN_KEY, placeholderPlugin(PLUGIN_KEY));
+
+    const createNode = this.container.get("CreateNode");
+    createNode({
+      paragraph: {
+        group: "block",
+        content: "inline*",
+        attrs: {
+          class: { default: "paragraph" },
+          track: { default: [] }
+        },
+        parseDOM: {
+          tag: "p.paragraph",
+          getAttrs(hook, next) {
+            Object.assign(hook, {
+              class: hook.dom.getAttribute("class")
+            });
+            next();
+          }
+        },
+        toDOM(hook, next) {
+          const attrs = { class: hook.node.attrs.class };
+
+          hook.value = ["p", attrs, 0];
+          next();
+        }
+      }
+    });
+
+    createNode({
+      paragraph: {
+        group: "block",
+        content: "inline*",
+        attrs: {
+          track: { default: [] }
+        },
+        parseDOM: {
+          tag: "p.paragraph",
+          getAttrs(hook, next) {
+            Object.assign(hook, {
+              track: parseTracks(hook.dom.dataset.track)
+            });
+            next();
+          }
+        },
+        toDOM(hook, next) {
+          const attrs = blockLevelToDOM(hook.node);
+          Object.assign(hook.value[1], {
+            "data-track": JSON.stringify(hook.node.attrs.track)
+          });
+          next();
+        }
+      }
+    });
   }
 }
diff --git a/wax-prosemirror-plugins/src/SchemaService/Mark.js b/wax-prosemirror-plugins/src/SchemaService/Mark.js
new file mode 100644
index 000000000..bee9a57e6
--- /dev/null
+++ b/wax-prosemirror-plugins/src/SchemaService/Mark.js
@@ -0,0 +1,66 @@
+import ParseRule from "./ParseRule";
+import Middleware from "../lib/Middleware";
+
+export default class Mark {
+  name = "";
+  importer = {};
+
+  inline = false;
+  group = "";
+  content = "";
+  draggable = false;
+  _attrs = {};
+  _parseRules = [];
+
+  constructor(name) {
+    this.name = name;
+    this.importer = new Middleware();
+  }
+
+  fromJSON(config) {
+    for (let key in config) {
+      let value = config[key];
+      this[key] = value;
+    }
+  }
+
+  set toDOM(value) {
+    this.importer.use(value);
+  }
+
+  set attrs(value) {
+    Object.assign(this._attrs, value);
+  }
+
+  set parseDOM(value) {
+    let parseRule = this._parseRules.find(
+      parseRule => parseRule.tag === value.tag
+    );
+    if (!parseRule) {
+      parseRule = new ParseRule(value);
+      this._parseRules.push(parseRule);
+    }
+    parseRule.addStack(value.getAttrs);
+  }
+
+  toJSON() {
+    const importer = this.importer;
+
+    return {
+      inline: this.inline,
+      group: this.group,
+      content: this.content,
+      draggable: this.draggable,
+      attrs: this._attrs,
+      parseDOM: this._parseRules.map(rule => rule.combineRules()),
+      toDOM: node => {
+        let hooks = {};
+
+        importer.go({ node }, hook => {
+          hooks = hook;
+        });
+        return hooks.value;
+      }
+    };
+  }
+}
diff --git a/wax-prosemirror-plugins/src/SchemaService/Node.js b/wax-prosemirror-plugins/src/SchemaService/Node.js
new file mode 100644
index 000000000..3b7e738ec
--- /dev/null
+++ b/wax-prosemirror-plugins/src/SchemaService/Node.js
@@ -0,0 +1,66 @@
+import ParseRule from "./ParseRule";
+import Middleware from "../lib/Middleware";
+
+export default class Node {
+  name = "";
+  importer = {};
+
+  inline = false;
+  group = "";
+  content = "";
+  draggable = false;
+  _attrs = {};
+  _parseRules = [];
+
+  constructor(name) {
+    this.name = name;
+    this.importer = new Middleware();
+  }
+
+  fromJSON(config) {
+    for (let key in config) {
+      let value = config[key];
+      this[key] = value;
+    }
+  }
+
+  set toDOM(value) {
+    this.importer.use(value);
+  }
+
+  set attrs(value) {
+    Object.assign(this._attrs, value);
+  }
+
+  set parseDOM(value) {
+    let parseRule = this._parseRules.find(
+      parseRule => parseRule.tag === value.tag
+    );
+    if (!parseRule) {
+      parseRule = new ParseRule(value);
+      this._parseRules.push(parseRule);
+    }
+    parseRule.addStack(value.getAttrs);
+  }
+
+  toJSON() {
+    const importer = this.importer;
+
+    return {
+      inline: this.inline,
+      group: this.group,
+      content: this.content,
+      draggable: this.draggable,
+      attrs: this._attrs,
+      parseDOM: this._parseRules.map(rule => rule.combineRules()),
+      toDOM: node => {
+        let hooks = {};
+
+        importer.go({ node }, hook => {
+          hooks = hook;
+        });
+        return hooks.value;
+      }
+    };
+  }
+}
diff --git a/wax-prosemirror-plugins/src/SchemaService/ParseRule.js b/wax-prosemirror-plugins/src/SchemaService/ParseRule.js
new file mode 100644
index 000000000..3245551c6
--- /dev/null
+++ b/wax-prosemirror-plugins/src/SchemaService/ParseRule.js
@@ -0,0 +1,36 @@
+import { omit } from "lodash";
+import Middleware from "../lib/Middleware";
+
+export default class ParseRule {
+  tag = "";
+  exporter = {};
+
+  constructor({ getAttrs, tag }) {
+    this.tag = tag;
+    this.exporter = new Middleware();
+    this.addStack(getAttrs);
+  }
+
+  addStack(getAttrs) {
+    this.exporter.use(getAttrs);
+  }
+
+  parseSchema(exporter) {
+    return {
+      tag: this.tag,
+      getAttrs(dom) {
+        let hooks = {};
+
+        exporter.go({ dom }, hook => {
+          hooks = hook;
+        });
+        return omit(hooks, ["dom"]);
+      }
+    };
+  }
+
+  combineRules() {
+    const exporter = this.exporter;
+    return this.parseSchema(exporter);
+  }
+}
diff --git a/wax-prosemirror-plugins/src/SchemaService/Schema.js b/wax-prosemirror-plugins/src/SchemaService/Schema.js
new file mode 100644
index 000000000..3f024dc76
--- /dev/null
+++ b/wax-prosemirror-plugins/src/SchemaService/Schema.js
@@ -0,0 +1,57 @@
+import Node from "./Node";
+import Mark from "./Mark";
+import { EditoriaSchema } from "wax-prosemirror-schema";
+import { injectable } from "inversify";
+
+@injectable()
+export default class Schema {
+  _nodes = {};
+  _marks = {};
+
+  has(instance) {
+    if (instance instanceof Node) {
+      return this._nodes[instance.name] ? this._nodes[instance.name] : false;
+    }
+    if (instance instanceof Mark) {
+      return this._marks[instance.name] ? this._marks[instance.name] : false;
+    }
+  }
+
+  addSchema(instance) {
+    if (instance instanceof Node) {
+      return this._nodes[instance.name]
+        ? this._nodes[instance.name]
+        : Object.assign(this._nodes, {
+            [instance.name]: instance
+          });
+    }
+
+    if (instance instanceof Mark) {
+      return this._marks[instance.name]
+        ? this._marks[instance.name]
+        : Object.assign(this._marks, {
+            [instance.name]: instance
+          });
+    }
+  }
+
+  getSchema() {
+    /* this is temporally until all of the packages moved to schemas */
+
+    const nodes = EditoriaSchema.nodes;
+    const marks = EditoriaSchema.marks;
+
+    for (let index in this._nodes) {
+      nodes[index] = this._nodes[index].toJSON();
+    }
+
+    for (let index in this._marks) {
+      marks[index] = this._marks[index].toJSON();
+    }
+
+    return {
+      nodes,
+      marks
+    };
+  }
+}
diff --git a/wax-prosemirror-plugins/src/SchemaService/SchemaService.js b/wax-prosemirror-plugins/src/SchemaService/SchemaService.js
new file mode 100644
index 000000000..1e4bac5f8
--- /dev/null
+++ b/wax-prosemirror-plugins/src/SchemaService/SchemaService.js
@@ -0,0 +1,54 @@
+import Service from "wax-prosemirror-core/src/services/Service";
+import Schema from "./Schema";
+import Node from "./Node";
+
+export default class SchemaService extends Service {
+  name = "SchemaService";
+
+  boot() {}
+
+  register() {
+    this.container
+      .bind("Schema")
+      .to(Schema)
+      .inSingletonScope();
+
+    this.container.bind("CreateNode").toFactory(context => {
+      return schemaConfig => {
+        const schema = context.container.get("Schema");
+        const name = Object.keys(schemaConfig)[0];
+        const config = schemaConfig[name];
+
+        const node = new Node(name);
+        let nd = {};
+        if ((nd = schema.has(node))) {
+          nd.fromJSON(config);
+          return nd;
+        } else {
+          node.fromJSON(config);
+          schema.addSchema(node);
+          return node;
+        }
+      };
+    });
+
+    this.container.bind("CreateMark").toFactory(context => {
+      return schemaConfig => {
+        const schema = context.container.get("Schema");
+        const name = Object.keys(schemaConfig)[0];
+        const config = schemaConfig[name];
+
+        const mark = new Mark(name);
+        let mr = {};
+        if ((mr = schema.has(mark))) {
+          mr.fromJSON(config);
+          return mr;
+        } else {
+          mark.fromJSON(config);
+          schema.addSchema(mark);
+          return mark;
+        }
+      };
+    });
+  }
+}
diff --git a/wax-prosemirror-plugins/src/lib/Middleware.js b/wax-prosemirror-plugins/src/lib/Middleware.js
new file mode 100644
index 000000000..25e883112
--- /dev/null
+++ b/wax-prosemirror-plugins/src/lib/Middleware.js
@@ -0,0 +1,33 @@
+export default class Middleware {
+  constructor() {
+    // Array prototype last
+    if (!Array.prototype.last) {
+      Array.prototype.last = function() {
+        return this[this.length - 1];
+      };
+    }
+
+    // Array prototype reduceOneRight
+    if (!Array.prototype.reduceOneRight) {
+      Array.prototype.reduceOneRight = function() {
+        return this.slice(0, -1);
+      };
+    }
+  }
+
+  use(fn) {
+    this.go = (stack => (...args) =>
+      stack(...args.reduceOneRight(), () => {
+        let _next = args.last();
+        fn.apply(this, [
+          ...args.reduceOneRight(),
+          _next.bind.apply(_next, [null, ...args.reduceOneRight()])
+        ]);
+      }))(this.go);
+  }
+
+  go(...args) {
+    let _next = args.last();
+    _next.apply(this, args.reduceOneRight());
+  }
+}
diff --git a/wax-prosemirror-schema/src/editoria/nodes.js b/wax-prosemirror-schema/src/editoria/nodes.js
index 8661b95d0..71366017f 100644
--- a/wax-prosemirror-schema/src/editoria/nodes.js
+++ b/wax-prosemirror-schema/src/editoria/nodes.js
@@ -65,7 +65,6 @@ const nodes = {
       {
         tag: "p[data-track]",
         getAttrs(dom) {
-          debugger;
           return {
             track: parseTracks(dom.dataset.track)
           };
@@ -74,8 +73,6 @@ const nodes = {
       {
         tag: "p.paragraph",
         getAttrs(dom) {
-          console.log(dom.getAttribute("class"));
-          debugger;
           return {
             class: dom.getAttribute("class")
           };
-- 
GitLab