diff --git a/README.md b/README.md
index 57a0526..bd879c4 100644
--- a/README.md
+++ b/README.md
@@ -29,7 +29,10 @@ Setting up Typester on your page is as easy as:
```
import Typester from 'typester-editor'
-new Typester({ el: document.querySelector('[contenteditable]') }) // Where document.querySelector(...) is a single DOM element.
+const typesterInstance = new Typester({ el: document.querySelector('[contenteditable]') }) // Where document.querySelector(...) is a single DOM element.
+
+// If you need to tear down for any reason:
+typesterInstance.destroy();
```
### Configuration
@@ -43,6 +46,15 @@ new Typester({
configs: {
toolbar: {
buttons: ['bold', 'italic', 'h1', 'h2', 'orderedlist', 'unorderedlist', 'quote', 'link']
+ },
+
+ styles: {
+ colors: {
+ flyoutBg: 'rgb(32, 31, 32)',
+ menuItemIcon: 'rgb(255, 255, 255)',
+ menuItemHover: 'rgb(0, 174, 239)',
+ menuItemActive: 'rgb(0, 156, 215)'
+ }
}
}
});
diff --git a/src/scripts/config/styles.js b/src/scripts/config/styles.js
new file mode 100644
index 0000000..7494347
--- /dev/null
+++ b/src/scripts/config/styles.js
@@ -0,0 +1,8 @@
+export default {
+ colors: {
+ flyoutBg: 'rgb(32, 31, 32)',
+ menuItemIcon: 'rgb(255, 255, 255)',
+ menuItemHover: 'rgb(0, 174, 239)',
+ menuItemActive: 'rgb(0, 156, 215)'
+ }
+};
diff --git a/src/scripts/containers/AppContainer.js b/src/scripts/containers/AppContainer.js
index a0e65c7..3b42c68 100644
--- a/src/scripts/containers/AppContainer.js
+++ b/src/scripts/containers/AppContainer.js
@@ -116,6 +116,15 @@ const AppContainer = Container({
*/
handleBlur () {
// Should the container require to do anything in particular here
+ },
+
+ /**
+ * Destroy the entire Typeset instance
+ * @func destroy
+ */
+ destroy () {
+ const { mediator } = this;
+ mediator.emit('app:destroy');
}
}
});
diff --git a/src/scripts/containers/UIContainer.js b/src/scripts/containers/UIContainer.js
index 39183f9..194ba0b 100644
--- a/src/scripts/containers/UIContainer.js
+++ b/src/scripts/containers/UIContainer.js
@@ -19,6 +19,7 @@ import Container from '../core/Container';
import Toolbar from '../modules/Toolbar';
import Flyout from '../modules/Flyout';
import Mouse from '../modules/Mouse';
+import Styles from '../modules/Styles';
/**
* @constructor UIContainer
@@ -32,7 +33,7 @@ const UIContainer = Container({
/**
* Child Modules: [{@link modules/Flyout}, {@link modules/Toolbar}]
* Note: The Toobar is instantiated with the document body set as it's dom.el.
- * @enum {Array<{class:Module}>} modules
+ * @enum {Array<{class:Module}>} modules
*/
modules: [
{
@@ -48,6 +49,9 @@ const UIContainer = Container({
},
{
class: Mouse
+ },
+ {
+ class: Styles
}
]
});
diff --git a/src/scripts/core/Container.js b/src/scripts/core/Container.js
index e89fc7d..8f03f33 100644
--- a/src/scripts/core/Container.js
+++ b/src/scripts/core/Container.js
@@ -166,7 +166,9 @@ const Container = function Container(containerObj) {
return {
setMediatorParent (parentMediator) {
mediator.setParent(parentMediator);
- }
+ },
+
+ destroy: boundMethods.destroy
};
}
};
diff --git a/src/scripts/core/Mediator.js b/src/scripts/core/Mediator.js
index 34a7cd6..216f0b2 100644
--- a/src/scripts/core/Mediator.js
+++ b/src/scripts/core/Mediator.js
@@ -122,6 +122,10 @@ const Mediator = function (opts={}) {
if (requestHandler) {
return requestHandler(options);
}
+ },
+
+ destroy () {
+ requests.handlers = {};
}
};
@@ -152,6 +156,10 @@ const Mediator = function (opts={}) {
if (commandHandler) {
commandHandler(options);
}
+ },
+
+ destroy () {
+ commands.handlers = {};
}
};
@@ -176,9 +184,18 @@ const Mediator = function (opts={}) {
emit (eventKey, options) {
const eventHandlers = events.getHandlers(eventKey);
+
if (eventHandlers.length) {
eventHandlers.forEach((eventHandler) => eventHandler(options));
}
+
+ if (eventKey === 'app:destroy') {
+ fn.destroy();
+ }
+ },
+
+ destroy () {
+ events.handlers = {};
}
};
@@ -370,6 +387,12 @@ const Mediator = function (opts={}) {
getId () {
return internal.id;
+ },
+
+ destroy () {
+ requests.destroy();
+ commands.destroy();
+ events.destroy();
}
};
diff --git a/src/scripts/core/Module.js b/src/scripts/core/Module.js
index 33d07e9..2bb8104 100644
--- a/src/scripts/core/Module.js
+++ b/src/scripts/core/Module.js
@@ -112,6 +112,11 @@ const Module = function (moduleObj) {
moduleUtils.bindDomEvents(handlerMethods, context);
},
+ deregisterDomHandlers (domHandlersMap, context) {
+ let handlerMethods = moduleUtils.getHandlerMethods(domHandlersMap, context);
+ moduleUtils.unbindDomEvents(handlerMethods, context);
+ },
+
getHandlerMethods (handlerMap, context) {
let routedHandlers = {};
@@ -171,6 +176,22 @@ const Module = function (moduleObj) {
});
},
+ unbindDomEvents (handlers, context) {
+ const { dom } = context;
+
+ if (!dom) {
+ return;
+ }
+
+ Object.keys(handlers).forEach((eventElKey) => {
+ const [eventKey, elemKey] = eventElKey.split(' @');
+ const elem = elemKey ? dom[elemKey][0] : dom.el[0];
+ const eventHandler = handlers[eventElKey];
+
+ elem.removeEventListener(eventKey, eventHandler);
+ });
+ },
+
mergeProps (defaultProps, props={}) {
let mergedProps = {};
@@ -248,6 +269,10 @@ const Module = function (moduleObj) {
context.setup();
}
+ context.mediator.registerHandler('event', 'app:destroy', function () {
+ moduleProto.destroyModule(opts);
+ });
+
if (moduleHandlers) {
moduleUtils.registerHandlers(opts.mediator, moduleHandlers, context);
}
@@ -280,8 +305,12 @@ const Module = function (moduleObj) {
}
},
- destroyModule () {
+ destroyModule (opts) {
+ const { context } = opts;
+ if (moduleHandlers.domEvents) {
+ moduleUtils.deregisterDomHandlers(moduleHandlers.domEvents, context);
+ }
}
};
diff --git a/src/scripts/modules/Config.js b/src/scripts/modules/Config.js
index 3b045c0..b66d82c 100644
--- a/src/scripts/modules/Config.js
+++ b/src/scripts/modules/Config.js
@@ -1,11 +1,12 @@
import Module from '../core/Module';
import toolbarConfig from '../config/toolbar';
import config from '../config/config';
+import stylesConfig from '../config/styles';
const Config = Module({
name: 'Config',
props: {},
- acceptsConfigs: ['toolbar'],
+ acceptsConfigs: ['toolbar', 'styles'],
handlers: {
requests: {
@@ -16,7 +17,8 @@ const Config = Module({
'config:toolbar:listTags' : 'getToolbarListTags',
'config:toolbar:preventNewlineDefault' : 'getToolbarPreventNewlineDefault',
'config:blockElementNames' : 'getConfigBlockElementNames',
- 'config:defaultBlock' : 'getDefaultBlock'
+ 'config:defaultBlock' : 'getDefaultBlock',
+ 'config:styles': 'getStyles'
}
},
@@ -70,6 +72,13 @@ const Config = Module({
getDefaultBlock () {
return config.defaultBlock;
+ },
+
+ getStyles () {
+ const { configs } = this;
+ return {
+ colors: Object.assign({}, stylesConfig.colors, configs.styles.colors)
+ };
}
}
});
diff --git a/src/scripts/modules/Styles.js b/src/scripts/modules/Styles.js
new file mode 100644
index 0000000..fdc9017
--- /dev/null
+++ b/src/scripts/modules/Styles.js
@@ -0,0 +1,104 @@
+// jshint strict: false
+
+/**
+ * Styles -
+ * Handle the creation and embedding of custom styles.
+ * @access protected
+ * @module modules/Styles
+ */
+
+import Module from '../core/Module';
+
+const Styles = Module({
+ name: 'Styles',
+
+ props: {
+ stylesheet: null,
+ },
+
+ handlers: {
+ events: {
+ 'app:destroy': 'destroy'
+ }
+ },
+
+ methods: {
+ setup () {
+ this.createStylesheet();
+ },
+
+ init () {
+ const { mediator } = this;
+ const config = mediator.get('config:styles');
+ let stylesheetContent = this.stylesTemplate(config);
+
+ this.appendStylesheet();
+ this.updateStylesheet(stylesheetContent);
+ },
+
+ stylesTemplate (config) {
+ return `
+ .typester-toolbar .typester-menu-item,
+ .typester-input-form input[type=text],
+ .typester-link-display a,
+ .typester-input-form button {
+ color: ${config.colors.menuItemIcon};
+ }
+
+ .typester-toolbar .typester-menu-item svg,
+ .typester-link-display .typester-link-edit svg,
+ .typester-input-form button svg {
+ fill: ${config.colors.menuItemIcon};
+ }
+
+ .typester-input-form button svg {
+ stroke: ${config.colors.menuItemIcon};
+ }
+
+ .typester-toolbar .typester-menu-item:hover,
+ .typester-link-display .typester-link-edit:hover
+ .typester-input-form button:hover {
+ background: ${config.colors.menuItemHover};
+ }
+
+ .typester-toolbar .typester-menu-item.s--active {
+ background: ${config.colors.menuItemActive};
+ }
+
+ .typester-flyout .typester-flyout-content {
+ background: ${config.colors.flyoutBg};
+ }
+
+ .typester-flyout.place-above .typester-flyout-arrow {
+ border-top-color: ${config.colors.flyoutBg};
+ }
+
+ .typester-flyout.place-below .typester-flyout-arrow {
+ border-bottom-color: ${config.colors.flyoutBg};
+ }
+ `;
+ },
+
+ createStylesheet () {
+ this.stylesheet = document.createElement('style');
+ },
+
+ appendStylesheet () {
+ document.head.appendChild(this.stylesheet);
+ },
+
+ updateStylesheet (stylesheetContent) {
+ this.stylesheet.textContent = stylesheetContent;
+ },
+
+ removeStylesheet () {
+ document.head.removeChild(this.stylesheet);
+ },
+
+ destroy () {
+ this.removeStylesheet();
+ }
+ }
+});
+
+export default Styles;
diff --git a/src/templates/inputForm.html b/src/templates/inputForm.html
index 5c6abd0..8d7b77c 100644
--- a/src/templates/inputForm.html
+++ b/src/templates/inputForm.html
@@ -3,7 +3,7 @@