///
/**
* Global Imports
*/
import events = require('events');
import query = require('component-query');
import DEBUG = require('debug');
import currentSelection = require('current-selection');
import currentRange = require('current-range');
import collapse = require('collapse');
import dataset = require('dataset');
/**
* Local Imports
*/
import Editor = require('../editor/index')
import Token = require('./token')
import TokenizerRenderer = require('./renderer');
import hacks = require('../hacks/index');
import is = require('../is/index');
import util = require('./util');
var debug = DEBUG('editor:tokenizer');
class Tokenizer extends events.EventEmitter {
private editor: Editor;
public renderer: TokenizerRenderer;
public allotted: Token[];
public updateTimer: any;
public constructor(editor: Editor) {
if (!(this instanceof Tokenizer)) {
return new Tokenizer(editor);
}
super();
this.allotted = [];
this.editor = editor;
this.renderer = new TokenizerRenderer(this);
this.editor.on('contentchange', () => {
this.requestUpdate();
});
this.editor.on('selectionchange', () => {
if (!this.updateTimer) {
this.renderer.render();
this.handleSelectionChange();
}
});
this.editor.on('reset', () => {
this.update();
this.handleLoad();
});
}
public update(): void {
this.allotted = [];
this._update(this.editor.el);
this.renderer.render();
this.handleSelectionChange();
}
private _update(el: HTMLElement): void {
var children = el.childNodes;
var length = children.length;
for (var i = 0; i < length; i++) {
var child = children[i];
if (is.list(child) || is.blockquote(child)) {
this._update(child);
} else {
this.emit('update', child, util.extractTextContent(child));
}
}
}
public requestUpdate(): void {
if (!this.updateTimer) {
this.updateTimer = setTimeout(() => {
try {
this.update();
} finally {
this.updateTimer = null;
}
}, 0);
}
}
public createToken(el: HTMLElement, text: string, index: number): Token {
return new Token(el, text, index);
}
public createAccessory(token: Token, leftHTML: string = null, rightHTML: string = null): HTMLElement {
var accessory = document.createElement('div');
accessory.className = 'token-accessory';
if (leftHTML) {
var left = document.createElement('div');
left.className = 'left';
left.innerHTML = leftHTML;
accessory.appendChild(left);
}
if (rightHTML) {
var right = document.createElement('div');
right.className = 'right';
right.innerHTML = rightHTML;
accessory.appendChild(right);
}
accessory.addEventListener('click', (e) => {
if ((e.target).className == 'replace') {
this.editor.transactions.run(() => {
var s = currentSelection(document);
var r = document.createRange();
var replacement = token.replace(this.editor);
if (replacement) {
r.selectNode(replacement);
s.removeAllRanges();
s.addRange(r);
collapse.toEnd(s);
}
});
}
if ((e.target).className == 'exclude') {
this.editor.transactions.run(() => {
var s = currentSelection(document);
var r = document.createRange();
var replacement = token.exclude(this.editor);
if (replacement) {
r.selectNode(replacement);
s.removeAllRanges();
s.addRange(r);
collapse.toEnd(s);
}
});
}
e.preventDefault();
}, false);
accessory.addEventListener('mousedown', (e) => {
e.preventDefault();
}, false);
return accessory;
}
public add(token: Token): void {
var exclusions = query.all('.no-tokens', token.el);
for (var i = 0; i < exclusions.length; i++) {
if (token.intersectsNode(exclusions[i])) {
return;
}
}
var intersecting = this.allotted.filter((allotted) => token.intersects(allotted));
var superseding = intersecting.filter((intersected) => token.supersedes(intersected));
if (superseding.length < intersecting.length) {
// token is ignored, as there are tokens it intersects with
// that it cannot supersede.
return;
}
if (superseding.length > 0) {
this.allotted = this.allotted.filter((allotted) => superseding.indexOf(allotted) == -1);
}
this.allotted.push(token);
}
public focused(): Token {
var allotted = this.allotted;
var length = allotted.length;
for (var i = 0; i < length; i++) {
if (allotted[i].focused()) {
return allotted[i];
}
}
}
public handleEsc() {
var allotted = this.allotted;
var length = allotted.length;
for (var i = 0; i < length; i++) {
if (allotted[i].focused() && (allotted[i].excludeOnEsc)) {
this.editor.transactions.run(() => {
var s = currentSelection(document);
var r = document.createRange();
var replacement = allotted[i].exclude(this.editor);
if (replacement) {
if (hacks.addSpaceOnEOLTokenDismiss) {
if (!replacement.nextSibling ||
(replacement.nextSibling.nodeType == Node.TEXT_NODE && replacement.nextSibling.nodeValue == '')) {
var space = document.createTextNode(' ');
replacement.parentNode.insertBefore(space, replacement.nextSibling);
}
}
r.selectNode(replacement);
s.removeAllRanges();
s.addRange(r);
collapse.toEnd(s);
}
});
return true;
}
}
return false;
}
public handleEnter(): boolean {
var allotted = this.allotted;
var length = allotted.length;
for (var i = 0; i < length; i++) {
if (allotted[i].focused() && (allotted[i].replaceOnEnter)) {
this.editor.transactions.run(() => {
var s = currentSelection(document);
var r = document.createRange();
var replacement = allotted[i].replace(this.editor);
if (replacement) {
r.selectNode(replacement);
s.removeAllRanges();
s.addRange(r);
collapse.toEnd(s);
}
});
return true;
}
}
return false;
}
public handleSelectionChange() {
var allotted = this.allotted;
var length = allotted.length;
var s = currentSelection(document);
if (!s.isCollapsed) return;
for (var i = 0; i < length; i++) {
if (allotted[i].excludeOnUnfocus && !allotted[i].focused()) {
this.editor.transactions.runAndSquash(() => {
if (hacks.storeSelectionForTextNodeChanges) {
var r = currentRange(s);
}
allotted[i].exclude(this.editor);
if (r) {
s.removeAllRanges();
s.addRange(r);
}
});
} else if (allotted[i].replaceOnUnfocus && !allotted[i].focused()) {
this.editor.transactions.runAndSquash(() => {
if (hacks.storeSelectionForTextNodeChanges) {
var r = currentRange(s);
}
allotted[i].replace(this.editor);
if (r) {
s.removeAllRanges();
s.addRange(r);
}
});
}
}
}
public handleLoad() {
var allotted = this.allotted;
var length = allotted.length;
this.editor.transactions.runAndSquash(() => {
for (var i = 0; i < length; i++) {
if (allotted[i].replaceOnLoad) {
this.editor.transactions.runAndSquash(() => {
allotted[i].replace(this.editor);
});
}
}
});
}
}
export = Tokenizer;