Skip to content

Commit 54a5582

Browse files
committed
#23 (+ #24, #35, #36) - Refinement of toolbar formatter config + global config module + some fixes
1 parent 5f68dcf commit 54a5582

File tree

13 files changed

+146
-56
lines changed

13 files changed

+146
-56
lines changed

src/scripts/config/toolbar.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ const Toolbar = {
102102

103103
strikethrough: {
104104
formatter: 'text:strikethrough',
105-
content: '<s>A</s>'
105+
content: '<s>Abc</s>'
106106
},
107107

108108
superscript: {

src/scripts/containers/AppContainer.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import FormatterContainer from '../containers/FormatterContainer';
3030
import CanvasContainer from '../containers/CanvasContainer';
3131
import ContentEditable from '../modules/ContentEditable';
3232
import Selection from '../modules/Selection';
33+
import Config from '../modules/Config';
3334

3435
let uiContainer, formatterContainer, canvasContainer;
3536

@@ -53,6 +54,9 @@ const AppContainer = Container({
5354
},
5455
{
5556
class: Selection
57+
},
58+
{
59+
class: Config
5660
}
5761
],
5862

src/scripts/core/Container.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,11 +149,13 @@ const Container = function Container(containerObj) {
149149

150150
containerUtils.initModules(containerModules, {
151151
dom: opts.dom,
152+
configs: opts.configs,
152153
mediator
153154
});
154155

155156
containerUtils.initChildContainers(containerChildContainers, {
156157
dom: opts.dom,
158+
configs: opts.configs,
157159
mediator
158160
});
159161

src/scripts/core/Module.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ const Module = function (moduleObj) {
5353
handlers: moduleHandlers,
5454
dom: moduleDom,
5555
methods: moduleMethods,
56-
requiredProps: moduleRequiredProps
56+
requiredProps: moduleRequiredProps,
57+
acceptsConfigs: moduleAcceptsConfigs
5758
} = moduleObj;
5859

5960
if (!moduleName) {
@@ -125,6 +126,7 @@ const Module = function (moduleObj) {
125126

126127
mergeDom (defaultDom, dom={}) {
127128
let mergedDom = {};
129+
128130
Object.keys(defaultDom).forEach((domKey) => {
129131
mergedDom[domKey] = defaultDom[domKey];
130132
});
@@ -139,6 +141,7 @@ const Module = function (moduleObj) {
139141

140142
getDom (dom) {
141143
const rootEl = dom.el || document.body;
144+
142145
Object.keys(dom).forEach((domKey) => {
143146
let selector, domEl;
144147

@@ -191,6 +194,7 @@ const Module = function (moduleObj) {
191194
const moduleProto = {
192195
moduleConstructor: function (opts) {
193196
moduleProto.prepModule(opts);
197+
moduleProto.bindConfigs(opts);
194198
moduleProto.buildModule(opts);
195199
moduleProto.setupModule(opts);
196200
moduleProto.renderModule(opts);
@@ -212,6 +216,20 @@ const Module = function (moduleObj) {
212216
opts.context = context;
213217
},
214218

219+
bindConfigs (opts) {
220+
if (!moduleAcceptsConfigs) { return; }
221+
222+
const { context } = opts;
223+
const optsConfigs = opts.configs || {};
224+
let moduleConfigs = {};
225+
226+
moduleAcceptsConfigs.forEach((configKey) => {
227+
moduleConfigs[configKey] = optsConfigs[configKey] || {}
228+
});
229+
230+
context.extendWith({ configs: moduleConfigs });
231+
},
232+
215233
buildModule (opts) {
216234
const { context } = opts;
217235
const boundMethods = moduleUtils.bindMethods(moduleMethods, context);

src/scripts/index.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import AppContainer from './containers/AppContainer';
1212
* @param {object} opts={} - instance options
1313
* @param {object} opts.dom - The dom components used by Typester
1414
* @param {element} opts.dom.el - The dom element to be the canvas for Typester
15+
* @param {object} opts.config - Additional instanced config
1516
* @return {appContainer} AppContainer instance
1617
*
1718
* @example
@@ -22,7 +23,10 @@ import AppContainer from './containers/AppContainer';
2223
* });
2324
*/
2425
const Typester = function (opts={}) {
25-
return new AppContainer({ dom: {el: opts.el }});
26+
return new AppContainer({
27+
dom: {el: opts.el },
28+
configs: opts.configs
29+
});
2630
};
2731

2832
export default Typester;

src/scripts/modules/BaseFormatter.js

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,7 @@ const BaseFormatter = Module({
6464
this.injectHooks(rootElement);
6565

6666
const rangeCoordinates = mediator.get('selection:range:coordinates');
67-
const clonedNodes = this.cloneNodes(rootElement);
68-
clonedNodes.forEach((node) => {
69-
DOM.trimNodeText(node);
70-
});
67+
const clonedNodes = DOM.cloneNodes(rootElement, { trim: true });
7168

7269
mediator.exec('canvas:content', clonedNodes);
7370
mediator.exec('canvas:select:by:coordinates', rangeCoordinates);
@@ -141,14 +138,6 @@ const BaseFormatter = Module({
141138
/**
142139
* PRIVATE METHODS:
143140
*/
144-
cloneNodes (rootElement) {
145-
let clonedNodes = [];
146-
rootElement.childNodes.forEach((node) => {
147-
clonedNodes.push(node.cloneNode(true));
148-
});
149-
return clonedNodes;
150-
},
151-
152141
injectHooks (rootElement) {
153142
while (!/\w+/.test(rootElement.firstChild.textContent)) {
154143
DOM.removeNode(rootElement.firstChild);
@@ -235,9 +224,10 @@ const BaseFormatter = Module({
235224
const isLastChild = brNode === brNode.parentNode.lastChild;
236225
const isDoubleBreak = brNode.nextSibling && brNode.nextSibling.nodeName === 'BR';
237226
const isInBlock = DOM.isIn(brNode, blockTags, rootElem);
227+
const isOrphan = brNode.parentNode === rootElem;
238228

239-
if (isLastChild) {
240-
brNodesToRemove.push(isLastChild);
229+
if (isLastChild || isOrphan) {
230+
brNodesToRemove.push(brNode);
241231
return;
242232
}
243233

@@ -291,8 +281,9 @@ const BaseFormatter = Module({
291281
let isInvalid = validTags.indexOf(currentNode.nodeName) < 0;
292282
let isBrNode = currentNode.nodeName === 'BR'; // BR nodes are handled elsewhere
293283
let isTypesterElem = currentNode.className && /typester/.test(currentNode.className);
284+
let isElement = currentNode.nodeType !== Node.TEXT_NODE;
294285

295-
if (isInvalid && !isBrNode && !isTypesterElem) {
286+
if (isInvalid && !isBrNode && !isTypesterElem && isElement) {
296287
invalidElements.unshift(currentNode);
297288
}
298289
}
@@ -312,6 +303,7 @@ const BaseFormatter = Module({
312303

313304
defaultOrphanedTextNodes (rootElem) {
314305
const { childNodes } = rootElem;
306+
315307
for (let i = 0; i < childNodes.length; i++) {
316308
let childNode = childNodes[i];
317309
if (childNode.nodeType === Node.TEXT_NODE && /\w+/.test(childNode.textContent)) {

src/scripts/modules/Canvas.js

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -305,13 +305,10 @@ const Canvas = Module({
305305
exportAll () {
306306
const { mediator } = this;
307307
const canvasBody = this.getCanvasBody();
308-
// this.exportPrep();
308+
const clonedNodes = DOM.cloneNodes(canvasBody, { trim: true });
309+
const exportHTMLString = DOM.nodesToHTMLString(clonedNodes);
309310

310-
let innerHTML = canvasBody.innerHTML;
311-
// innerHTML = innerHTML.replace(/\s{2,}/g, ' ');
312-
// innerHTML = innerHTML.replace(/\r?\n|\r/g, '');
313-
314-
mediator.exec('contenteditable:inserthtml', innerHTML);
311+
mediator.exec('contenteditable:inserthtml', exportHTMLString);
315312
},
316313

317314
getFormattedBlock () {
@@ -357,8 +354,6 @@ const Canvas = Module({
357354
) {
358355
DOM.unwrap(node);
359356
}
360-
361-
362357
}
363358
},
364359

src/scripts/modules/Config.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import Module from '../core/Module';
2+
import toolbarConfig from '../config/toolbar';
3+
4+
const Config = Module({
5+
name: 'Config',
6+
props: {},
7+
acceptsConfigs: ['toolbar'],
8+
9+
10+
handlers: {
11+
requests: {
12+
'config:toolbar:buttons' : 'getToolbarButtons',
13+
'config:toolbar:buttonConfig' : 'getToolbarButtonConfig'
14+
}
15+
},
16+
17+
methods: {
18+
setup () {},
19+
init () {
20+
},
21+
22+
getToolbarButtons () {
23+
const { mediator, configs } = this;
24+
const contentEditableButtons = mediator.get('contenteditable:toolbar:buttons') || [];
25+
const configButtons = contentEditableButtons.length ? contentEditableButtons :
26+
configs.toolbar.buttons ? configs.toolbar.buttons :
27+
toolbarConfig.buttons;
28+
29+
let buttons = [];
30+
configButtons.forEach((configKey) => {
31+
// NB This needs to be looked at
32+
if (configKey === 'anchor') {
33+
configKey = 'link';
34+
}
35+
const buttonConfig = Object.assign({ configKey }, toolbarConfig.buttonConfigs[configKey]);
36+
buttons.push(buttonConfig);
37+
});
38+
39+
return { buttons };
40+
},
41+
42+
getToolbarButtonConfig (buttonConfigKey) {
43+
return toolbarConfig.buttonConfigs[buttonConfigKey];
44+
}
45+
}
46+
});
47+
48+
export default Config;

src/scripts/modules/Toolbar.js

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -95,21 +95,7 @@ const Toolbar = Module({
9595

9696
getButtonConfigs () {
9797
const { mediator } = this;
98-
const contentEditableButtons = mediator.get('contenteditable:toolbar:buttons') || [];
99-
const configButtons = contentEditableButtons.length ? contentEditableButtons : toolbarConfig.buttons;
100-
101-
let buttons = [];
102-
103-
configButtons.forEach((configKey) => {
104-
// NB This needs to be looked at
105-
if (configKey === 'anchor') {
106-
configKey = 'link';
107-
}
108-
const buttonConfig = Object.assign({ configKey }, toolbarConfig.buttonConfigs[configKey]);
109-
buttons.push(buttonConfig);
110-
});
111-
112-
return { buttons };
98+
return mediator.get('config:toolbar:buttons');
11399
},
114100

115101
handleToolbarClick (evnt) {
@@ -120,7 +106,7 @@ const Toolbar = Module({
120106
const menuItemEl = DOM.getClosest(evnt.target, `.${props.classNames.MENU_ITEM}`);
121107
const { dataset } = menuItemEl;
122108
const { configKey } = dataset;
123-
const buttonConfig = toolbarConfig.buttonConfigs[configKey];
109+
const buttonConfig = mediator.get('config:toolbar:buttonConfig', configKey);
124110
const { formatter, opts } = buttonConfig;
125111
const toolbarMenuItemState = this.getMenuItemState(menuItemEl);
126112

src/scripts/utils/DOM.js

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -535,17 +535,48 @@ const DOM = {
535535
return childIndex;
536536
},
537537

538+
cloneNodes (rootElem, opts={}) {
539+
let clonedNodes = [];
540+
rootElem.childNodes.forEach((node) => {
541+
const clonedNode = node.cloneNode(true);
542+
clonedNodes.push(node.cloneNode(true));
543+
});
544+
545+
if (opts.trim) {
546+
clonedNodes.forEach((node) => {
547+
DOM.trimNodeText(node);
548+
});
549+
}
550+
551+
return clonedNodes;
552+
},
553+
538554
trimNodeText (node) {
539-
node.childNodes.forEach((childNode) => {
540-
if (childNode.nodeType === Node.TEXT_NODE) {
541-
childNode.textContent = childNode.textContent
542-
.replace(/\s{2,}/g, ' ')
543-
.replace(/\r?\n|\r/g, '')
544-
.trim();
545-
} else {
555+
if (node.nodeType === Node.TEXT_NODE) {
556+
const trimmedText = node.textContent
557+
.replace(/\s{2,}/g, ' ')
558+
.replace(/\r?\n|\r/g, '')
559+
.trim();
560+
node.textContent = trimmedText;
561+
} else {
562+
node.childNodes.forEach((childNode) => {
546563
DOM.trimNodeText(childNode);
564+
});
565+
}
566+
},
567+
568+
nodesToHTMLString (nodes) {
569+
let HTMLString = '';
570+
571+
nodes.forEach((node) => {
572+
if (node.nodeType === Node.TEXT_NODE) {
573+
HTMLString += node.textContent;
574+
} else {
575+
HTMLString += node.outerHTML;
547576
}
548577
});
578+
579+
return HTMLString;
549580
},
550581

551582
//Pseudo-private methods

0 commit comments

Comments
 (0)