PageSourceSearch

https://hannahimlach.com/_jsapps/inline-editor/cargoEditor_plugins_all.js

js hannahimlach.com collected 2026-10-04 13:23:41 UTC 173,045 bytes, 5,829 lines download raw bytes

1/*! cargoEditor - v0.2.0 - 2015-05-05 */ 
2
3
4(function () {
5
6  var diffcount;
7
8  var ADD_ATTRIBUTE = 0,
9    MODIFY_ATTRIBUTE = 1,
10    REMOVE_ATTRIBUTE = 2,
11    MODIFY_TEXT_ELEMENT = 3,
12    RELOCATE_GROUP = 4,
13    REMOVE_ELEMENT = 5,
14    ADD_ELEMENT = 6,
15    REMOVE_TEXT_ELEMENT = 7,
16    ADD_TEXT_ELEMENT = 8,
17    REPLACE_ELEMENT = 9,
18    MODIFY_VALUE = 10,
19    MODIFY_CHECKED = 11,
20    MODIFY_SELECTED = 12,
21    MODIFY_DATA = 13,
22    ACTION = 14,
23    ROUTE = 15,
24    OLD_VALUE = 16,
25    NEW_VALUE = 17,
26    ELEMENT = 18,
27    GROUP = 19,
28    FROM = 20,
29    TO = 21,
30    NAME = 22,
31    VALUE = 23,
32    TEXT = 24,
33    ATTRIBUTES = 25,
34    NODE_NAME = 26,
35    COMMENT = 27,
36    CHILD_NODES = 28,
37    CHECKED = 29,
38    SELECTED = 30;
39
40  var Diff = function (options) {
41    var diff = this;
42    Object.keys(options).forEach(function (option) {
43      diff[option] = options[option];
44    });
45  }
46  Diff.prototype = {
47    toString: function () {
48      return JSON.stringify(this);
49    }
50  };
51
52  var SubsetMapping = function SubsetMapping(a, b) {
53    this["old"] = a;
54    this["new"] = b;
55  };
56
57  SubsetMapping.prototype = {
58    contains: function contains(subset) {
59      if (subset.length < this.length) {
60        return subset["new"] >= this["new"] && subset["new"] < this["new"] + this.length;
61      }
62      return false;
63    },
64    toString: function toString() {
65      return this.length + " element subset, first mapping: old " + this["old"] + " → new " + this["new"];
66    }
67  };
68
69  var roughlyEqual = function roughlyEqual(e1, e2, preventRecursion) {
70    if (!e1 || !e2) return false;
71    if (e1.nodeType !== e2.nodeType) return false;
72    if (e1.nodeType === 3) {
73      if (e2.nodeType !== 3) return false;
74      // Note that we initially don't care what the text content of a node is,
75      // the mere fact that it's the same tag and "has text" means it's roughly
76      // equal, and then we can find out the true text difference later.
77      return preventRecursion ? true : e1.data === e2.data;
78    }
79    if (e1.nodeName !== e2.nodeName) return false;
80    if (e1.childNodes.length !== e2.childNodes.length) return false;
81    var thesame = true;
82    for (var i = e1.childNodes.length - 1; i >= 0; i--) {
83      if (preventRecursion) {
84        thesame = thesame && (e1.childNodes[i].nodeName === e2.childNodes[i].nodeName);
85      } else {
86        // note: we only allow one level of recursion at any depth. If 'preventRecursion'
87        //       was not set, we must explicitly force it to true for child iterations.
88        thesame = thesame && roughlyEqual(e1.childNodes[i], e2.childNodes[i], true);
89      }
90    }
91    return thesame;
92  };
93
94
95  var cleanCloneNode = function (node) {
96    // Clone a node with contents and add values manually,
97    // to avoid https://bugzilla.mozilla.org/show_bug.cgi?id=230307
98    var clonedNode = node.cloneNode(true),
99      textareas, clonedTextareas, options, clonedOptions, i;
100
101    if (node.nodeType != 8 && node.nodeType != 3) {
102
103      textareas = node.querySelectorAll('textarea');
104      clonedTextareas = clonedNode.querySelectorAll('textarea');
105      for (i = 0; i < textareas.length; i++) {
106        if (clonedTextareas[i].value !== textareas[i].value) {
107          clonedTextareas[i].value = textareas[i].value;
108        }
109      }
110      if (node.value && (node.value !== clonedNode.value)) {
111        clonedNode.value = node.value;
112      }
113      options = node.querySelectorAll('option');
114      clonedOptions = clonedNode.querySelectorAll('option');
115      for (i = 0; i < options.length; i++) {
116        if (options[i].selected && !(clonedOptions[i].selected)) {
117          clonedOptions[i].selected = true;
118        } else if (!(options[i].selected) && clonedOptions[i].selected) {
119          clonedOptions[i].selected = false;
120        }
121      }      
122      if (node.selected && !(clonedNode.selected)) {
123        clonedNode.selected = true;
124      } else if (!(node.selected) && clonedNode.selected) {
125        clonedNode.selected = false;
126      }
127    }
128    return clonedNode;
129  };
130
131  var nodeToObj = function (node) {
132    var objNode = {}, i;
133
134    if (node.nodeType === 3) {
135      objNode[TEXT] = node.data;
136    } else if (node.nodeType === 8) {
137      objNode[COMMENT] = node.data;
138    } else {
139      objNode[NODE_NAME] = node.nodeName;
140      if (node.attributes && node.attributes.length > 0) {
141        objNode[ATTRIBUTES] = [];
142        for (i = 0; i < node.attributes.length; i++) {
143          objNode[ATTRIBUTES].push([node.attributes[i].name, node.attributes[i].value]);
144        }
145      }
146      if (node.childNodes && node.childNodes.length > 0) {
147        objNode[CHILD_NODES] = [];
148        for (i = 0; i < node.childNodes.length; i++) {
149          objNode[CHILD_NODES].push(nodeToObj(node.childNodes[i])
149);
150        }
151      }
152      if (node.value) {
153        objNode[VALUE] = node.value;
154      }
155      if (node.checked) {
156        objNode[CHECKED] = node.checked;
157      }
158      if (node.selected) {
159        objNode[SELECTED] = node.selected;
160      }
161    }
162    return objNode;
163  };
164
165  var objToNode = function (objNode, insideSvg) {
166    var node, i;
167    if (objNode.hasOwnProperty(TEXT)) {
168      node = document.createTextNode(objNode[TEXT]);
169    } else if (objNode.hasOwnProperty(COMMENT)) {
170      node = document.createComment(objNode[COMMENT]);
171    } else {
172      if (objNode[NODE_NAME] === 'svg' || insideSvg) {
173        node = document.createElementNS('http://www.w3.org/2000/svg', objNode[NODE_NAME]);
174        insideSvg = true;
175      } else {
176        node = document.createElement(objNode[NODE_NAME]);
177      }
178      if (objNode[ATTRIBUTES]) {
179        for (i = 0; i < objNode[ATTRIBUTES].length; i++) {
180          node.setAttribute(objNode[ATTRIBUTES][i][0], objNode[ATTRIBUTES][i][1]);
181        }
182      }
183      if (objNode[CHILD_NODES]) {
184        for (i = 0; i < objNode[CHILD_NODES].length; i++) {
185          node.appendChild(objToNode(objNode[CHILD_NODES][i], insideSvg));
186        }
187      }
188      if (objNode[VALUE]) {
189        node.value = objNode[VALUE];
190      }
191      if (objNode[CHECKED]) {
192        node.checked = objNode[CHECKED];
193      }
194      if (objNode[SELECTED]) {
195        node.selected = objNode[SELECTED];
196      }
197    }
198    return node;
199  };
200
201
202
203  /**
204   * based on https://en.wikibooks.org/wiki/Algorithm_implementation/Strings/Longest_common_substring#JavaScript
205   */
206  var findCommonSubsets = function (c1, c2, marked1, marked2) {
207    var lcsSize = 0,
208      index = [],
209      len1 = c1.length,
210      len2 = c2.length;
211    // set up the matching table
212    var matches = [],
213      a, i, j;
214    for (a = 0; a < len1 + 1; a++) {
215      matches[a] = [];
216    }
217    // fill the matches with distance values
218    for (i = 0; i < len1; i++) {
219      for (j = 0; j < len2; j++) {
220        if (!marked1[i] && !marked2[j] && roughlyEqual(c1[i], c2[j])) {
221          matches[i + 1][j + 1] = (matches[i][j] ? matches[i][j] + 1 : 1);
222          if (matches[i + 1][j + 1] > lcsSize) {
223            lcsSize = matches[i + 1][j + 1];
224            index = [i + 1, j + 1];
225          }
226        } else {
227          matches[i + 1][j + 1] = 0;
228        }
229      }
230    }
231    if (lcsSize === 0) {
232      return false;
233    }
234    var origin = [index[0] - lcsSize, index[1] - lcsSize];
235    var ret = new SubsetMapping(origin[0], origin[1]);
236    ret.length = lcsSize;
237    return ret;
238  };
239
240  /**
241   * This should really be a predefined function in Array...
242   */
243  var makeArray = function (n, v) {
244    var deepcopy = function (v) {
245      v.slice();
246      for (var i = 0, last = v.length; i < last; i++) {
247        if (v[i] instanceof Array) {
248          v[i] = deepcopy(v[i]);
249        }
250      }
251    };
252    if (v instanceof Array) {
253      v = deepcopy(v);
254    }
255    var set = function () {
256      return v;
257    };
258    return (new Array(n)).join('.').split('.').map(set);
259  };
260
261  /**
262   * Generate arrays that indicate which node belongs to which subset,
263   * or whether it's actually an orphan node, existing in only one
264   * of the two trees, rather than somewhere in both.
265   */
266  var getGapInformation = function (t1, t2, stable) {
267    // [true, true, ...] arrays
268    var set = function (v) {
269      return function () {
270        return v;
271      }
272    },
273      gaps1 = makeArray(t1.childNodes.length, true),
274      gaps2 = makeArray(t2.childNodes.length, true),
275      group = 0;
276
277    // give elements from the same subset the same group number
278    stable.forEach(function (subset) {
279      var i, end;
280      for (i = subset["old"], end = i + subset.length; i < end; i++) {
281        gaps1[i] = group;
282      }
283      for (i = subset["new"], end = i + subset.length; i < end; i++) {
284        gaps2[i] = group;
285      }
286      group++;
287    });
288
289    return {
290      gaps1: gaps1,
291      gaps2: gaps2
292    };
293  };
294
295  /**
296   * Find all matching subsets, based on immediate child differences only.
297   */
298  var markSubTrees = function (oldTree, newTree) {
299    oldTree = cleanCloneNode(oldTree);
300    newTree = cleanCloneNode(newTree);
301    // note: the child lists are views, and so update as we update old/newTree
302    var oldChildren = oldTree.childNodes,
303      newChildren = newTree.childNodes,
304      marked1 = makeArray(oldChildren.length, false),
305      marked2 = makeArray(newChildren.length, false),
306      subsets = [],
307      subset = true,
308      i;
309    while (subset) {
310      subset = findCommonSubsets(oldChildren, newChildren, marked1, marked2);
311      if (subset) {
312        subsets.push(subset);
313        for (i = 0; i < subset.length; i++) {
314          marked1[subset.old + i] = true;
315        }
316        for (i = 0; i < subset.length; i++) {
317          marked2[subset.new + i] = true;
318        }
319      }
320    }
321    return subsets;
322  };
323
324  var findFirstInnerDiff = function (t1, t2, subtrees, route) {
325    if (subtrees.length === 0) return false;
326
327    var gapInformation = getGapInformation(t1, t2, subtrees),
328      gaps1 = gapInformation.gaps1,
329      gl1 = gaps1.length,
330      gaps2 = gapInformation.gaps2,
331      gl2 = gaps1.length,
332      i, j, k,
333      last = gl1 < gl2 ? gl1 : gl2;
334
335    // Check for correct submap sequencing (irrespective of gaps) first:
336    var sequence = 0,
337      group, node, similarNode, testNode,
338      shortest = gl1 < gl2 ? gaps1 : gaps2;
339
340    // group relocation
341    for (i = 0, last = shortest.length; i < last; i++) {
342      if (gaps1[i] === true) {
343        node = t1.childNodes[i];
344        if (node.nodeType === 3) {
345          if (t2.childNodes[i].nodeType === 3 && node.data != t2.childNodes[i].data) {
346            testNode = node;
347            while (testNode.nextSibling && testNode.nextSibling.nodeType === 3) {
348              testNode = testNode.nextSibling;
349              if (t2.childNodes[i].data === testNode.data) {
350                similarNode = true;
351                break;
352              }
353            }
354            if (!similarNode) {
355              k = {};
356              k[ACTION] = MODIFY_TEXT_ELEMENT;
357              k[ROUTE] = route.concat(i);
358              k[OLD_VALUE] = node.data;
359              k[NEW_VALUE] = t2.childNodes[i].data;
360              return new Diff(k);
361            }
362          }
363          k = {};
364          k[ACTION] = REMOVE_TEXT_ELEMENT;
365          k[ROUTE] = route.concat(i);
366          k[VALUE] = node.data;
367          return new Diff(k);
368        }
369        k = {};
370        k[ACTION] = REMOVE_ELEMENT;
371        k[ROUTE] = route.concat(i);
372        k[ELEMENT] = nodeToObj(node);
373        return new Diff(k);
374      }
375      if (gaps2[i] === true) {
376        node = t2.childNodes[i];
377        if (node.nodeType === 3) {
378          k = {};
379          k[ACTION] = ADD_TEXT_ELEMENT;
380          k[ROUTE] = route.concat(i);
381          k[VALUE] = node.data;
382          return new Diff(k);
383        }
384        k = {};
385        k[ACTION] = ADD_ELEMENT;
386        k[ROUTE] = route.concat(i);
387        k[ELEMENT] = nodeToObj(node);
388        return new Diff(k);
389      }
390      if (gaps1[i] != gaps2[i]) {
391        group = subtrees[gaps1[i]];
392        var toGroup = Math.min(group["new"], (t1.childNodes.length - group.length));
393        if (toGroup != i) {
394          //Check wehther destination nodes are different than originating ones.
395          var destinationDifferent = false;
396          for (j = 0; j < group.length; j++) {
397            if (!t1.childNodes[toGroup + j].isEqualNode(t1.childNodes[i + j])) {
398              destinationDifferent = true;
399            }
400
401          }
402          if (destinationDifferent) {
403            k = {};
404            k[ACTION] = RELOCATE_GROUP;
405            k[GROUP] = group;
406            k[FROM] = i;
407            k[TO] = toGroup;
408            k[ROUTE] = route;
409            return new Diff(k);
410          }
411        }
412      }
413    }
414    return false;
415  };
416
417
418  function swap(obj, p1, p2) {
419    (function (_) {
420      obj[p1] = obj[p2];
421      obj[p2] = _;
422    }(obj[p1]));
423  };
424
425
426  var DiffTracker = function () {
427    this.list = [];
428  };
429  DiffTracker.prototype = {
430    list: false,
431    add: function (difflist) {
432      var list = this.list;
433      difflist.forEach(function (diff) {
434        list.push(diff);
435      });
436    },
437    forEach: function (fn) {
438      this.list.forEach(fn);
439    }
440  };
441
442
443
444
445  var diffDOM = function (debug, diffcap) {
446    if (typeof debug === 'undefined') {
447      debug = false;
448
449    } else {
450
451      ADD_ATTRIBUTE = "add attribute",
452      MODIFY_ATTRIBUTE = "modify attribute",
453      REMOVE_ATTRIBUTE = "remove attribute",
454      MODIFY_TEXT_ELEMENT = "modify text element",
455      RELOCATE_GROUP = "relocate group",
456      REMOVE_ELEMENT = "remove element",
457      ADD_ELEMENT = "add element",
458      REMOVE_TEXT_ELEMENT = "remove text element",
459      ADD_TEXT_ELEMENT = "add text element",
460      REPLACE_ELEMENT = "replace element",
461      MODIFY_VALUE = "modify value",
462      MODIFY_CHECKED = "modify checked",
463      MODIFY_SELECTED = "modify selected",
464      ACTION = "action",
465      ROUTE = "route",
466      OLD_VALUE = "oldValue",
467      NEW_VALUE = "newValue",
468      ELEMENT = "element",
469      GROUP = "group",
470      FROM = "from",
471      TO = "to",
472      NAME = "name",
473      VALUE = "value",
474      TEXT = "text",
475      ATTRIBUTES = "attributes",
476    NODE_NAME = "nodeName",
477    COMMENT = "comment",
478    CHILD_NODES = "childNodes",
479    CHECKED = "checked",
480    SELECTED = "selected";
481    }
482
483
484
485
486    if (typeof diffcap === 'undefined')
487      diffcap = 10;
488    this.debug = debug;
489    this.diffcap = diffcap;
490  };
491  diffDOM.prototype = {
492
493    // ===== Create a diff =====
494
495    diff: function (t1, t2) {
496      diffcount = 0;
497      t1 = cleanCloneNode(t1);
498      t2 = cleanCloneNode(t2);
499      if (this.debug) {
500        this.t1Orig = nodeToObj(t1);
501        this.t2Orig = nodeToObj(t2);
502      }
503
504      this.tracker = new DiffTracker();
505      return this.findDiffs(t1, t2);
506    },
507    findDiffs: function (t1, t2) {
508      var diff;
509      do {
510        if (this.debug) {
511          diffcount++;
512          if (diffcount > this.diffcap) {
513            window.diffError = [this.t1Orig, this.t2Orig];
514            throw new Error("surpassed diffcap:" + JSON.stringify(this.t1Orig) + " -> " + JSON.stringify(this.t2Orig));
515          }
516        }
517        difflist = this.findFirstDiff(t1, t2, []);
518        if (difflist) {
519          if (!difflist.length) {
520            difflist = [difflist];
521          }
522          this.tracker.add(difflist);
523          this.apply(t1, difflist);
524        }
525      } while (difflist);
526      return this.tracker.list;
527    },
528    findFirstDiff: function (t1, t2, route) {
529      // outer differences?
530      var difflist = this.findOuterDiff(t1, t2, route);
531      if (difflist.length > 0) {
532        return difflist;
533      }
534      // inner differences?
535      var diff = this.findInnerDiff(t1, t2, route);
536      if (diff) {
537        if (typeof diff.length === "undefined") {
538          diff = [diff];
539        }
540        if (diff.length > 0) {
541          return diff;
542        }
543      }
544      // no differences
545      return false;
546    },
547    findOuterDiff: function (t1, t2, route) {
548      var k;
549      
550      if (t1.nodeName != t2.nodeName) {
551        k = {};
552        k[ACTION] = REPLACE_ELEMENT;
553        k[OLD_VALUE] = nodeToObj(t1);
554        k[NEW_VALUE] = nodeToObj(t2);
555        k[ROUTE] = route;
556        return [new Diff(k)];
557      }
558      
559      var slice = Array.prototype.slice,
560        byName = function (a, b) {
561          return a.name > b.name;
562        },
563        attr1 = t1.attributes ? slice.call(t1.attributes).sort(byName) : [],
564        attr2 = t2.attributes ? slice.call(t2.attributes).sort(byName) : [],
565        find = function (attr, list) {
566          for (var i = 0, last = list.length; i < last; i++) {
567            if (list[i].name === attr.name)
568              return i;
569          }
570          return -1;
571        },
572        diffs = [];
573      if ((t1.value || t2.value) && t1.value !== t2.value && t1.nodeName !== 'OPTION') {
574        k = {};
575        k[ACTION] = MODIFY_VALUE;
576        k[OLD_VALUE] = t1.value;
577        k[NEW_VALUE] = t2.value;
578        k[ROUTE] = route;
579        diffs.push(new Diff(k));
580      }
581      if ((t1.checked || t2.checked) && t1.checked !== t2.checked) {
582        k = {};
583        k[ACTION] = MODIFY_CHECKED;
584        k[OLD_VALUE] = t1.checked;
585        k[NEW_VALUE] = t2.checked;
586        k[ROUTE] = route;
587        diffs.push(new Diff(k));
588      }  
589
590      attr1.forEach(function (attr) {
591        var pos = find(attr, attr2),
592          k;
593        if (pos === -1) {
594          k = {};
595          k[ACTION] = REMOVE_ATTRIBUTE;
596          k[ROUTE] = route;
597          k[NAME] = attr.name;
598          k[VALUE] = attr.value;
599          diffs.push(new Diff(k));
600          return diffs;
601        }
602        var a2 = attr2.splice(pos, 1)[0];
603        if (attr.value !== a2.value) {
604          k = {};
605          k[ACTION] = MODIFY_ATTRIBUTE;
606          k[ROUTE] = route;
607          k[NAME] = attr.name;
608          k[OLD_VALUE] = attr.value;
609          k[NEW_VALUE] = a2.value;
610
611          diffs.push(new Diff(k));
612               //    console.log(diffs);
613        }
614      });
615      if (!t1.attributes && t1.data !== t2.data) {
616          k = {};
617          k[ACTION] = MODIFY_DATA;
618          k[ROUTE] = route;
619          k[OLD_VALUE] = t1.data;
620          k[NEW_VALUE] = t2.data;
621          diffs.push(new Diff(k));          
622      }
623      if (diffs.length > 0) {
624        return diffs;
625      };
626      attr2.forEach(function (attr) {
627        var k;
628        k = {};
629        k[ACTION] = ADD_ATTRIBUTE;
630        k[ROUTE] = route;
631        k[NAME] = attr.name;
632        k[VALUE] = attr.value;
633        diffs.push(new Diff(k));
634        
635      });
636      
637      if ((t1.selected || t2.selected) && t1.selected !== t2.selected) {
638        if (diffs.length > 0) {
639            return diffs;
640        }
641        k = {};
642        k[ACTION] = MODIFY_SELECTED;
643        k[OLD_VALUE] = t1.selected;
644        k[NEW_VALUE] = t2.selected;
645        k[ROUTE] = route;
646        diffs.push(new Diff(k));
647      }      
648      
649      return diffs;
650    },
651    findInnerDiff: function (t1, t2, route) {
652      var subtrees = markSubTrees(t1, t2),
653        mappings = subtrees.length,
654        k;
655      // no correspondence whatsoever
656      // if t1 or t2 contain differences that are not text nodes, return a diff. 
657
658      // two text nodes with differences
659      if (mappings === 0) {
660        if (t1.nodeType === 3 && t2.nodeType === 3 && t1.data !== t2.data) {
661          k = {};
662          k[ACTION] = MODIFY_TEXT_ELEMENT;
663          k[OLD_VALUE] = t1.data;
664          k[NEW_VALUE] = t2.data;
665          k[ROUTE] = route;
666          return new Diff(k);
667        }
668      }
669      // possibly identical content: verify
670      if (mappings < 2) {
671        var diff, difflist, i, last, e1, e2;
672        for (i = 0, last = Math.max(t1.childNodes.length, t2.childNodes.length); i < last; i++) {
673          e1 = t1.childNodes[i];
674          e2 = t2.childNodes[i];
675          // TODO: this is a similar code path to the one
676          //       in findFirstInnerDiff. Can we unify these?
677          if (e1 && !e2) {
678            if (e1.nodeType === 3) {
679              k = {};
680              k[ACTION] = REMOVE_TEXT_ELEMENT;
681              k[ROUTE] = route.concat(i);
682              k[VALUE] = e1.data;
683              return new Diff(k);
684            }
685            k = {};
686            k[ACTION] = REMOVE_ELEMENT;
687            k[ROUTE] = route.concat(i);
688            k[ELEMENT] = nodeToObj(e1);
689            return new Diff(k);
690          }
691          if (e2 && !e1) {
692            if (e2.nodeType === 3) {
693              k = {};
694              k[ACTION] = ADD_TEXT_ELEMENT;
695              k[ROUTE] = route.concat(i);
696              k[VALUE] = e2.data;
697              return new Diff(k);
698            }
699            k = {};
700            k[ACTION] = ADD_ELEMENT;
701            k[ROUTE] = route.concat(i);
702            k[ELEMENT] = nodeToObj(e2);
703            return new Diff(k);
704          }
705          if (e1.nodeType != 3 || e2.nodeType != 3) {
706            difflist = this.findOuterDiff(e1, e2, route.concat(i));
707            if (difflist.length > 0) {
708              return difflist;
709            }
710          }
711          diff = this.findInnerDiff(e1, e2, route.concat(i));
712          if (diff) {
713            return diff;
714          }
715        }
716      }
717
718      // one or more differences: find first diff
719      return this.findFirstInnerDiff(t1, t2, subtrees, route);
720    },
721
722    // imported
723    findFirstInnerDiff: findFirstInnerDiff,
724
725    // ===== Apply a diff =====
726
727    apply: function (tree, diffs) {
728      var dobj = this;
729      if (typeof diffs.length === "undefined") {
730        diffs = [diffs];
731      }
732      if (diffs.length === 0) {
733        return true;
734      }
735      diffs.forEach(function (diff) {
736        if (!dobj.applyDiff(tree, diff))
737          return false;
738      });
739      return true;
740    },
741    getFromRoute: function (tree, route) {
742      route = route.slice();
743      var c, node = tree;
744      while (route.length > 0) {
745        if (!node.childNodes) {
746          return false;
747        }
748        c = route.splice(0, 1)[0];
749        node = node.childNodes[c];
750      }
751      return node;
752    },
753    // diffing text elements can be overwritten for use with diff_match_patch and alike
754    textDiff: function (node, currentValue, expectedValue, newValue) {
755      node.data = newValue;
756      return;
757    },
758    applyDiff: function (tree, diff) {
759      var node = this.getFromRoute(tree, diff[ROUTE]);
760      if (diff[ACTION] === ADD_ATTRIBUTE) {
761        if (!node || !node.setAttribute)
762          return false;
763        node.setAttribute(diff[NAME], diff[VALUE]);
764      } else if (diff[ACTION] === MODIFY_ATTRIBUTE) {
765        if (!node || !node.setAttribute)
766          return false;
767        node.setAttribute(diff[NAME], diff[NEW_VALUE]);
768      } else if (diff[ACTION] === REMOVE_ATTRIBUTE) {
769        if (!node || !node.removeAttribute)
770          return false;
771        node.removeAttribute(diff[NAME]);
772      } else if (diff[ACTION] === MODIFY_VALUE) {
773        if (!node || typeof node.value === 'undefined')
774          return false;
775        node.value = diff[NEW_VALUE];
776      } else if (diff[ACTION] === MODIFY_DATA) {
777        if (!node || typeof node.data === 'undefined')
778          return false;
779        node.data = diff[NEW_VALUE];
780      } else if (diff[ACTION] === MODIFY_CHECKED) {
781        if (!node || typeof node.checked === 'undefined')
782          return false;
783        node.checked = diff[NEW_VALUE];
784      } else if (diff[ACTION] === MODIFY_SELECTED) {
785        if (!node || typeof node.selected === 'undefined')
786          return false;
787        node.selected = diff[NEW_VALUE];     
788      } else if (diff[ACTION] === MODIFY_TEXT_ELEMENT) {
789        if (!node || node.nodeType != 3)
790          return false;
791        this.textDiff(node, node.data, diff[OLD_VALUE], diff[NEW_VALUE]);
792      } else if (diff[ACTION] === REPLACE_ELEMENT) {
793        var newNode = objToNode(diff[NEW_VALUE]);
794        node.parentNode.replaceChild(newNode, node);
795      } else if (diff[ACTION] === RELOCATE_GROUP) {
796        var group = diff[GROUP],
797          from = diff[FROM],
798          to = diff[TO],
799          child, reference;
800        reference = node.childNodes[to + group.length];
801        // slide elements up
802        if (from < to) {
803          for (var i = 0; i < group.length; i++) {
804            child = node.childNodes[from];
805            node.insertBefore(child, reference);
806          }
807        } else {
808          // slide elements down
809          reference = node.childNodes[to];
810          for (var i = 0; i < group.length; i++) {
811            child = node.childNodes[from + i];
812            node.insertBefore(child, reference);
813          }
814        }
815      }
815 else if (diff[ACTION] === REMOVE_ELEMENT) {
816        node.parentNode.removeChild(node);
817      } else if (diff[ACTION] === REMOVE_TEXT_ELEMENT) {
818        if (!node || node.nodeType != 3)
819          return false;
820        node.parentNode.removeChild(node);
821      } else if (diff[ACTION] === ADD_ELEMENT) {
822        var route = diff[ROUTE].slice(),
823          c = route.splice(route.length - 1, 1)[0];
824        node = this.getFromRoute(tree, route);
825        var newNode = objToNode(diff[ELEMENT]);
826        if (c >= node.childNodes.length) {
827          node.appendChild(newNode);
828        } else {
829          var reference = node.childNodes[c];
830          node.insertBefore(newNode, reference);
831        }
832      } else if (diff[ACTION] === ADD_TEXT_ELEMENT) {
833        var route = diff[ROUTE].slice(),
834          c = route.splice(route.length - 1, 1)[0],
835          newNode = document.createTextNode(diff[VALUE]);
836        node = this.getFromRoute(tree, route);
837        if (!node || !node.childNodes)
838          return false;
839        if (c >= node.childNodes.length) {
840          node.appendChild(newNode);
841        } else {
842          var reference = node.childNodes[c];
843          node.insertBefore(newNode, reference);
844        }
845      }
846      return true;
847    },
848
849    // ===== Undo a diff =====
850
851    undo: function (tree, diffs) {
852      diffs = diffs.slice();
853      var dobj = this;
854      if (!diffs.length) {
855        diffs = [diffs];
856      }
857      diffs.reverse();
858      diffs.forEach(function (diff) {
859        dobj.undoDiff(tree, diff);
860      });
861    },
862    undoDiff: function (tree, diff) {
863      if (diff[ACTION] === ADD_ATTRIBUTE) {
864        diff[ACTION] = REMOVE_ATTRIBUTE;
865        this.applyDiff(tree, diff);
866      } else if (diff[ACTION] === MODIFY_ATTRIBUTE) {
867        swap(diff, OLD_VALUE, NEW_VALUE);
868        this.applyDiff(tree, diff);
869      } else if (diff[ACTION] === REMOVE_ATTRIBUTE) {
870        diff[ACTION] = ADD_ATTRIBUTE;
871        this.applyDiff(tree, diff);
872      } else if (diff[ACTION] === MODIFY_TEXT_ELEMENT) {
873        swap(diff, OLD_VALUE, NEW_VALUE);
874        this.applyDiff(tree, diff);
875      } else if (diff[ACTION] === MODIFY_VALUE) {
876        swap(diff, OLD_VALUE, NEW_VALUE);
877        this.applyDiff(tree, diff);
878      } else if (diff[ACTION] === MODIFY_DATA) {
879        swap(diff, OLD_VALUE, NEW_VALUE);
880        this.applyDiff(tree, diff);        
881      } else if (diff[ACTION] === MODIFY_CHECKED) {
882        swap(diff, OLD_VALUE, NEW_VALUE);
883        this.applyDiff(tree, diff);
884      } else if (diff[ACTION] === MODIFY_SELECTED) {
885        swap(diff, OLD_VALUE, NEW_VALUE);
886        this.applyDiff(tree, diff);
887      } else if (diff[ACTION] === REPLACE_ELEMENT) {
888        swap(diff, OLD_VALUE, NEW_VALUE);
889        this.applyDiff(tree, diff);
890      } else if (diff[ACTION] === RELOCATE_GROUP) {
891        swap(diff, FROM, TO);
892        this.applyDiff(tree, diff);
893      } else if (diff[ACTION] === REMOVE_ELEMENT) {
894        diff[ACTION] = ADD_ELEMENT;
895        this.applyDiff(tree, diff);
896      } else if (diff[ACTION] === ADD_ELEMENT) {
897        diff[ACTION] = REMOVE_ELEMENT;
898        this.applyDiff(tree, diff);
899      } else if (diff[ACTION] === REMOVE_TEXT_ELEMENT) {
900        diff[ACTION] = ADD_TEXT_ELEMENT;
901        this.applyDiff(tree, diff);
902      } else if (diff[ACTION] === ADD_TEXT_ELEMENT) {
903        diff[ACTION] = REMOVE_TEXT_ELEMENT;
904        this.applyDiff(tree, diff);
905      }
906    },
907  };
908
909  if (typeof exports !== 'undefined') {
910    if (typeof module !== 'undefined' && module.exports) {
911      exports = module.exports = diffDOM;
912    }
913    exports.diffDOM = diffDOM;
914  } else {
915    // `window` in the browser, or `exports` on the server
916    this.diffDOM = diffDOM;
917  }
918
919}.call(this));
920
921CargoEditor.addPlugin('cargo', function(cargoEditor){
922
923	'use strict'
924
925	// this plugin is unusable outside a Cargo environment. 
926	if(typeof Cargo === "undefined"){
927		console.error('Not in a Cargo environment.');
928		return false;
929	}
930
931	// plugin namespace reference
932	var _this = this;
933
934	// disable rail
935	Cargo.Rail.Drag = function(){};
936
937	// helper for doing AJAX requests
938	function doPostRequest(url, data, callback, async) {
939
940		var postData = null;
941
942		if(typeof data === "object"){
943			var key, postData = [];
944
945			for(key in data){
946				postData.push(key + '=' + encodeURIComponent(data[key]));
947			}
948
949			postData = postData.join('&');
950		}
951
952		var xmlHttp = new XMLHttpRequest();
953
954		if(async !== false){
955			async = true;
956		}
957
958		xmlHttp.open( "POST", url, async );
959		xmlHttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
960
961		xmlHttp.onreadystatechange = function(){
962
963			if (this.readyState != 4){
964				return;
965			}
966
967			if(typeof callback === 'function'){
968				callback.call(this, this);
969			}
970
971		};
972
973		xmlHttp.send( postData );
974
975	}
976
977	// Fail-safe JSON parsing
978	function parseJSON(JSONString){
979
980		try {
981			return JSON.parse(JSONString);
982		} catch(e){
983			return false;
984		}
985
986	}
987
988	// Attach the plugin to any editors that might have instanciated already
989	CargoEditor.getEditorInstances().forEach(function(editor){
990		attachToEditor(editor);
991	});
992
993	// attach the plugin to editors that instanciate at a later time
994	_this.events.on('editor-instanciated', function(editor) {
995		attachToEditor(editor);
996	});
997
998	function attachToEditor(editor){
999
1000		var editorElement = editor.getElement(),
1001			pid = editorElement.getAttribute('data-ce-model-id'),
1002			model = Cargo.Collection.Projects.findWhere({
1003				id: pid
1004			});
1005
1006		if(model === undefined && Cargo.View.ProjectDetail.collection !== undefined){
1007
1008			//sconsole.log(Cargo.View.ProjectDetail.collection.id, pid);
1009
1010			if(Cargo.View.ProjectDetail.collection.id == pid) {
1011				model = Cargo.View.ProjectDetail.collection;
1012			}
1013		}
1014
1015		//console.log(model);
1016
1017		editor.cargoData = {
1018			pid: (pid !== undefined ? pid : false),
1019			model: model
1020		}
1021	}
1022
1023	// Place to store the templates for various Cargo elements 
1024	// like the slideshow, video & audio player, etc...
1025	this.templates = undefined;
1026
1027	// Collection of methods that read and write from and to the Cargo API.
1028	this.api = {
1029
1030		uploadImage: function(file, pid, callback, progressCallback){
1031
1032			var formData = new FormData(),
1033				uid = Cargo.Model.Site.GetId(),
1034				url = Cargo.Helper.GetUrl();
1035
1036			formData.append('Filedata[]', file);
1037			
1038			return new Cargo.utils.CargoXHR({
1039				url: '/dispatch/adminproject/handleMultiFileUpload?pid=' + pid + '&url=' + url + '&uid=' + uid,
1040				type: 'POST',
1041				data: formData,
1042				progress: function(e){
1043					if(typeof progressCallback === "function"){
1044						progressCallback.apply(this, arguments);
1045					}
1046				},
1047				success: function(e){
1048					if(typeof callback === "function"){
1049						callback.apply(this, arguments);
1050					}
1051				}
1052			});
1053
1054		},
1055
1056		fetchTemplates: function(){
1057
1058			doPostRequest('/dispatch/adminproject/getProjectTemplates/' + Cargo.Model.Site.GetId() , null, function(xmlHttp){
1059
1060				if (this.status != 200) {
1061					console.error('Failed to fetch Cargo component templates.');
1062					return;
1063				}
1064
1065				var result = parseJSON(this.responseText);
1066
1067				if(result !== false){
1068					_this.templates = result.jdata.templates;
1069
1070					// make sure the templates are never overwritten.
1071					Object.freeze(_this.templates);
1072				}
1073
1074			});
1075
1076		}, 
1077
1078		saveProjectContent: function(pid, content){
1079
1080			if(content === false || content === undefined){
1081				console.warn('No content to save.');
1082				return false;
1083			}
1084
1085			doPostRequest('/dispatch/adminproject/saveInlineEditor', {
1086				'pid': pid,
1087				'content': content
1088			}, function(xmlHttp){
1089
1090				if (this.status != 200) {
1091					alert('couldn\'t save project content to server');
1092					return;
1093				}
1094
1095				var result = parseJSON(this.responseText);
1096
1097				if(result !== false){
1098
1099					if(result.error.length > 0){
1100						alert('Saving failed. The following error' + (result.error.length > 1 ? 's' : '') + ' occurred: ' + result.error.join(', '));
1101					}
1102
1103				}
1104
1105			});
1106
1107		},
1108
1109		projectContentToHTML: function(pid, content){
1110
1111			var result = false;
1112
1113			doPostRequest('/dispatch/project/getRenderedProject', {
1114				'pid': pid,
1115				'uid': Cargo.Model.Site.GetId(),
1116				'content': content
1117			}, function(xmlHttp){
1118
1119				if (this.status != 200) {
1120					alert('Problem during project parsing');
1121					return;
1122				}
1123
1124				var data = parseJSON(this.responseText);
1125
1126				if(data){
1127
1128					if(data.error.length > 0){
1129						alert('Parsing failed. The following error' + (result.error.length > 1 ? 's' : '') + ' occurred: ' + result.error.join(', '));
1130					}
1131
1132					result = data.jdata.project;
1133
1134				}
1135
1136			}, false);
1137
1138			return result;
1139		},
1140
1141		HTMLToProjectContent: function(editor){
1142
1143			var result = false,
1144				self = this;
1145
1146			if(editor !== undefined){
1147
1148				_this.helpers.loadJSON('/_api/v0/site/' + Cargo.Helper.GetUrl() + '/projects?pid=' + editor.cargoData.pid + '&fields=images', function(data){
1149					data = data[0];
1150
1151					var imgRefs = {};
1152
1153					if(data.images !== undefined){
1154
1155						for(var i = 0; i < data.images.length; i++){
1156							imgRefs[data.images[i].id] = data.images[i].image_ref;
1157						}
1158						
1159					}
1160					//_this.helpers.logDocumentFragment(frag);
1161
1162					var frag = editor.getSaveableNodes(),
1163						imgs;
1164
1165					//console.log(frag.cloneNode(true));
1166
1167					/*_this.helpers.getAllDescendants(frag, function(child){
1168
1169						if(child.nodeType === Node.TEXT_NODE){
1170							child.data = self.escapeHTMLInText(child.data);
1171						}
1172
1173					});*/
1174
1175					//console.log(frag.cloneNode(true));
1176
1177					var imgs = frag.querySelectorAll('img[data-mid]');
1178
1179					for(var i = 0; i < imgs.length; i++){
1180						imgs[i].parentNode.insertBefore(document.createTextNode(imgRefs[imgs[i].getAttribute('data-mid')]), imgs[i]);
1181
1182						imgs[i].parentNode.removeChild(imgs[i]);
1183					}
1184
1185					var slideshows = frag.querySelectorAll('.slideshow'),
1186						slideshowContent;
1187
1188					//console.log(frag.cloneNode(true));
1189
1190					for(var i = 0; i < slideshows.length; i++){
1191
1192						slideshowContent = slideshows[i].querySelector('.slideshow_container').innerHTML.replace(/\s{2,}/g, ' ');
1193
1194						console.log(slideshowContent);
1195
1196						slideshows[i].parentNode.insertBefore(document.createTextNode('{slideshow}' +  slideshowContent + '{/slideshow}'), slideshows[i]);
1197						slideshows[i].parentNode.removeChild(slideshows[i]);
1198					}
1199
1200					//console.log(frag.cloneNode(true));
1201
1202					var fullscreenBtns = frag.querySelectorAll('#fullscreen');
1203
1204					for(var i = 0; i < fullscreenBtns.length; i++){
1205						fullscreenBtns[i].parentNode.insertBefore(document.createTextNode('{fullscreen}'), fullscreenBtns[i]);
1206						fullscreenBtns[i].parentNode.removeChild(fullscreenBtns[i]);
1207					}
1208
1209					var audioComponents = frag.querySelectorAll('.audio_component');
1210
1211					for(var i = 0; i < audioComponents.length; i++){
1212
1213						var playplause = audioComponents[i].querySelector('.play_pause'),
1214							rel = (playplause === null ? '' : playplause.getAttribute('rel'));
1215
1216						audioComponents[i].parentNode.insertBefore(document.createTextNode('{audio=' + rel + '}'), audioComponents[i]);
1217						audioComponents[i].parentNode.removeChild(audioComponents[i]);
1218					}
1219
1220					var videoComponents = frag.querySelectorAll('.video_component');
1221
1222					for(var i = 0; i < videoComponents.length; i++){
1223
1224						var object = videoComponents[i].querySelector('object'),
1225							width  = (object === null ? '' : object.getAttribute('width')),
1226							height = (object === null ? '' : object.getAttribute('height')),
1227
1228							posterParam = videoComponents[i].querySelector('param[id="poster"]'),
1229							posterframe = (posterParam === null ? '' : posterParam.getAttribute('value')),
1230
1231							urlParam = videoComponents[i].querySelector('source'),
1232							url = (urlParam === null ? '' : urlParam.getAttribute('src'));
1233
1234						videoComponents[i].parentNode.insertBefore(document.createTextNode('{video=' + url + ' width=' + width + ' height=' + height + ' poster=' + posterframe + '}'), videoComponents[i]);
1235						videoComponents[i].parentNode.removeChild(videoComponents[i]);
1236					}
1237
1238					/*var styles = frag.querySelectorAll('style');
1239
1240					for(var i = 0; i < styles.length; i++){
1241						styles[i].parentNode.removeChild(styles[i]);
1242					}*/
1243
1244					var div = document.createElement('div');
1245					div.appendChild( frag.cloneNode(true) );
1246
1247					//console.log(div.cloneNode(true), div.innerHTML);
1248
1249					// .trim() here is probably a territory we don't want to enter.
1250					result = div.innerHTML.trim();
1251
1252					result = result.replace(/\n/mg, '');
1253					result = result.replace(/<br\s*\/?>/mg, '\n');
1254
1255					div = null;
1256
1257				}, false);
1258
1259			}
1260
1261			//console.log(result);
1262
1263			return result;
1264
1265		},
1266
1267		populateTemplate: function(templateName, context){
1268
1269			var template = _this.templates[templateName];
1270
1271			if(template === undefined) { throw 'No template with the name \'' + templateName + '\''}
1272
1273			// in case there's no variables in the template
1274			if(context === undefined) { context = {}; }
1275
1276			var expressionMatcher = /{{(.+?)}}/g,
1277				match, expressions = [];
1278
1279			// find variables in template
1280			while((match = expressionMatcher.exec(template)) !== null){
1281				if(expressions.indexOf(match[1]) === -1){
1282					expressions.push(match[1]);
1283				}
1284			}
1285
1286			if(expressions.length > 0){
1287
1288				var	undefinedExpressions = [];
1289
1290				// Replace expressions with context
1291				expressions.forEach(function(expression){
1292
1293					if(context.hasOwnProperty(expression.trim())){
1294						template = template.replace(new RegExp('{{'+expression+'}}', 'g'), context[expression]);
1295					} else {
1296						undefinedExpressions.push(expression);
1297					}
1298
1299				});
1300
1301				if(undefinedExpressions.length > 0){
1302					throw 'Context is missing the following fields: ' + undefinedExpressions.join(', ');
1303				}
1304
1305			}
1306
1307			return template;
1308
1309		},
1310
1311		reRenderProject: function(html){
1312
1313			console.warn('Re-render called');
1314
1315			//return false;
1316
1317			var editor = CargoEditor.getActiveEditor();
1318
1319			if(editor === undefined){
1320				return false;
1321			}
1322
1323			var saveableNodes = document.createElement('div'),
1324				previousContent = document.createDocumentFragment();
1325
1326			if(html === undefined){
1327				saveableNodes.appendChild( editor.getSaveableNodes().cloneNode(true) );
1328			} else {
1329				saveableNodes.innerHTML = html;
1330			}
1331
1332			CargoEditor.helpers.getAllDescendants(saveableNodes, function(node){
1333				node.setSaveable(true);
1334			});
1335
1336			Cargo.Model.DisplayOptions.set('disable_project_scroll', true);
1337
1338			// reset slideshow initialized states
1339			var slideshows = saveableNodes.querySelectorAll('[data-slideshow-initialized]');
1340
1341			for(var i = 0; i < slideshows.length; i++){
1342				slideshows[i].removeAttribute('data-slideshow-initialized');
1343			}
1344
1345			if(html === undefined){
1346				html = saveableNodes.innerHTML;
1347			}
1348
1349			/**
1350			 * @todo Make sure this also works when pixelRatio changes during runtime.
1351			 */
1352
1353			/*_this.events.trigger('editor-render-before', editor);
1354
1355			while(Cargo.View.ProjectDetail.el.childNodes.length > 0){
1356				previousContent.appendChild(Cargo.View.ProjectDetail.el.childNodes[0]);
1357			}*/
1358
1359			var originalRenderTarget = Cargo.View.ProjectDetail.el,
1360				tempRenderTarget = document.createElement('div');
1361
1362			Cargo.View.ProjectDetail.setElement(tempRenderTarget);
1363
1364			if(Cargo.Model.Site.GetUseHiRes()){
1365				Cargo.View.ProjectDetail.collection.set('content_2x', saveableNodes.innerHTML);
1366			} else {
1367				Cargo.View.ProjectDetail.collection.set('content', saveableNodes.innerHTML);
1368			}
1369
1370			var newProjectContent = tempRenderTarget.querySelector('[data-ce-model-id="' + editor.cargoData.pid + '"'),
1371				currentProjectContent = originalRenderTarget.querySelector('[data-ce-model-id="' + editor.cargoData.pid + '"');
1372
1373			if(newProjectContent !== null && currentProjectContent !== null){
1374
1375				while(currentProjectContent.childNodes.length > 0){
1376					currentProjectContent.removeChild(currentProjectContent.childNodes[0]);
1377				}
1378
1379				while(newProjectContent.childNodes.length > 0){
1380					currentProjectContent.appendChild(newProjectContent.childNodes[0]);
1381				}
1382
1383			}
1384
1385			Cargo.View.ProjectDetail.setElement(originalRenderTarget);
1386
1387			// generate a mutation summary to let everybody know the content has changed.
1388			editor.generateMutationSummary();
1389
1390			/*var leftTree = previousContent.childNodes[1],
1391				rightTree = Cargo.View.ProjectDetail.el.childNodes[1];
1392
1393			console.log(leftTree, rightTree);
1394
1395			var diff = _this.dd.diff(leftTree, rightTree);
1396
1397			console.log(diff);
1398			_this.dd.apply(previousContent.childNodes[1], diff);
1399			Cargo.Event.trigger('project_load_complete', Cargo.Helper.GetCurrentProjectId());
1400			console.log(previousContent);
1401
1402			Cargo.View.ProjectDetail.el.innerHTML = '';
1403
1404			while(previousContent.childNodes.length > 0){
1405				Cargo.View.ProjectDetail.el.appendChild(previousContent.childNodes[0]);
1406			}
1407
1408			_this.events.trigger('editor-render-complete', editor);*/
1409
1410			saveableNodes = null;
1411			
1412		}
1413
1414	}
1415
1416	//this.dd = new diffDOM();
1417
1418	this.api.fetchTemplates();
1419
1420});
1421
1422CargoEditor.addPlugin('toolbar', function(CargoEditor){
1423
1424	this.addButtons = function(){};
1425
1426	return false;
1427
1428	'use strict'
1429
1430	var _this = this,
1431		toolbarElement = document.createElement('div');
1432
1433	toolbarElement.id = 'ce-toolbar';
1434	document.body.appendChild(toolbarElement);
1435
1436	this.buttons = {};
1437	this.activeButton;
1438
1439	/*
1440	 * Codemirror
1441	 */
1442
1443	this.addButtons = function(buttonsObj){
1444
1445		for(var key in buttonsObj){
1446			_this.addButton(key, buttonsObj[key]);
1447		}
1448
1449	}
1450
1451	this.addButton = function(id, options) {
1452
1453		// add a getState method in case it's not there.
1454		if(!options.hasOwnProperty('getState')){
1455			options.getState = function(){
1456				return false;
1457			}
1458		}
1459
1460		// reference to this plugin
1461		options.plugin = this;
1462
1463		// if no group is specified, just append button to the toolbar
1464		var buttonParent = toolbarElement;
1465
1466		// place the button in a group
1467		if(options.hasOwnProperty('groupName')){
1468
1469			// find the group
1470			buttonParent = toolbarElement.querySelector('[data-group-name=' + options.groupName + ']');
1471
1472			// group is not there, create group.
1473			if(buttonParent === null){
1474
1475				buttonParent = document.createElement('div');
1476				buttonParent.className = 'ce-toolbar-group';
1477				buttonParent.setAttribute('data-group-name', options.groupName);
1478				toolbarElement.appendChild(buttonParent);
1479				
1480			}
1481		}
1482
1483		// create the elements for the button wrapper and the button
1484		var buttonwrapper 	= document.createElement('div'),
1485			button 			= document.createElement('div');
1486
1487		buttonwrapper.className = 'ce-toolbar-button-wrapper';
1488		buttonwrapper.setAttribute('data-button-id', id);
1489
1490		// Check if the config specifies a popup
1491		if(options.hasOwnProperty('popup')) {
1492
1493			options.popup.isActive = false;
1494			
1495			var popup = document.createElement('div');
1496			popup.className = 'ce-toolbar-popup';
1497			popup.innerHTML = options.popup.innerHTML;
1498
1499			popup.addEventListener('mouseenter', function(e){
1500				CargoEditor.helpers.addClass(document.body, 'ce-prevent-scroll')
1501			});
1502
1503			popup.addEventListener('mouseleave', function(e){
1504				CargoEditor.helpers.removeClass(document.body, 'ce-prevent-scroll')
1505			});
1506
1507			buttonwrapper.appendChild(popup);
1508
1509			options.popup.el = popup;
1510		}
1511
1512		button.className = 'ce-toolbar-button ce-' + id;
1513		button.innerHTML = options.innerHTML;
1514
1515		button.addEventListener('mousedown', function(event){
1516			event.preventDefault();
1517		}, false);
1518
1519		options.active = false;
1520
1521		button.addEventListener('click', function(event){
1522
1523			options.onclick.call(options, event);
1524
1525			var alreadyActive = false;
1526
1527			if(_this.activeButton !== undefined){
1528				CargoEditor.helpers.getAllParents(event.target, function(parent){
1529					if(parent === _this.activeButton.el){
1530						alreadyActive = true;
1531					}
1532				}, document.body);
1533			}
1534
1535			_this.closePopup();
1536
1537			if(options.hasOwnProperty('popup') && alreadyActive === false){
1538				CargoEditor.helpers.addClass(options.popup.el, 'active');
1539				CargoEditor.helpers.addClass(options.el, 'active');
1540				options.popup.isActive = true;
1541				_this.activeButton = options;
1542			}
1543
1544		}, false);
1545
1546		buttonwrapper.appendChild(button);
1547		buttonParent.appendChild(buttonwrapper);
1548
1549		options.el = button;
1550
1551		// store reference in the buttons object.
1552		this.buttons[id] = options;
1553
1554		if(typeof options.init === "function"){
1555			options.init.call(options);
1556		}
1557
1558	}
1559
1560	this.checkButtonStates = function(){
1561
1562		if(CargoEditor.getActiveEditor() === undefined){
1563			return false;
1564		}
1565
1566		setTimeout(function(){
1567			for(var key in _this.buttons){
1568				if(_this.buttons[key].getState()) {
1569					CargoEditor.helpers.addClass(_this.buttons[key].el, 'active');
1570				} else {
1571					CargoEditor.helpers.removeClass(_this.buttons[key].el, 'active');
1572				}
1573			}
1574		}, 30);
1575	}
1576
1577	// hide popup on outside click
1578	window.addEventListener('click', function(e){
1579
1580		if(_this.activeButton !== undefined) {
1581			
1582			var toolbarClick = false;
1583
1584			CargoEditor.helpers.getAllParents(e.target, function(node){
1585				if(node === toolbarElement) {
1586					toolbarClick = true;
1587					return;
1588				}
1589			}, document.body);
1590
1591			if(!toolbarClick) {
1592				_this.closePopup();
1593			}
1594
1595		}
1596
1597	}, false);
1598
1599	this.closePopup = function(){
1600
1601		for(var key in _this.buttons){
1602			if(_this.buttons[key].hasOwnProperty('popup')){
1603				CargoEditor.helpers.removeClass(_this.buttons[key].popup.el, 'active');
1604				CargoEditor.helpers.removeClass(_this.buttons[key].el, 'active');
1605				_this.buttons[key].popup.isActive = false;
1606			}
1607		}
1608
1609		_this.activeButton = undefined;
1610		CargoEditor.helpers.removeClass(document.body, 'ce-prevent-scroll');
1611
1612	};
1613
1614	document.addEventListener('keyup', function(e){
1615		if(e.keyCode === 27){
1616			_this.closePopup();
1617		}
1618	});
1619
1620	CargoEditor.events.on('cursor-activity', this.checkButtonStates);
1621	CargoEditor.events.on('editor-slideshow-edit-start', this.checkButtonStates);
1622	CargoEditor.events.on('editor-slideshow-edit-stop', this.checkButtonStates);
1623	//CargoEditor.events.on('editor-click', this.checkButtonStates);
1624	CargoEditor.events.on('editor-content-change', this.checkButtonStates);
1625
1626	toolbarElement.className = "open";
1627	
1628});
1629
1630CargoEditor.plugins.toolbar.templates = {
1631	'images':	'<div class="project-images"> \
1632				 \
1633					<div class="uploader"> \
1634						<div class="uploadbutton" >Upload images</div> \
1635					</div> \
1636					 \
1637					<div class="images"> \
1638					<% if (images && images.length > 0) { %> \
1639						<% _.each( images, function( img ){ %> \
1640							<div class="img-holder" data-id="<%= img.id %>"> \
1641								<div class="imgcrop"> \
1642									<img src="<%= img.thumbnail_url %>" data-imgref="<%= img.image_ref %>" data-rendered="<%- img.rendered_html %>" draggable="true" border="0" width="70" height="70"> \
1643								</div> \
1644								<span class="imgref"><%= img.image_ref %></span> <a href="#" class="delete">×</a> \
1645							</div> \
1646						<% }); %> \
1647					<% } else { %> \
1648						<div class="noimages"> \
1649							You can also drag & drop <br /> images directly onto your site <br /> or into this panel \
1650							<br /><br /> \
1651							<a href="http://support.cargocollective.com/customer/portal/articles/708664-image-formatting-guide" target="_blank">Image Formatting Guide</a> \
1652						</div> \
1653					<% } %> \
1654					</div> \
1655				</div>',
1656
1657		'code':	'<div class="ce-fullscreen-overlay"> </div> \
1658				 <textarea cols="80" rows="30">Loading</textarea> \
1659				 <button type="button" class="save">OK</button> <button type="button" class="cancel">CANCEL</button>'
1660};
1661
1662CargoEditor.plugins.toolbar.addButtons({
1663	
1664	'close': {
1665		onclick: function(){
1666
1667		},
1668		innerHTML: '×',
1669		groupName: 'close'
1670	},
1671
1672	'image': {
1673		init: function(){
1674
1675			CargoEditor.events.on('editor-drop', function(editor, event){
1676
1677				if (event.dataTransfer) {
1678
1679					var data = event.dataTransfer.getData('draggedThumbnail');
1680
1681					// If no data exists for the specified format, the getData method 
1682					// returns null in Internet Explorer, an empty string in Firefox 
1683					// and undefined in Google Chrome and Safari
1684					
1685					if(data !== null && data !== undefined && data !== ''){
1686
1687						event.preventDefault();
1688						
1689						var imgEl = CargoEditor.helpers.stringToHTMLNodes(data)[0];
1690
1691						imgEl.setSaveable(true);
1692						
1693						CargoEditor.helpers.insertNodeAtCursorPosition(event, imgEl);
1694
1695						CargoEditor.plugins.cargo.api.reRenderProject();
1696					}
1697				}
1698			});
1699
1700			/*document.addEventListener('dragenter', function(e){
1701				console.log('ENTER', e);
1702			});
1703
1704
1705			document.addEventListener('dragleave', function(e){
1706				console.log('LEAVE', e);
1707			});*/
1708
1709		},
1710
1711		onclick: function(){
1712			
1713			this.loadImages();
1714
1715		},
1716
1717		loadImages: function(){
1718			var _this = this,
1719				editor = CargoEditor.getActiveEditor();
1720
1721			if(editor !== undefined && typeof Cargo !== "undefined") {
1722				CargoEditor.helpers.loadJSON('/_api/v0/site/' + Cargo.Helper.GetUrl() + '/projects?pid=' + editor.cargoData.pid + '&fields=images', function(data){
1723					data = data[0];
1724
1725					var template = _.template(_this.plugin.templates.images, data);
1726
1727					_this.popup.el.innerHTML = template;
1728
1729					var items = _this.popup.el.querySelectorAll('.img-holder'),
1730						img, deleteButton, i;
1731
1732					for( i = 0; i < items.length; i++ ){
1733						img = items[i].querySelector('.imgcrop img'),
1734						deleteButton = items[i].querySelector('.delete');
1735
1736						if(img !== null){
1737							img.addEventListener('click', function(){
1738								
1739								CargoEditor.execCommand('inserthtml', false, this.getAttribute('data-rendered'));
1740								CargoEditor.plugins.cargo.api.reRenderProject();
1741
1742							}, false);
1743
1744							img.addEventListener('dragstart', function(event){
1745								event.dataTransfer.setData('draggedThumbnail', this.getAttribute('data-rendered'));
1746								event.dataTransfer.dropEffect = "move";
1747								CargoEditor.helpers.addClass(_this.popup.el, 'whiledragging');
1748								console.log('dragging img', this.getAttribute('data-rendered'));
1749							}, false);
1750
1751							img.addEventListener('dragend', function(event){
1752								CargoEditor.helpers.removeClass(_this.popup.el, 'whiledragging');
1753							}, false);
1754						}
1755					}
1756
1757				});
1758			}
1759		},
1760
1761		getState: function(){
1762
1763			return false;
1764
1765		},
1766		popup: {
1767			innerHTML: '<div class="project-images"></div>'
1768		},
1769		innerHTML: '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="24px" height="18px" viewBox="0 0 24 18" enable-background="new 0 0 24 18" xml:space="preserve"><g><circle fill="#fff" cx="14.184" cy="5.553" r="1.5"/><path fill="#fff" d="M1,1v15h21V1H1z M21,2v12.734l-4.074-5.191l-3.266,4.161L8.125,6.65L2,14.455V2H21z"/></g></svg>',
1770		groupName: 'editing'
1771	},
1772
1773	'slideshow': {
1774		init: function(){
1775
1776			_this = this;
1777
1778			_this.editingSlideshow = false;
1779
1780			CargoEditor.events.on('editor-slideshow-edit-start', function(){
1781				_this.editingSlideshow = true;
1782			});
1783
1784			CargoEditor.events.on('editor-slideshow-edit-stop', function(){
1785				_this.editingSlideshow = false;
1786			});
1787		},
1788		onclick: function(){
1789
1790			var range = CargoEditor.getActiveRange(),
1791				editor = CargoEditor.getActiveEditor();
1792
1793			if(range !== null){
1794				var target = range.startContainer.childNodes[range.startOffset];
1795
1796				if(target !== undefined){
1797					if(target.nodeName === "IMG"){
1798						
1799						var tempContainer = document.createElement('div');
1800
1801						tempContainer.innerHTML = CargoEditor.plugins.cargo.templates.slideshow_header + CargoEditor.plugins.cargo.templates.slideshow_footer;
1802
1803						var slideshow = tempContainer.childNodes[1];
1804						slideshow.setAttribute('data-id', editor.cargoData.pid);
1805
1806						target.parentNode.insertBefore(slideshow, target);
1807
1808						slideshow.querySelector('.slideshow_container').appendChild(target);
1809
1810						Cargo.Core.Slideshow.InitSlideshow();
1811
1812					}
1813				}
1814			}
1815
1816		},
1817		getState: function(){
1818
1819			/*var range = CargoEditor.getActiveRange();
1820
1821			if(range !== null){
1822				var target = range.startContainer.childNodes[range.startOffset];
1823
1824				if(target !== undefined){
1825					if(target.nodeName === "IMG"){
1826						return true;
1827					}
1828				}
1829			}*/
1830
1831			return _this.editingSlideshow;
1832
1833		},
1834		innerHTML: '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="20px" height="15px" viewBox="0 0 20 15" enable-background="new 0 0 20 15" xml:space="preserve"><g><path fill="#fff" d="M18,4V2h-2V0H0v11h2v2h2v2h16V4H18z M19,5v8.935l-3.106-3.958l-2.407,3.068L9.404,7.843L5,13.455v-0.58V5H19z M2,10H1V1h14v1H2V10z M4,12H3V3h14v1H4V12z"/><circle fill="#fff" cx="13.766" cy="7.928" r="1"/></g></svg>',
1835		groupName: 'editing'
1836	},
1837
1838	'bold': {
1839		onclick: function(){
1840
1841			CargoEditor.execCommand('bold');
1842
1843		},
1844		getState: function(){
1845
1846			return CargoEditor.queryCommandState('bold');
1847
1848		},
1849		innerHTML: '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="20px" height="15px" viewBox="0 0 20 15" enable-background="new 0 0 20 15" xml:space="preserve"><path fill="#fff" d="M5.2578,2.5h6.0234c0.9648,0,1.7051,0.2373,2.2227,0.7158c0.5137,0.4775,0.7734,1.0684,0.7734,1.7725c0,0.5908-0.1855,1.0996-0.5527,1.5215c-0.2461,0.2822-0.6055,0.5049-1.0781,0.667c0.7188,0.1738,1.248,0.4707,1.5859,0.8916c0.3379,0.4199,0.5098,0.9492,0.5098,1.5859c0,0.5195-0.1211,0.9854-0.3633,1.3994c-0.2402,0.4121-0.5703,0.7402-0.9883,0.9805c-0.2598,0.1494-0.6494,0.2598-1.1738,0.3271C11.5215,12.4521,11.0615,12.5,10.832,12.5H5.2578V2.5z M8.375,6.4219h1.3438c0.4824,0,1.0596-0.083,1.2461-0.248c0.1895-0.166,0.2832-0.4062,0.2832-0.7217c0-0.291-0.0938-0.5186-0.2832-0.6816c-0.1875-0.1641-0.5176-0.2461-0.9844-0.2461H8.376v1.8975H8.375z M8.375,10.3506h1.8164c0.5332,0,0.9062-0.0938,1.125-0.2832c0.2188-0.1875,0.3252-0.4404,0.3252-0.7607c0-0.2949-0.1064-0.5332-0.3213-0.7129c-0.2168-0.1777-0.5957-0.2686-1.1367-0.2686H8.374v2.0254H8.375z"/></svg>',
1850		groupName: 'editing'
1851	},
1852
1853	'italic': {
1854		onclick: function(){
1855
1856			CargoEditor.execCommand('italic');
1857
1858		},
1859		getState: function(){
1860
1861			return CargoEditor.queryCommandState('italic');
1862
1863		},
1864		innerHTML: '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="20px" height="15px" viewBox="0 0 20 15" enable-background="new 0 0 20 15" xml:space="preserve"><path fill="#fff" d="M11.2485,12.0176L11.0356,12.5H5.2983l0.2139-0.4824h0.2285c0.3047,0,0.9736-0.0557,1.1963-0.166c0.2236-0.1084,0.3799-0.2363,0.4736-0.3828c0.0898-0.1445,0.1592-0.3193,0.3584-0.8115l2.5928-6.3643c0.2051-0.4912,0.2617-0.6729,0.2715-0.7959c0.0371-0.3545-0.6406-0.4902-1.2734-0.4902H9.0942L9.2847,2.5h5.417l-0.209,0.5156h-0.207c-0.2344,0-0.9199-0.0078-1.1191,0.0752c-0.2002,0.083-0.3496,0.2002-0.4453,0.3467c-0.0967,0.1484-0.2168,0.4023-0.4688,1.0186l-2.5098,6.127c-0.1904,0.4668-0.2188,0.6191-0.2314,0.7422c-0.0244,0.2393,0.0615,0.417,0.2578,0.5254c0.1973,0.1104,0.9209,0.166,1.1934,0.166h0.2861V12.0176z"/></svg>',
1865		groupName: 'editing'
1866	},
1867
1868	'link': {
1869		onclick: function(){
1870
1871			if(this.getState() === false) {
1872				// no link yet.
1873				CargoEditor.execCommand('createlink', false, prompt("link location","http://"));
1874			} else {
1875				// selected text is link. Remove.
1876				if(confirm('Are you sure you want to remove this link?')){
1877					CargoEditor.execCommand('unlink');
1878				}
1879			}
1880
1881		},
1882		getState: function(){
1883			
1884			var result = false,
1885				range = CargoEditor.getActiveRange();
1886
1887			if(range !== null) {
1888				var container = range.commonAncestorContainer;
1889
1890				while(container !== document.body && result === false) {
1891
1892					if(container.nodeName === "A") {
1893						result = true;
1894					}
1895
1896					container = container.parentNode;
1897				}
1898			}
1899
1900			return result;
1901
1902		},
1903		innerHTML: '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="22px" height="15px" viewBox="0 0 22 15" enable-background="new 0 0 22 15" xml:space="preserve"><g><g><path fill="#fff" d="M8.5181,11.5H3.9995C1.7944,11.5,0,9.7051,0,7.5s1.7944-4,3.9995-4h4.5186c0.5039,0,0.9116,0.4072,0.9116,0.9116c0,0.5024-0.4077,0.9106-0.9116,0.9106H3.9995c-1.1997,0-2.1777,0.9766-2.1777,2.1777c0,1.1992,0.978,2.1768,2.1777,2.1768h4.5186c0.5039,0,0.9116,0.4082,0.9116,0.9106C9.4297,11.0918,9.022,11.5,8.5181,11.5z"/><path fill="#fff" d="M18.0024,11.5h-4.5176c-0.5039,0-0.9116-0.4082-0.9116-0.9126c0-0.5024,0.4077-0.9106,0.9116-0.9106h4.5176c1.1997,0,2.1748-0.9775,2.1748-2.1768c0-1.2012-0.9751-2.1777-2.1748-2.1777h-4.5176c-0.5039,0-0.9116-0.4082-0.9116-0.9106c0-0.5044,0.4077-0.9116,0.9116-0.9116h4.5176C20.2065,3.5,22,5.2949,22,7.5S20.2065,11.5,18.0024,11.5z"/></g><path fill="#fff" d="M14.8047,8.4121H7.1992c-0.5044,0-0.9111-0.4082-0.9111-0.9121c0-0.5029,0.4067-0.9121,0.9111-0.9121h7.6055c0.5034,0,0.9121,0.4092,0.9121,0.9121C15.7168,8.0039,15.3081,8.4121,14.8047,8.4121z"/></g></svg>',
1904		groupName: 'editing'
1905	},
1906
1907	'fullscreen': {
1908		onclick: function(){
1909
1910			CargoEditor.execCommand('inserthtml', false, CargoEditor.plugins.cargo.api.populateTemplate('fullscreen'));
1911
1912		},
1913		getState: function(){
1914
1915			return false;
1916		
1917		},
1918		innerHTML: '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="20px" height="15px" viewBox="0 0 20 15" enable-background="new 0 0 20 15" xml:space="preserve"><g><polygon fill="#fff" points="16,1.4995 10.1118,1.4995 12.2954,3.6851 9.9136,6.064 11.437,7.5874 13.8184,5.2065 16,7.3901 "/><polygon fill="#fff" points="8.564,7.4136 6.1821,9.7944 3.9995,7.6099 3.9995,13.5005 9.8911,13.5005 7.7065,11.3159 10.0864,8.9351 "/></g></svg>',
1919		groupName: 'editing'
1920	},
1921
1922	'audio': {
1923		onclick: function(){
1924
1925			var editor = CargoEditor.getActiveEditor();
1926
1927			CargoEditor.execCommand('inserthtml', false, 
1928				CargoEditor.plugins.cargo.api.populateTemplate('audio', {
1929					pid: editor.cargoData.pid,
1930					audio_url: window.prompt("Enter URL to external MP3 file:","http://example.com/example.mp3")
1931				})
1932			);
1933			
1934			CargoEditor.plugins.cargo.api.reRenderProject();
1935
1936			Cargo.Core.Audio.CheckForAudio();
1937 
1938		},
1939		getState: function(){
1940
1941			return false;
1942		
1943		},
1944		innerHTML: '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="20px" height="15px" viewBox="0 0 20 15" enable-background="new 0 0 20 15" xml:space="preserve"><g><polygon fill="#fff" points="9.8774,1.5 5.9365,5.5 2.9995,5.5 2.9995,9.5 5.9365,9.5 9.8774,13.5 "/><path fill="#fff" d="M13.4922,12.8496c-0.2568,0-0.5054-0.1406-0.6348-0.3877c-0.1846-0.3535-0.0537-0.7959,0.2969-0.9854c1.4893-0.8027,2.4121-2.3252,2.4121-3.9775c0-1.6494-0.9229-3.1738-2.4121-3.9775c-0.3481-0.1875-0.48-0.6299-0.2969-0.9844c0.186-0.3545,0.6216-0.4902,0.9702-0.3008c1.9556,1.0557,3.1719,3.0713,3.1719,5.2627s-1.2129,4.208-3.1719,5.2646C13.7202,12.8203,13.6055,12.8496,13.4922,12.8496z"/><path fill="#fff" d="M11.7192,10.5c-0.2603,0-0.5083-0.1406-0.6353-0.3867c-0.1841-0.3555-0.0518-0.7969,0.2998-0.9863c0.6099-0.3281,0.9917-0.9531,0.9917-1.627c0-0.6748-0.3818-1.2979-0.9917-1.6279c-0.3506-0.1895-0.4829-0.6309-0.2979-0.9854c0.1841-0.3545,0.6196-0.4902,0.9692-0.3018c1.082,0.582,1.7534,1.6992,1.7534,2.915c0,1.2148-0.6743,2.333-1.7534,2.917C11.9458,10.4727,11.8325,10.5,11.7192,10.5z"/></g></svg>',
1945		groupName: 'editing'
1946	},
1947
1948	'video': {
1949		onclick: function(){
1950
1951			CargoEditor.execCommand('inserthtml', false, 
1952				CargoEditor.plugins.cargo.api.populateTemplate('video', {
1953					image: 'https://cargocollective.com/example/posterframe.jpg',
1954					video_url: window.prompt('Enter URL to external Video file: (QT H.264 or FLV)', 'http://example.com/example.mov')
1955				})
1956			);
1957
1958			CargoEditor.plugins.cargo.api.reRenderProject();
1959
1960		},
1961		getState: function(){
1962
1963			return false;
1964		
1965		},
1966		innerHTML: '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="24px" height="18px" viewBox="0 0 24 18" enable-background="new 0 0 24 18" xml:space="preserve"><path fill="#fff" d="M3.5,3v12h17V3H3.5z M16.5,4h1v1h-1V4z M14.5,4h1v1h-1V4z M12.5,4h1v1h-1V4z M8.5,4h1v1h-1V4z M6.5,4h1v1h-1V4z M4.5,4h1v1h-1V4z M5.5,14h-1v-1h1V14z M7.5,14h-1v-1h1V14z M9.5,14h-1v-1h1V14z M11.5,14h-1v-1h1V14z M11.5,12h-7V6h7V12z M11.5,5h-1V4h1V5z M13.5,14h-1v-1h1V14z M15.5,14h-1v-1h1V14z M17.5,14h-1v-1h1V14z M19.5,14h-1v-1h1V14z M19.5,12h-7V6h7V12z M18.5,5V4h1v1H18.5z"/></svg>',
1967		groupName: 'editing'
1968	},
1969
1970	'code': {
1971		onclick: function(){
1972
1973			var _this = this;
1974
1975			if(!this.popup.isActive) {
1976				var editor = CargoEditor.getActiveEditor();
1977
1978				if(editor !== undefined){
1979
1980					var txt = CargoEditor.plugins.cargo.api.HTMLToProjectContent(editor);
1981					_this.codemirror.getDoc().setValue(txt);
1982
1983				} else {
1984					_this.codemirror.getDoc().setValue('No editor selected.');
1985				}
1986
1987				setTimeout(function(){
1988					_this.codemirror.clearHistory();
1989					_this.codemirror.refresh();
1990				}, 1);
1991			}
1992
1993		},
1994		init: function(){
1995			var script = document.createElement( 'script' ),
1996				self = this;
1997
1998			this.popup.el.innerHTML = this.plugin.templates.code;
1999			
2000			script.onload = function(){
2001
2002				var ch;
2003
2004				console.log('load');
2005				
2006				// Add some Cargo salt to the htmlmixed mode.
2007				
2008				CodeMirror.defineMode("cargohtmlmixed", function(config, parserConfig) {
2009					
2010					var mustacheOverlay = {
2011						token: function(stream, state) {
2012							if (stream.match("{")) {
2013								while ((ch = stream.next()) != null){
2014									if (ch == "}") break;
2015								}
2016								return "cargotag";
2017							}
2018							while (stream.next() != null && !stream.match("{", false)) {}
2019							return null;
2020						}
2021					};
2022
2023					return CodeMirror.overlayMode(CodeMirror.getMode(config, parserConfig.backdrop || "text/html"), mustacheOverlay);
2024
2025				});
2026
2027
2028				self.codemirror = CodeMirror.fromTextArea(self.popup.el.querySelector('textArea'), {
2029					mode: "cargohtmlmixed",
2030					lineNumbers: true,
2031					lineWrapping: true
2032				});
2033
2034				self.codemirror.setSize(400, 400);
2035			};
2036
2037			script.src = '/_jsapps/inline-editor/plugins/toolbar/libs/codemirror-compressed.js';
2038			document.body.appendChild(script);
2039
2040			/*var style = document.createElement( 'link' );
2041			style.href = '/_jsapps/inline-editor/plugins/toolbar/libs/codemirror.css';
2042			style.type = 'text/css';
2043			style.rel = 'stylesheet';
2044			document.head.appendChild(style);*/
2045
2046			self.popup.el.querySelector('.save').addEventListener('click', function(){
2047
2048				var editor = CargoEditor.getActiveEditor();
2049
2050				if(editor === undefined){
2051					return false;
2052				}
2053
2054				var html = CargoEditor.plugins.cargo.api.projectContentToHTML(editor.cargoData.pid, self.codemirror.getDoc().getValue());
2055
2056				CargoEditor.plugins.cargo.api.reRenderProject(html);
2057
2058				CargoEditor.plugins.toolbar.closePopup();
2059				
2060			});
2061
2062			self.popup.el.querySelector('.cancel').addEventListener('click', function(){
2063				CargoEditor.plugins.toolbar.closePopup();
2064			});
2065		},
2066		getState: function(){
2067
2068			return false;
2069		
2070		},
2071		popup: {
2072			innerHTML: ''
2073		},
2074		innerHTML: '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="41px" height="19px" viewBox="0 0 41 19" enable-background="new 0 0 41 19" xml:space="preserve"><g><path fill="#AAA" d="M9.731,13.129c-1.236,0-2.19-0.363-2.863-1.088c-0.673-0.726-1.01-1.752-1.01-3.08c0-1.325,0.343-2.354,1.028-3.084s1.651-1.096,2.897-1.096c0.713,0,1.547,0.117,2.504,0.35v1.059c-1.089-0.377-1.929-0.564-2.52-0.564c-0.863,0-1.531,0.292-2.004,0.875C7.292,7.084,7.056,7.908,7.056,8.971c0,1.014,0.253,1.813,0.758,2.398c0.505,0.586,1.194,0.879,2.068,0.879c0.752,0,1.557-0.231,2.416-0.693v0.967C11.515,12.926,10.658,13.129,9.731,13.129z"/><path fill="#AAA" d="M17.138,13.129c-1.103,0-1.989-0.383-2.661-1.147c-0.671-0.765-1.007-1.774-1.007-3.032c0-1.264,0.338-2.274,1.013-3.031c0.675-0.758,1.576-1.137,2.704-1.137c1.124,0,2.025,0.378,2.701,1.134c0.678,0.756,1.016,1.764,1.016,3.024c0,1.285-0.338,2.305-1.016,3.059C19.212,12.752,18.295,13.129,17.138,13.129z M17.154,12.285c0.812,0,1.441-0.293,1.886-0.878c0.443-0.586,0.666-1.412,0.666-2.479c0-1.034-0.224-1.844-0.669-2.428c-0.446-0.583-1.062-0.875-1.851-0.875c-0.791,0-1.409,0.293-1.854,0.878c-0.443,0.586-0.666,1.399-0.666,2.441c0,1.038,0.221,1.854,0.661,2.449S16.377,12.285,17.154,12.285z"/><path fill="#AAA" d="M22.481,12.93V4.98h2.643c0.788,0,1.425,0.088,1.91,0.264c0.485,0.175,0.904,0.473,1.26,0.891c0.562,0.666,0.843,1.544,0.843,2.633c0,1.32-0.349,2.345-1.048,3.072c-0.697,0.727-1.681,1.09-2.948,1.09H22.481z M23.61,12.086h1.444c1.031,0,1.762-0.277,2.191-0.832c0.462-0.591,0.692-1.384,0.692-2.379c0-0.936-0.227-1.669-0.682-2.203c-0.275-0.325-0.605-0.548-0.988-0.668s-0.96-0.18-1.729-0.18H23.61V12.086z"/><path fill="#AAA" d="M30.721,12.93V4.98h4.442v0.844H31.85v2.551h2.776v0.832H31.85v2.879h3.545v0.844H30.721z"/></g><path fill="#AAA" d="M0,0v19h41V0H0z M40,18H1V1h39V18z"/></svg>',
2075		groupName: 'editing'
2076	},
2077
2078	'save': {
2079		onclick: function(){
2080			var editor = CargoEditor.getActiveEditor();
2081
2082			CargoEditor.plugins.cargo.api.saveProjectContent(
2083				editor.cargoData.pid, 
2084				CargoEditor.plugins.cargo.api.HTMLToProjectContent(editor)
2085			);
2086
2087		},
2088		getState: function(){
2089
2090			return false;
2091		
2092		},
2093		innerHTML: '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="41px" height="19px" viewBox="0 0 41 19" enable-background="new 0 0 41 19" xml:space="preserve"><path fill="#666666" d="M0,0v19h41V0H0z"/><g><path d="M6.946,13.342v-1.363c0.855,0.357,1.588,0.537,2.196,0.537c0.427,0,0.763-0.096,1.008-0.285s0.367-0.447,0.367-0.773c0-0.258-0.076-0.476-0.23-0.652c-0.154-0.178-0.445-0.388-0.875-0.631L8.879,9.878C8.167,9.477,7.664,9.093,7.371,8.726C7.077,8.358,6.929,7.93,6.929,7.439c0-0.655,0.241-1.182,0.723-1.579s1.118-0.597,1.91-0.597c0.633,0,1.348,0.092,2.143,0.274v1.272c-0.812-0.304-1.455-0.456-1.928-0.456c-0.365,0-0.658,0.081-0.879,0.244c-0.22,0.163-0.33,0.377-0.33,0.642c0,0.219,0.077,0.41,0.23,0.575C8.953,7.98,9.243,8.183,9.669,8.422l0.568,0.317c0.767,0.43,1.295,0.825,1.585,1.187c0.29,0.362,0.435,0.803,0.435,1.322c0,0.737-0.273,1.315-0.821,1.734s-1.304,0.629-2.267,0.629C8.532,13.611,7.791,13.521,6.946,13.342z"/><path d="M12.805,13.412l3.094-7.949h1.654l3.094,7.949H18.95l-0.826-2.121h-3.287l-0.828,2.121H12.805z M15.245,10.232h2.471l-1.236-3.169L15.245,10.232z"/><path d="M23.854,13.412l-2.937-7.949h1.767l2.277,6.15l2.201-6.15h1.193l-2.842,7.949H23.854z"/><path d="M29.463,13.412V5.463h4.967v1.09h-3.312v2.219h2.717v1.063h-2.717v2.449h3.566v1.128H29.463z"/></g></svg>',
2094		groupName: 'editing'
2095	},
2096
2097	'menu': {
2098		onclick: function(){
2099
2100		},
2101		getState: function(){
2102
2103			return false;
2104		
2105		},
2106		popup: {
2107			innerHTML:	'<div><a href="#">Manage Project</a></div>' +
2108						'<div><a href="#">New Project</a></div>' +
2109						'<div><a href="#">New Page</a></div>' +
2110						'<div><a href="#">New Link</a></div>' +
2111						'<div><a href="#">Project list</a></div>' +
2112						'<div><a href="#">Site Settings</a></div>' 
2113		},
2114		innerHTML: '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="20px" height="15px" viewBox="0 0 20 15" enable-background="new 0 0 20 15" xml:space="preserve"><g><path fill="#FFF" d="M8.8574,9.7861v1.3125c0,0.6597-0.2383,1.2236-0.7139,1.6948C7.667,13.2646,7.0938,13.5,6.4248,13.5S5.1846,13.2637,4.71,12.79C4.2378,12.3164,4,11.7422,4,11.0684c0-0.6597,0.2378-1.228,0.71-1.7085c0.4746-0.478,1.0381-0.7173,1.6914-0.7173h1.3125V6.3574H6.4014c-0.6533,0-1.2168-0.2393-1.6914-0.7173C4.2378,5.1597,4,4.5884,4,3.9248C4,3.2544,4.2378,2.6836,4.71,2.21C5.1846,1.7363,5.7559,1.5,6.4248,1.5S7.667,1.7354,8.1436,2.2065c0.4756,0.4712,0.7139,1.0352,0.7139,1.6948v1.3125h2.2861V3.9014c0-0.6597,0.2373-1.2236,0.7129-1.6948C12.333,1.7354,12.9062,1.5,13.5762,1.5c0.6738,0,1.2461,0.2363,1.7188,0.71C15.7646,2.6836,16,3.2544,16,3.9248c0,0.6636-0.2354,1.2349-0.7051,1.7153c-0.4727,0.478-1.0366,0.7173-1.6963,0.7173h-1.3125v2.2852h1.3125c0.6597,0,1.2236,0.2393,1.6963,0.7173C15.7646,9.8403,16,10.4087,16,11.0684c0,0.6738-0.2354,1.248-0.7051,1.7217c-0.4727,0.4736-1.0449,0.71-1.7188,0.71c-0.6699,0-1.2432-0.2354-1.7197-0.7065c-0.4756-0.4712-0.7129-1.0352-0.7129-1.6948V9.7861H8.8574z M7.7139,5.2139V3.9248c0-0.355-0.1255-0.6577-0.3779-0.9072s-0.5562-0.375-0.9111-0.375c-0.3501,0-0.6509,0.1255-0.9033,0.375S5.1426,3.5698,5.1426,3.9248S5.269,4.5835,5.5215,4.8359s0.5562,0.3779,0.9111,0.3779H7.7139z M7.7139,9.7861H6.4326c-0.355,0-0.6587,0.1255-0.9111,0.3779s-0.3789,0.5532-0.3789,0.9043c0,0.355,0.1265,0.6587,0.3789,0.9116c0.2524,0.251,0.5562,0.3774,0.9111,0.3774c0.3501,0,0.6509-0.1265,0.9033-0.3774c0.2524-0.2529,0.3779-0.5547,0.3779-0.9048V9.7861z M8.8574,8.6426h2.2861V6.3574H8.8574V8.6426z M12.2861,5.2139h1.29c0.3501,0,0.6509-0.1255,0.9038-0.3779c0.251-0.2524,0.3774-0.5562,0.3774-0.9111s-0.1255-0.6577-0.375-0.9072s-0.5513-0.375-0.9062-0.375c-0.356,0-0.6597,0.1255-0.9111,0.375c-0.2534,0.2495-0.3789,0.5522-0.3789,0.9072
2114V5.2139z M12.2861,9.7861v1.2891c0,0.3501,0.1255,0.6519,0.3789,0.9048c0.2515,0.251,0.5552,0.3774,0.9111,0.3774c0.355,0,0.6567-0.1265,0.9062-0.3774c0.2495-0.2529,0.375-0.5566,0.375-0.9116c0-0.3511-0.1265-0.6519-0.3774-0.9043c-0.2529-0.2524-0.5537-0.3779-0.9038-0.3779H12.2861z"/></g></svg>',
2115		groupName: 'menu'
2116	},
2117
2118	'css': {
2119		onclick: function(){
2120
2121		},
2122		getState: function(){
2123
2124			return false;
2125		
2126		},
2127		innerHTML: 'CSS',
2128		groupName: 'external'
2129	},
2130
2131	'html': {
2132		onclick: function(){
2133
2134		},
2135		getState: function(){
2136
2137			return false;
2138		
2139		},
2140		innerHTML: 'HTML',
2141		groupName: 'external'
2142	}
2143
2144});
2145
2146CargoEditor.addPlugin('UIOverlay', function(CargoEditor){
2147
2148	'use strict'
2149
2150	var _this = this,
2151		UILayer = document.createElement('div');
2152
2153	UILayer.className = 'ce-ui-layer'
2154
2155	document.body.appendChild(UILayer);
2156
2157	CargoEditor.events.on('editor-click', function(editor, event){
2158
2159		
2160		if(event.target.nodeName === "IMG"){
2161			CargoEditor.helpers.selectNode(event.target);
2162		}
2163
2164		if(!isUnEditable(event.target)) {
2165			CargoEditor.helpers.getAllParents(event.target, function(parent){
2166				if(isUnEditable(parent)){
2167					//enableUneditableUI(parent);
2168					CargoEditor.helpers.selectNode(parent);
2169					event.preventDefault();
2170					event.stopImmediatePropagation();
2171				}
2172			}, event.target.ownerDocument.body);
2173		} else {
2174			//enableUneditableUI(event.target);
2175			CargoEditor.helpers.selectNode(event.target);
2176		}
2177
2178	});
2179
2180	function isUnEditable(el){
2181		if(el !== undefined){
2182			if(el.getAttribute('contenteditable') === 'false'){
2183				return true;
2184			}
2185		}
2186
2187		return false;
2188	}
2189
2190	function enableUneditableUI(el){
2191		console.log('This click is in an uneditable element', el);
2192
2193
2194	}
2195
2196});
2197
2198CargoEditor.addPlugin('classApplier', function(CargoEditor){
2199
2200	'use strict'
2201
2202	var _this = this;
2203
2204	var classAppliers = {};
2205
2206	CargoEditor.setCommand('applyClass', {
2207		action: function(className) {
2208
2209			if(className === undefined){
2210				return false;
2211			}
2212
2213			if( ! classAppliers.hasOwnProperty(className) ){
2214				classAppliers[className] = CargoEditor.rangy.createClassApplier(className, {
2215					ignoreWhiteSpace: true,
2216					applyToEditableOnly: true,
2217					normalize: false
2218				}); 
2219			}
2220
2221			classAppliers[className].toggleSelection();
2222			
2223			return true;
2224
2225		}
2226	});
2227
2228
2229});
2230
2231CargoEditor.addPlugin('copypaste', function(CargoEditor){
2232
2233	/**
2234	 * Prevents the default copy-paste action from happening.
2235	 */
2236
2237	'use strict'
2238
2239	var _this = this;
2240
2241
2242	_this.events.on('editor-paste', function(editor, event) {
2243
2244		event.preventDefault();
2245
2246		/*
2247		console.log(event.clipboardData.files.length);
2248		console.log(event.clipboardData.items);
2249		console.log(event.clipboardData.types);
2250
2251		if(event.clipboardData.items !== undefined){
2252			[].forEach.call(event.clipboardData.items, function(item){
2253				console.log(item);
2254			});
2255		}
2256
2257		if(event.clipboardData.types !== undefined){
2258			[].forEach.call(event.clipboardData.types, function(type){
2259				console.log(type, ':', event.clipboardData.getData(type));
2260			});
2261		}
2262		*/
2263	
2264		// Get the text, but parse it for URLs first
2265		var textData = parseAndConvertUrls( event.clipboardData.getData('text/plain') );
2266
2267		// If no data exists for the specified format, the getData method 
2268		// returns null in Internet Explorer, an empty string in Firefox 
2269		// and undefined in Google Chrome and Safari
2270		
2271		if(textData !== null && textData !== undefined && textData !== ''){
2272			// there's text to be pasted and no to time to be wasted.
2273			
2274			var selection = window.getSelection();
2275
2276			if(selection.rangeCount > 0){
2277				var range = selection.getRangeAt(0);
2278
2279				// breaks
2280				textData = textData.replace(/\n/g, '<br />');
2281
2282				// tabs
2283				textData = textData.replace(/\t/g, '&nbsp;&nbsp;&nbsp;&nbsp;');
2284
2285				var nodes = _this.helpers.stringToHTMLNodes(textData);
2286
2287				CargoEditor.mutationManager.execute(function(){
2288
2289					// silently delete selection as it's not supposed to have it's own
2290					// summary callback
2291					if( ! range.collapsed ) {
2292						CargoEditor.execCommandSilent('delete');
2293						range.collapse();
2294					}
2295
2296					[].forEach.call(nodes, function(node){
2297						range.insertNode(node);
2298					});
2299
2300				});
2301
2302				_this.events.trigger('cursor-activity', editor);
2303				
2304			}
2305			
2306		}
2307
2308	});
2309	
2310	/**
2311	 * Parse and convert URLs to wrap them in a link tag
2312	 * This will wrap both URLs and Email addresses in an appropriate link
2313	 * and return back the result
2314	 * @param  {String} input The paste content
2315	 * @return {String} 
2316	 */
2317	var parseAndConvertUrls = function(plain) {
2318		var reg = new RegExp(/(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&\/\/=]*)/gi);
2319		var rendered = plain;
2320
2321		if(reg.test(plain)) {
2322			plain.replace(reg, function(match, text, urlId){
2323				if(/\@/.test(match)) {
2324					var link_string = '<a href=mailto:'+match+'>'+match+'</a>';
2325				} else {
2326					var link_url = match;
2327					if(!/(http(s)?:\/\/)/.test(match)) {
2328						link_url = 'http://'+link_url;
2329					}
2330					var link_string = '<a href='+link_url+' target="_blank">'+match+'</a>';
2331				}
2332				rendered = rendered.replace(match, link_string);
2333			});
2334		}
2335
2336		return rendered;
2337	}
2338
2339});
2340
2341CargoEditor.addPlugin('highlighter', function(CargoEditor) {
2342
2343	'use strict'
2344
2345	var selectors = {},
2346		activeListeners = {},
2347		hoverlay, hoverlayTether;
2348	
2349	this.events.on('tether-mouseleave', function(editor, event){
2350		removeOverlay();
2351	});
2352
2353	var enableOverlay = function(events){
2354
2355		removeOverlay();
2356
2357		hoverlay = document.createElement('div');
2358		hoverlay.className = 'ce-hoverlay';
2359
2360		var eventType, newEvents = {};
2361
2362		/**
2363		 * Hijack the event so we can add the tether Object as parameter to the callback. This 
2364		 * allows us to access both the tethered element and the target element.
2365		 */
2366		
2367		for(eventType in events){
2368
2369			var originalCallback = events[eventType];
2370
2371			newEvents[eventType] = function(e){
2372				originalCallback.call(this, e, hoverlayTether);
2373			}
2374			
2375		}
2376		
2377		hoverlayTether = CargoEditor.plugins.tether.addTether(hoverlay, this, {
2378			ignoreEvents: false,
2379			events: newEvents,
2380			coverParent: true,
2381			padding: 20
2382		});
2383	};
2384
2385	var removeOverlay = function(){
2386
2387		if(hoverlayTether !== undefined){
2388			hoverlayTether.destroy();
2389			hoverlayTether = undefined;
2390		}
2391
2392		if(hoverlay !== undefined && hoverlay !== null){
2393			if(hoverlay.parentNode !== null){
2394				hoverlay.parentNode.removeChild(hoverlay);
2395			}
2396		}
2397
2398	}
2399
2400	var addEvents = function(editor){
2401
2402		removeEvents();
2403
2404		var selector, elements, event;
2405
2406		for(selector in selectors){
2407
2408			elements = editor.getElement().querySelectorAll(selector);
2409
2410			activeListeners[selector] = {};
2411			activeListeners[selector].elements = elements;
2412			activeListeners[selector].events = {};
2413
2414			(function(overlayEvents){
2415
2416				activeListeners[selector].events.mouseenter = function(){
2417					enableOverlay.call(this, overlayEvents);
2418				};
2419
2420				activeListeners[selector].events.click = function(){
2421					enableOverlay.call(this, overlayEvents);
2422				};
2423
2424			})(selectors[selector]);
2425			
2426
2427			[].forEach.call(elements, function(element){
2428
2429				for(event in activeListeners[selector].events){
2430					element.addEventListener(event, activeListeners[selector].events[event]);
2431				}
2432
2433			});
2434		}
2435
2436	}
2437
2438	var removeEvents = function(editor) {
2439
2440		var selector, elements, event;
2441
2442		for(selector in activeListeners){
2443
2444			[].forEach.call(activeListeners[selector].elements, function(element){
2445
2446				for(event in activeListeners[selector].events){
2447					element.removeEventListener(event, activeListeners[selector].events[event]);
2448				}
2449
2450			});
2451
2452			delete activeListeners[selector];
2453		}
2454
2455	}
2456
2457	this.events.on('editor-mouseenter', function(editor) {
2458		// refresh events every time in case stuff changed while we're gone.
2459		addEvents(editor);
2460	});
2461
2462	this.events.on('editor-instanciated', function(editor) {
2463		addEvents(editor);
2464	});
2465
2466	this.addHighlightHandler = function(selector, eventListeners){
2467
2468		selectors[selector] = eventListeners;
2469
2470	}
2471
2472	this.removeHighlightHandler = function(){
2473
2474	}
2475
2476});
2477
2478CargoEditor.addPlugin('implementationFixes', function(CargoEditor){
2479
2480	/**
2481	 * A collection of fixes for issues that are browser implementation related.
2482	 */
2483
2484	'use strict'
2485
2486	var _this = this;
2487
2488	/**
2489	 *	Uneditable regions inside an editable region do not receive drop events. Fix this by
2490	 *	enabling uneditable regions when dragging and disable them again when dragging is done.
2491\	 */
2492
2493	var uneditables = [];
2494
2495	var enableUneditables = function(el){
2496		
2497		disableUneditables(); // reset previous uneditables that might have been forgotten.
2498
2499		uneditables = el.querySelectorAll('[contenteditable="false"]');
2500
2501		[].forEach.call(uneditables, function(a){
2502			a.setAttribute('contenteditable', 'true');
2503		});
2504
2505	};
2506
2507	var disableUneditables = function(){
2508
2509		[].forEach.call(uneditables, function(a){
2510			a.setAttribute('contenteditable', 'false');
2511		});
2512
2513		uneditables = [];
2514		
2515	}
2516
2517	this.events.on('editor-dragin', function(editor, event){
2518		enableUneditables(editor.getElement());
2519	});
2520
2521	this.events.on('editor-drop', function(editor, event){
2522		disableUneditables();
2523	});
2524
2525	this.events.on('editor-dragout', function(editor, event){
2526		disableUneditables();
2527	});
2528
2529	this.events.on('editor-dragend', function(editor, event){
2530		disableUneditables();
2531	});
2532
2533	this.events.on('editor-destroyed', function(editor, event){
2534		disableUneditables();
2535	});
2536
2537	/**
2538	 *	When dragging an element like an image the browser creates an internal copy of the image instead of using
2539	 *	the reference found in event.target in the dragstart event. This causes our custom flag 'saveable' to be lost.
2540	 *	
2541	 */
2542	
2543	var dragged;
2544
2545	this.events.on('editor-dragstart', function(editor, event){
2546
2547		dragged = event.target;
2548
2549		/**
2550		 * Reset the dragged var when the target has been dropped somewhere.
2551		 */
2552		
2553		var once = function(){
2554			dragged.removeEventListener('dragend', once);
2555			dragged = undefined;
2556		}
2557
2558		dragged.addEventListener('dragend', once, false);
2559
2560	});
2561
2562	this.events.on('slideshow-dragstart', function(editor, event){
2563
2564		dragged = event.target;
2565
2566		/**
2567		 * Reset the dragged var when the target has been dropped somewhere.
2568		 */
2569		
2570		var once = function(){
2571			dragged.removeEventListener('dragend', once);
2572			dragged = undefined;
2573		}
2574
2575		dragged.addEventListener('dragend', once, false);
2576
2577	});
2578
2579	this.events.on('editor-drop', function(editor, event){
2580
2581		if(dragged !== undefined && dragged !== null){
2582			
2583			if(event.target.nodeName === "IMG" && event.target !== dragged){
2584
2585				CargoEditor.mutationManager.execute(function(){
2586					
2587					var tempContainer = document.createElement('div');
2588
2589					tempContainer.innerHTML = CargoEditor.plugins.cargo.templates.slideshow_header + CargoEditor.plugins.cargo.templates.slideshow_footer;
2590
2591					var slideshow = tempContainer.childNodes[1];
2592					slideshow.setAttribute('data-id', editor.cargoData.pid);
2593
2594					event.target.parentNode.insertBefore(slideshow, event.target);
2595
2596					slideshow.querySelector('.slideshow_container').appendChild(event.target);
2597					slideshow.querySelector('.slideshow_container').appendChild(dragged);
2598
2599					Cargo.Core.Slideshow.InitSlideshow();
2600
2601					CargoEditor.plugins.slideshows.addSlideshowDropInterface(editor, slideshow);
2602					//CargoEditor.plugins.slideshows.setInTether(true);
2603
2604					//console.log($(slideshow).is(':hover'));
2605
2606					return true;
2607
2608				});
2609
2610			} else {
2611
2612				CargoEditor.mutationManager.execute(function(){
2613
2614					_this.helpers.insertNodeAtCursorPosition(event, dragged);
2615
2616					var hasTextBefore = dragged.previousSibling !== null && dragged.previousSibling.nodeType === Node.TEXT_NODE && CargoEditor.helpers.whiteSpaceHelpers.is_all_ws(dragged.previousSibling) === false,
2617						hasTextAfter  = dragged.nextSibling !== null && dragged.nextSibling.nodeType === Node.TEXT_NODE && CargoEditor.helpers.whiteSpaceHelpers.is_all_ws(dragged.nextSibling) === false;
2618
2619					if(hasTextBefore && hasTextAfter){
2620						dragged.parentNode.insertBefore(document.createElement('br'), dragged);
2621						dragged.parentNode.insertBefore(document.createElement('br'), dragged.nextSibling);
2622					}
2623
2624					editor.moveCaret(dragged, 1);
2625
2626				});
2627
2628			}
2629
2630		}
2631
2632	});
2633
2634
2635});
2636
2637CargoEditor.addPlugin('projectimages', function(CargoEditor){
2638
2639	'use strict'
2640
2641	return;
2642
2643	/**
2644	 * This plugin handles the following: 
2645	 * 
2646	 * 		- Dragging image files into projects & uploading them
2647	 * 
2648	 */
2649
2650
2651	if(typeof Cargo !== "object"){
2652		console.error('Not in a Cargo environment.');
2653		return false;
2654	}
2655
2656	var _this = this;
2657
2658	Cargo.utils = Cargo.utils || {};
2659
2660	if(typeof Cargo.utils.ImageUploader !== 'function'){
2661		_this.helpers.loadScript('/_jsapps/api/_cargo/_core/CargoXHR.js', function(){
2662			_this.helpers.loadScript('/_jsapps/api/_cargo/_core/ImageUploader.js', function(){
2663				_this.uploader = new Cargo.utils.ImageUploader({
2664				});
2665			});
2666		});
2667	} else {
2668		_this.uploader = new Cargo.utils.ImageUploader({});
2669	}
2670
2671	var loadanimHTML = '<div class="ce-img-upload-anim"> \
2672							<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="40px" height="40px" viewBox="0 0 40 40" style="enable-background:new 0 0 40 40;" xml:space="preserve"> \
2673								<g class="background" style="opacity:0.6;"> \
2674									<circle style="fill:none;stroke:#FFFFFF;stroke-width:3;stroke-miterlimit:10;" cx="20" cy="20" r="12.5" /> \
2675								</g> \
2676								<g class="progress"> \
2677									<path style="fill:none;stroke:#FFFFFF;stroke-width:3;stroke-miterlimit:10;" d="M20,7.5c6.904,0,12.499,5.595,12.5,12.5 \
2678		c0,6.904-5.596,12.5-12.5,12.5C13.095,32.499,7.5,26.904,7.5,20C7.5,13.095,13.095,7.5,20,7.5z" /> \
2679								</g> \
2680							</svg> \
2681						</div>';
2682
2683	this.filequeue = [];
2684
2685	this.events.on('editor-dragenter', function(editor, event){
2686
2687		if(event.dataTransfer.files.length > 0){
2688
2689			event.dataTransfer.dropEffect = "move";
2690
2691			console.log('move');
2692
2693			var sel = window.getSelection();
2694
2695			if(sel.rangeCount > 0){
2696				sel.removeAllRanges();
2697			}
2698		}
2699
2700	});
2701
2702	this.events.on('editor-click', function(editor, event){
2703		
2704		if(event.target.nodeName === "IMG"){
2705
2706			event.preventDefault();
2707			event.stopPropagation();
2708
2709		}
2710
2711	});
2712
2713	this.events.on('editor-dragstart', function(editor, event){
2714	//	console.log(event);
2715	});
2716
2717	var slideshowDragged;
2718
2719	this.events.on('slideshow-dragstart', function(editor, event){
2720		slideshowDragged = event.target;
2721
2722		var once = function(){
2723			slideshowDragged.removeEventListener('dragend', once);
2724			slideshowDragged = undefined;
2725		}
2726
2727		slideshowDragged.addEventListener('dragend', once, false);
2728	});
2729
2730
2731
2732	this.events.on('editor-drop', function(editor, event){
2733
2734		if(event.dataTransfer.files.length > 0){
2735
2736			editor.getElement().focus();
2737
2738			event.preventDefault();
2739
2740			var uploadProcess = _this.uploader.upload(event.dataTransfer.files, editor.cargoData.pid);
2741		
2742			_this.events.trigger('project-images-upload-start', uploadProcess.files);
2743
2744			// insert images at the right place
2745			[].forEach.call(uploadProcess.files, function(file){
2746				
2747				var imgHTML = CargoEditor.plugins.cargo.api.populateTemplate('image', {
2748					'image': '',
2749					'w': '',
2750					'h': '',
2751					'w_o': '',
2752					'h_o': '',
2753					'src_o': '',
2754					'caption': ''
2755				});
2756
2757				var imgEl = _this.helpers.stringToHTMLNodes(imgHTML)[0];
2758
2759				imgEl.addEventListener('load', function(){
2760
2761					file.tether = CargoEditor.plugins.tether.addTether(loadanimHTML, imgEl, {
2762						ignoreEvents: true
2763					});
2764
2765					console.log(imgEl.naturalWidth, imgEl.naturalHeight);
2766
2767					imgEl.setAttribute('width_o', imgEl.naturalWidth);
2768					imgEl.setAttribute('height_o', imgEl.naturalHeight);
2769
2770					var defaultImageWidth = parseInt(Cargo.Model.DisplayOptions.get('image_width'));
2771
2772					if(isNaN(defaultImageWidth) === false){
2773						imgEl.setAttribute('width', (defaultImageWidth < imgEl.naturalWidth ? defaultImageWidth : imgEl.naturalWidth));
2774						imgEl.setAttribute('height', 'auto');
2775					}
2776
2777				});
2778
2779				imgEl.src = URL.createObjectURL(file);
2780
2781				imgEl.style.maxWidth = Cargo.Model.DisplayOptions.GetImageWidth() + 'px';
2782				imgEl.style.height = 'auto';
2783
2784				_this.helpers.insertNodeAtCursorPosition(event, imgEl);
2785
2786				file.imgEl = imgEl;
2787
2788			});
2789
2790			// upload the files
2791			if(uploadProcess !== false) {
2792
2793				uploadProcess.events.onComplete = function(){
2794					console.log("COMPLETE");
2795
2796					
2797					CargoEditor.plugins.cargo.api.reRenderProject();
2798					
2799				}
2800
2801				uploadProcess.events.onFileComplete = function(file, event){
2802
2803					var response = JSON.parse(this.xhr.responseText);
2804
2805					// Image is in our backend. Let the editor know it's safe to save the element.
2806					file.imgEl.setAttribute('data-mid', response.id);
2807					file.imgEl.setSaveable(true);
2808
2809					if(file.tether !== undefined){
2810						// remove the tethered overlay
2811						file.tether.destroy();
2812						file.tether.element.parentNode.removeChild(file.tether.element);
2813					}
2814
2815				}
2816
2817				uploadProcess.events.onFileProgress = function(file, e){
2818
2819					if(file.tether === undefined){
2820						return false;
2821					}
2822
2823					var path = file.tether.element.querySelector('.progress path'),
2824						length = path.getTotalLength(),
2825						percentComplete = Math.ceil(e.loaded * 100 / e.total),
2826						lengthComplete = Math.ceil((length / 100) * percentComplete);
2827
2828					if(percentComplete > 99){
2829
2830						file.tether.element.querySelector('.background circle').style.display = 'none';
2831						
2832						// The backend is processing the file.
2833						path.style.strokeDasharray = '3, 3.5';
2834						_this.helpers.addClass(file.tether.element, 'processing');	
2835
2836					} else {
2837
2838						// show the current upload progress
2839						path.style.strokeDasharray = length;
2840						path.style.strokeDashoffset = (length - lengthComplete) * -1;
2841
2842					}
2843				}
2844			}
2845
2846		} else if(slideshowDragged !== undefined){
2847
2848			_this.helpers.insertNodeAtCursorPosition(event, slideshowDragged);
2849			slideshowDragged.setSaveable(true);
2850
2851		}
2852
2853	});
2854
2855});
2856
2857CargoEditor.addPlugin('slideshows', function(CargoEditor){
2858
2859	'use strict'
2860
2861	var _this = this;
2862
2863	if(typeof Cargo !== "object"){
2864		console.error('Not in a Cargo environment.');
2865		return false;
2866	}
2867	
2868	Cargo.utils = Cargo.utils || {};
2869
2870	if(typeof Cargo.utils.ImageUploader !== 'function'){
2871		
2872		_this.helpers.loadScript('/_jsapps/api/_cargo/_core/CargoXHR.js', function(){
2873		
2874			_this.helpers.loadScript('/_jsapps/api/_cargo/_core/ImageUploader.js', function(){
2875		
2876				_this.uploader = new Cargo.utils.ImageUploader({
2877		
2878				});
2879		
2880			});
2881		
2882		});
2883
2884	} else {
2885		_this.uploader = new Cargo.utils.ImageUploader({});
2886	}
2887
2888	var tethers = [],
2889		dragginInEditor = false,
2890		hoveringEditor = false,
2891		inTether = false,
2892		uploadProcessQueue = [],
2893		dragged,
2894		lastActiveDropzone;
2895
2896	var loadanimHTML = '<div class="ce-img-upload-anim"> \
2897							<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="40px" height="40px" viewBox="0 0 40 40" style="enable-background:new 0 0 40 40;" xml:space="preserve"> \
2898								<g class="background" style="opacity:0.6;"> \
2899									<circle style="fill:none;stroke:#FFFFFF;stroke-width:3;stroke-miterlimit:10;" cx="20" cy="20" r="12.5" /> \
2900								</g> \
2901								<g class="progress"> \
2902									<path style="fill:none;stroke:#FFFFFF;stroke-width:3;stroke-miterlimit:10;" d="M20,7.5c6.904,0,12.499,5.595,12.5,12.5 \
2903		c0,6.904-5.596,12.5-12.5,12.5C13.095,32.499,7.5,26.904,7.5,20C7.5,13.095,13.095,7.5,20,7.5z" /> \
2904								</g> \
2905							</svg> \
2906						</div>';
2907
2908	_this.events.on('global-delete-pressed', function(){
2909
2910		var selection = getSelection();
2911
2912		if(selection.rangeCount > 0){
2913			var range = selection.getRangeAt(0),
2914				i;
2915
2916			for(i = range.startOffset; i < range.endOffset; i++){
2917				var node = range.commonAncestorContainer.childNodes[i];
2918
2919				if(node !== undefined){
2920					node.parentNode.removeChild(node);
2921				}
2922			}
2923
2924			if(lastActiveDropzone !== undefined){
2925				updateSlideshowImages(lastActiveDropzone.thumbContainer, lastActiveDropzone.slideshowObject);
2926			}
2927		}
2928
2929	});
2930
2931	var addSlideshowDropInterface = function(editor, slideshow, manuallyOpened){
2932
2933		removeSlideshowDropInterface();
2934
2935		var slideshows,
2936			i;
2937
2938		if(slideshow !== undefined) {
2939			slideshows = [slideshow];
2940		} else {
2941			slideshows = editor.getElement().querySelectorAll('.slideshow');
2942		}
2943
2944		_this.events.trigger('editor-slideshow-edit-start', CargoEditor, slideshows);
2945
2946		[].forEach.call(slideshows, function(slideshow){
2947
2948			var slideshowObject = Cargo.Core.Slideshow.SlideshowObjects[slideshow.getAttribute('data-id')];
2949
2950			if(slideshowObject === undefined){
2951				alert('Couldn\'t retrieve slideshow object. Unable to edit this slideshow.');
2952				return false;
2953			}
2954
2955			var dropzone = document.createElement('div'),
2956				dropzonetop = document.createElement('div'),
2957				thumbContainer = document.createElement('div'),
2958				slideshowImgs = slideshow.querySelectorAll('.slideshow_container img');
2959
2960			dropzonetop.className = 'topbar';
2961			dropzonetop.innerHTML = '<span class="title">Slideshow</span><div class="done">Done <div class="retinaSpinner rotating"><div style="position: absolute; top: -2px;"><div style="-webkit-transform: rotate(0deg) translate(6px, 0px); transform: rotate(0deg) translate(6px, 0px);" class="spinner_circle part1"></div></div><div style="position: absolute; top: -2px;"><div style="-webkit-transform: rotate(45deg) translate(6px, 0px); transform: rotate(45deg) translate(6px, 0px);" class="spinner_circle part2"></div></div><div style="position: absolute; top: -2px;"><div style="-webkit-transform: rotate(90deg) translate(6px, 0px); transform: rotate(90deg) translate(6px, 0px);" class="spinner_circle part3"></div></div><div style="position: absolute; top: -2px;"><div style="-webkit-transform: rotate(135deg) translate(6px, 0px); transform: rotate(135deg) translate(6px, 0px);" class="spinner_circle part4"></div></div><div style="position: absolute; top: -2px;"><div style="-webkit-transform: rotate(180deg) translate(6px, 0px); transform: rotate(180deg) translate(6px, 0px);" class="spinner_circle part5"></div></div><div style="position: absolute; top: -2px;"><div style="-webkit-transform: rotate(225deg) translate(6px, 0px); transform: rotate(225deg) translate(6px, 0px);" class="spinner_circle part6"></div></div><div style="position: absolute; top: -2px;"><div style="-webkit-transform: rotate(270deg) translate(6px, 0px); transform: rotate(270deg) translate(6px, 0px);" class="spinner_circle part7"></div></div><div style="position: absolute; top: -2px;"><div style="-webkit-transform: rotate(315deg) translate(6px, 0px); transform: rotate(315deg) translate(6px, 0px);" class="spinner_circle part8"></div></div></div></div> <div class="remove">Remove</div>';
2962			dropzone.appendChild(dropzonetop);
2963
2964			var doneButton = dropzonetop.querySelector('.done');
2965
2966			doneButton.addEventListener('click', function(){
2967				removeSlideshowDropInterface();
2968			});
2969
2970			var removeButton = dropzonetop.querySelector('.remove'),
2971				removeSlideshow = function(verify){
2972
2973					if(verify) {
2974						if(!confirm('This will remove the slideshow. Your images will still be available in the images panel.')) {
2975							return false;
2976						}
2977					}
2978
2979					console.log("REMOVE");
2980			
2981					slideshow.parentNode.removeChild(slideshow);
2982
2983					[].forEach.call(uploadProcessQueue, function(uploadProcess){
2984						uploadProcess.abort();
2985					});
2986
2987					uploadProcessQueue = [];
2988
2989					removeSlideshowDropInterface();
2990
2991				}
2992
2993			removeButton.addEventListener('click', function(){
2994				removeSlideshow(true);
2995			});
2996
2997			thumbContainer.className = 'thumb-container';
2998			dropzone.appendChild(thumbContainer);
2999
3000			dropzone.className = 'ce-slideshow-dropzone';
3001
3002			/**
3003			 * The element that acts as placeholder for the dragged element
3004			 */
3005			var dragPlaceHolder = document.createElement('div');
3006
3007			dragPlaceHolder.className = 'ce-drag-placeholder';
3008
3009			thumbContainer.addEventListener('dragstart', function(event){
3010
3011				_this.events.trigger('slideshow-dragstart', editor, event);
3012				dragged = event.target;
3013				_this.helpers.addClass(dragged, 'dragged');
3014
3015				var once = function(){
3016					dragged.removeEventListener('dragend', once);
3017
3018					_this.events.trigger('slideshow-dragend', editor, event);
3019					_this.helpers.removeClass(dragged, 'dragged');
3020
3021					if(dragPlaceHolder.parentNode !== null){
3022						dragPlaceHolder.parentNode.removeChild(dragPlaceHolder);
3023					}
3024
3025					updateSlideshowImages(thumbContainer, slideshowObject);
3026					
3027					if(slideshowObject.slides.length <= 1) {
3028
3029						// move images out
3030						[].forEach.call(thumbContainer.querySelectorAll('img'), function(img){
3031 							slideshow.parentNode.insertBefore(img, slideshow);
3032						});
3033
3034						removeSlideshow(false);
3035					} 
3036
3037					dragged = undefined;
3038				}
3039
3040				dragged.addEventListener('dragend', once, false);
3041
3042			});
3043
3044			thumbContainer.addEventListener('click', function(event){
3045
3046				console.log("CLICK 1");
3047
3048				lastActiveDropzone = {
3049					thumbContainer: thumbContainer, 
3050					slideshowObject: slideshowObject
3051				}
3052
3053				if(event.target.nodeName === "IMG"){
3054					_this.helpers.selectNode(event.target);
3055
3056					event.preventDefault();
3057					event.stopPropagation();
3058				}
3059
3060			});
3061
3062
3063			[].forEach.call(slideshowImgs, function(img){
3064				
3065				img = img.cloneNode();
3066				img.removeAttribute('class');
3067				img.removeAttribute('style');
3068
3069				thumbContainer.appendChild(img);
3070
3071			});
3072
3073			tethers.push(_this.pluginNS.tether.addTether(dropzone, slideshow, {
3074				ignoreEvents: false,
3075				coverParent: true,
3076				padding: 5,
3077				events: {
3078					dragover: function(event){
3079						event.preventDefault();
3080					},
3081					dragenter: function(event){
3082
3083						if(event.target !== dropzonetop && event.target.parentNode !== dropzonetop){
3084							if(event.target.nodeName === "IMG"){
3085								dragPlaceHolder.style.height = event.target.clientHeight + 'px';
3086							}
3087
3088							_this.helpers.insertNodeAtCursorPosition(event, dragPlaceHolder);
3089						}
3090
3091					},
3092					dragend: function(){
3093						
3094						if(dragPlaceHolder.parentNode !== null){
3095							dragPlaceHolder.parentNode.removeChild(dragPlaceHolder);
3096						}
3097
3098					},
3099					drop: function(event){
3100
3101						var placeholderComment = document.createComment('placeholder');
3102
3103						if(dragPlaceHolder.parentNode !== null){
3104
3105							dragPlaceHolder.parentNode.insertBefore(placeholderComment, dragPlaceHolder);
3106							dragPlaceHolder.parentNode.removeChild(dragPlaceHolder);
3107
3108						}
3109
3110						if(event.dataTransfer.files.length > 0){
3111
3112							event.preventDefault();
3113
3114							var acceptedFiles = [];
3115
3116							[].forEach.call(event.dataTransfer.files, function(file){
3117
3118								if(file.type.indexOf('image/') === 0){
3119									acceptedFiles.push(file);
3120
3121									// insert preview thumb
3122									var img = document.createElement('img');
3123									img.src = URL.createObjectURL(file);
3124
3125									img.setAttribute('align', 'left');
3126
3127									if(event.target === thumbContainer || event.target.parentNode === thumbContainer){
3128										placeholderComment.parentNode.insertBefore(img, placeholderComment);
3129										//_this.helpers.insertNodeAtCursorPosition(event, img);
3130									} else if(placeholderComment.parentNode !== null) {
3131										placeholderComment.parentNode.insertBefore(img, placeholderComment);
3132									} else {
3133										thumbContainer.appendChild(img);
3134									}
3135
3136									img.addEventListener('load', function(){
3137
3138										file.tether = _this.pluginNS.tether.addTether(loadanimHTML, this, {
3139											ignoreEvents: true
3140										});
3141
3142										img.setAttribute('width_o', this.naturalWidth);
3143										img.setAttribute('height_o', this.naturalHeight);
3144
3145										var defaultImageWidth = parseInt(Cargo.Model.DisplayOptions.get('image_width'));
3146
3147										if(isNaN(defaultImageWidth) === false){
3148											img.setAttribute('width', (defaultImageWidth < this.naturalWidth ? defaultImageWidth : this.naturalWidth));
3149											img.setAttribute('height', 'auto');
3150										}
3151
3152									});
3153
3154									file.tempImage = img;
3155									file.slideshow = slideshow;
3156									file.dropzone = dropzone;
3157
3158								}
3159
3160							});
3161
3162							var uploadProcess = _this.uploader.upload(acceptedFiles, editor.cargoData.pid);
3163
3164							if(uploadProcess !== false) {
3165
3166								uploadProcessQueue.push(uploadProcess);
3167
3168								_this.helpers.addClass(doneButton, 'uploading');
3169
3170								uploadProcess.events.onFileProgress = function(file, e){
3171									if(file.tether === undefined){
3172										return false;
3173									}
3174
3175									var path = file.tether.element.querySelector('.progress path'),
3176										length = path.getTotalLength(),
3177										percentComplete = Math.ceil(e.loaded * 100 / e.total),
3178										lengthComplete = Math.ceil((length / 100) * percentComplete);
3179
3180									if(percentComplete > 99){
3181
3182										file.tether.element.querySelector('.background circle').style.display = 'none';
3183
3184										// The backend is processing the file.
3185										path.style.strokeDasharray = '3, 3.5';
3186										_this.helpers.addClass(file.tether.element, 'processing');	
3187
3188									} else {
3189
3190										// show the current upload progress
3191										path.style.strokeDasharray = length;
3192										path.style.strokeDashoffset = (length - lengthComplete) * -1;
3193
3194									}
3195								}
3196
3197								uploadProcess.events.onFileComplete = function(file, event){
3198
3199									if(file.tether !== undefined){
3200										// remove the tethered overlay
3201										file.tether.destroy();
3202										file.tether.element.parentNode.removeChild(file.tether.element);
3203									}
3204
3205									var response = JSON.parse(this.xhr.responseText);
3206
3207									file.tempImage.setAttribute('data-mid', response.mid);
3208
3209								}
3210
3211								uploadProcess.events.onAbort = function(process){
3212
3213									[].forEach.call(process.files, function(file){
3214
3215										if(file.tether !== undefined){
3216											// remove the tethered overlay
3217											file.tether.destroy();
3218											file.tether.element.parentNode.removeChild(file.tether.element);
3219										}
3220
3221										file.tempImage.parentNode.removeChild(file.tempImage);
3222									});
3223
3224									uploadProcessQueue.splice(uploadProcessQueue.indexOf(uploadProcess), 1);
3225
3226								}
3227
3228								uploadProcess.events.onFileFail = function(file, event){
3229
3230									if(file.tether !== undefined){
3231										// remove the tethered overlay
3232										file.tether.destroy();
3233										file.tether.element.parentNode.removeChild(file.tether.element);
3234									}
3235
3236									alert('failed to upload ' + file.name);
3237
3238									file.tempImage.parentNode.removeChild(file.tempImage);
3239
3240								}
3241
3242								uploadProcess.events.onComplete = function(process){
3243
3244									uploadProcessQueue.splice(uploadProcessQueue.indexOf(uploadProcess), 1);
3245
3246									if(uploadProcessQueue.length === 0){
3247										_this.helpers.removeClass(doneButton, 'uploading');
3248									}
3249
3250									placeholderComment.parentNode.removeChild(placeholderComment);
3251
3252									updateSlideshowImages(thumbContainer, slideshowObject);
3253
3254									if(manuallyOpened !== true){
3255										removeSlideshowDropInterface();
3256									}
3257								}
3258							}
3259
3260						} else if(dragged !== undefined) {
3261
3262							placeholderComment.parentNode.insertBefore(dragged, placeholderComment);
3263							placeholderComment.parentNode.removeChild(placeholderComment);
3264
3265							updateSlideshowImages(thumbContainer, slideshowObject);
3266
3267						} else if(editorDragged !== undefined){
3268
3269							placeholderComment.parentNode.insertBefore(editorDragged, placeholderComment);
3270							placeholderComment.parentNode.removeChild(placeholderComment);
3271
3272							updateSlideshowImages(thumbContainer, slideshowObject);
3273
3274						}
3275					} 
3276				}
3277			}));
3278
3279		});
3280
3281	}
3282
3283	this.addSlideshowDropInterface = addSlideshowDropInterface;
3284
3285	this.setInTether = function(bool){
3286		inTether = bool
3287	}
3288
3289	var updateSlideshowImages = function(dropzoneThumbs, slideshowObject){
3290
3291		slideshowObject.elements.$container.html('');
3292
3293		[].forEach.call(dropzoneThumbs.children, function(child){
3294			
3295			var clone = child.cloneNode(true);
3296			
3297			clone.style.width = Cargo.Model.DisplayOptions.GetImageWidth() + 'px';
3298			clone.style.height = 'auto';
3299			clone.setSaveable(true);
3300			
3301			slideshowObject.elements.$container[0].appendChild(clone);
3302
3303		});
3304
3305		slideshowObject.setSlides();
3306		slideshowObject.elements.$thumbnails.html('');
3307		slideshowObject.setThumbnails();
3308		slideshowObject.setFirstSlide();
3309
3310		_this.events.trigger('global-reposition-tethers');
3311
3312	}
3313
3314	var removeSlideshowDropInterface = function(){
3315
3316		if(uploadProcessQueue.length === 0 && dragged === undefined){
3317
3318			[].forEach.call(tethers, function(tether){
3319				tether.destroy();
3320				tether.element.parentNode.removeChild(tether.element);
3321			});
3322
3323			inTether = false;
3324			lastActiveDropzone = undefined;
3325
3326			tethers = [];
3327
3328			_this.events.trigger('editor-slideshow-edit-stop', CargoEditor);
3329		}
3330	}
3331
3332	/**
3333	 * Handle hiding and showing the dropzone
3334	 */
3335	
3336	_this.pluginNS.highlighter.addHighlightHandler('.slideshow', {
3337
3338		click: function(e, tetherObj) {
3339
3340			// open slideshow interface
3341			addSlideshowDropInterface(undefined, tetherObj.target, true);
3342
3343			tetherObj.element.parentNode.removeChild(tetherObj.element);
3344			tetherObj.destroy();
3345
3346			inTether = true;
3347
3348		}
3349
3350	});
3351
3352	document.body.addEventListener('click', function(){
3353		if(!inTether){
3354			removeSlideshowDropInterface();
3355		}
3356	}, false);
3357
3358	var editorDragged;
3359
3360	_this.events.on('editor-dragstart', function(editor, event){
3361
3362		editorDragged = event.target;
3363
3364		var once = function(){
3365
3366			editorDragged.removeEventListener('dragend', once);
3367			editorDragged = undefined;
3368		
3369		}
3370
3371		editorDragged.addEventListener('dragend', once, false);
3372
3373	});
3374
3375
3376	_this.events.on('editor-mouseenter', function(editor, event){
3377
3378		hoveringEditor = true;
3379
3380	});
3381
3382	_this.events.on('editor-mouseleave', function(editor, event){
3383
3384		hoveringEditor = false;
3385
3386	});
3387
3388	_this.events.on('editor-dragin', function(editor, event){
3389
3390		dragginInEditor = true;
3391
3392		if(inTether === false) {
3393			addSlideshowDropInterface(editor);
3394		}
3395
3396	});
3397
3398	_this.events.on('editor-click', function(editor, event){
3399
3400		console.log("CLICK", event.currentTarget);
3401
3402		var parent = event.currentTarget,
3403			inSlidshow = false;
3404
3405		while(parent !== null){
3406
3407			if($(parent).hasClass('slideshow')) {
3408
3409				console.log('Slideshow', parent);
3410				
3411				if(inTether === false) {
3412					addSlideshowDropInterface(editor);
3413				}
3414
3415				break;
3416			}
3417
3418			parent = parent.parentNode;
3419		}
3420
3421	});
3422
3423	_this.events.on('editor-drop', function(editor, event){
3424
3425		removeSlideshowDropInterface();
3426
3427		dragginInEditor = false;
3428		inTether = false;
3429
3430	});
3431
3432	_this.events.on('editor-dragout', function(editor, event){
3433		
3434		dragginInEditor = false;
3435	
3436		if(dragginInEditor === false && inTether === false) {
3437			removeSlideshowDropInterface();
3438		}
3439
3440	});
3441
3442	_this.events.on('tether-dragin', function(editor, event){
3443
3444		inTether = true;
3445
3446	});
3447
3448	_this.events.on('tether-dragout', function(editor, event){
3449
3450		inTether = false;
3451
3452		if(dragginInEditor === false && dragged === undefined){
3453			removeSlideshowDropInterface();
3454		}
3455
3456	});
3457
3458	_this.events.on('tether-mouseenter', function(editor, event){
3459
3460		inTether = true;
3461
3462	});
3463
3464	_this.events.on('tether-mouseleave', function(editor, event){
3465
3466		inTether = false;
3467
3468	});
3469
3470});
3471
3472/*! tether 0.6.5 */
3473
3474
3475(function(root, factory) {
3476  /*if (typeof define === 'function' && define.amd) {
3477    define(factory);
3478  } else if (typeof exports === 'object') {
3479    module.exports = factory(require,exports,module);
3480  } else {
3481    root.Tether = factory();
3482  }*/
3483
3484  root.Tether = factory();
3485
3486}(this, function(require,exports,module) {
3487
3488(function() {
3489  var Evented, addClass, defer, deferred, extend, flush, getBounds, getOffsetParent, getOrigin, getScrollBarSize, getScrollParent, hasClass, node, removeClass, uniqueId, updateClasses, zeroPosCache,
3490    __hasProp = {}.hasOwnProperty,
3491    __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
3492    __slice = [].slice;
3493
3494  if (this.Tether == null) {
3495    this.Tether = {
3496      modules: []
3497    };
3498  }
3499
3500  getScrollParent = function(el) {
3501    var parent, position, scrollParent, style, _ref;
3502    position = getComputedStyle(el).position;
3503    if (position === 'fixed') {
3504      return el;
3505    }
3506    scrollParent = void 0;
3507    parent = el;
3508    while (parent = parent.parentNode) {
3509      try {
3510        style = getComputedStyle(parent);
3511      } catch (_error) {}
3512      if (style == null) {
3513        return parent;
3514      }
3515      if (/(auto|scroll)/.test(style['overflow'] + style['overflow-y'] + style['overflow-x'])) {
3516        if (position !== 'absolute' || ((_ref = style['position']) === 'relative' || _ref === 'absolute' || _ref === 'fixed')) {
3517          return parent;
3518        }
3519      }
3520    }
3521    return document.body;
3522  };
3523
3524  uniqueId = (function() {
3525    var id;
3526    id = 0;
3527    return function() {
3528      return id++;
3529    };
3530  })();
3531
3532  zeroPosCache = {};
3533
3534  getOrigin = function(doc) {
3535    var id, k, node, v, _ref;
3536    node = doc._tetherZeroElement;
3537    if (node == null) {
3538      node = doc.createElement('div');
3539      node.setAttribute('data-tether-id', uniqueId());
3540      extend(node.style, {
3541        top: 0,
3542        left: 0,
3543        position: 'absolute'
3544      });
3545      doc.body.appendChild(node);
3546      doc._tetherZeroElement = node;
3547    }
3548    id = node.getAttribute('data-tether-id');
3549    if (zeroPosCache[id] == null) {
3550      zeroPosCache[id] = {};
3551      _ref = node.getBoundingClientRect();
3552      for (k in _ref) {
3553        v = _ref[k];
3554        zeroPosCache[id][k] = v;
3555      }
3556      defer(function() {
3557        return zeroPosCache[id] = void 0;
3558      });
3559    }
3560    return zeroPosCache[id];
3561  };
3562
3563  node = null;
3564
3565  getBounds = function(el) {
3566    var box, doc, docEl, k, origin, v, _ref;
3567    if (el === document) {
3568      doc = document;
3569      el = document.documentElement;
3570    } else {
3571      doc = el.ownerDocument;
3572    }
3573    docEl = doc.documentElement;
3574    box = {};
3575    _ref = el.getBoundingClientRect();
3576    for (k in _ref) {
3577      v = _ref[k];
3578      box[k] = v;
3579    }
3580    origin = getOrigin(doc);
3581    box.top -= origin.top;
3582    box.left -= origin.left;
3583    if (box.width == null) {
3584      box.width = document.body.scrollWidth - box.left - box.right;
3585    }
3586    if (box.height == null) {
3587      box.height = document.body.scrollHeight - box.top - box.bottom;
3588    }
3589    box.top = box.top - docEl.clientTop;
3590    box.left = box.left - docEl.clientLeft;
3591    box.right = doc.body.clientWidth - box.width - box.left;
3592    box.bottom = doc.body.clientHeight - box.height - box.top;
3593    return box;
3594  };
3595
3596  getOffsetParent = function(el) {
3597    return el.offsetParent || document.documentElement;
3598  };
3599
3600  getScrollBarSize = function() {
3601    var inner, outer, width, widthContained, widthScroll;
3602    inner = document.createElement('div');
3603    inner.style.width = '100%';
3604    inner.style.height = '200px';
3605    outer = document.createElement('div');
3606    extend(outer.style, {
3607      position: 'absolute',
3608      top: 0,
3609      left: 0,
3610      pointerEvents: 'none',
3611      visibility: 'hidden',
3612      width: '200px',
3613      height: '150px',
3614      overflow: 'hidden'
3615    });
3616    outer.appendChild(inner);
3617    document.body.appendChild(outer);
3618    widthContained = inner.offsetWidth;
3619    outer.style.overflow = 'scroll';
3620    widthScroll = inner.offsetWidth;
3621    if (widthContained === widthScroll) {
3622      widthScroll = outer.clientWidth;
3623    }
3624    document.body.removeChild(outer);
3625    width = widthContained - widthScroll;
3626    return {
3627      width: width,
3628      height: width
3629    };
3630  };
3631
3632  extend = function(out) {
3633    var args, key, obj, val, _i, _len, _ref;
3634    if (out == null) {
3635      out = {};
3636    }
3637    args = [];
3638    Array.prototype.push.apply(args, arguments);
3639    _ref = args.slice(1);
3640    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
3641      obj = _ref[_i];
3642      if (obj) {
3643        for (key in obj) {
3644          if (!__hasProp.call(obj, key)) continue;
3645          val = obj[key];
3646          out[key] = val;
3647        }
3648      }
3649    }
3650    return out;
3651  };
3652
3653  removeClass = function(el, name) {
3654    var cls, _i, _len, _ref, _results;
3655    if (el.classList != null) {
3656      _ref = name.split(' ');
3657      _results = [];
3658      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
3659        cls = _ref[_i];
3660        if (cls.trim()) {
3661          _results.push(el.classList.remove(cls));
3662        }
3663      }
3664      return _results;
3665    } else {
3666      return el.className = el.className.replace(new RegExp("(^| )" + (name.split(' ').join('|')) + "( |$)", 'gi'), ' ');
3667    }
3668  };
3669
3670  addClass = function(el, name) {
3671    var cls, _i, _len, _ref, _results;
3672    if (el.classList != null) {
3673      _ref = name.split(' ');
3674      _results = [];
3675      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
3676        cls = _ref[_i];
3677        if (cls.trim()) {
3678          _results.push(el.classList.add(cls));
3679        }
3680      }
3681      return _results;
3682    } else {
3683      removeClass(el, name);
3684      return el.className += " " + name;
3685    }
3686  };
3687
3688  hasClass = function(el, name) {
3689    if (el.classList != null) {
3690      return el.classList.contains(name);
3691    } else {
3692      return new RegExp("(^| )" + name + "( |$)", 'gi').test(el.className);
3693    }
3694  };
3695
3696  updateClasses = function(el, add, all) {
3697    var cls, _i, _j, _len, _len1, _results;
3698    for (_i = 0, _len = all.length; _i < _len; _i++) {
3699      cls = all[_i];
3700      if (__indexOf.call(add, cls) < 0) {
3701        if (hasClass(el, cls)) {
3702          removeClass(el, cls);
3703        }
3704      }
3705    }
3706    _results = [];
3707    for (_j = 0, _len1 = add.length; _j < _len1; _j++) {
3708      cls = add[_j];
3709      if (!hasClass(el, cls)) {
3710        _results.push(addClass(el, cls));
3711      } else {
3712        _results.push(void 0);
3713      }
3714    }
3715    return _results;
3716  };
3717
3718  deferred = [];
3719
3720  defer = function(fn) {
3721    return deferred.push(fn);
3722  };
3723
3724  flush = function() {
3725    var fn, _results;
3726    _results = [];
3727    while (fn = deferred.pop()) {
3728      _results.push(fn());
3729    }
3730    return _results;
3731  };
3732
3733  Evented = (function() {
3734    function Evented() {}
3735
3736    Evented.prototype.on = function(event, handler, ctx, once) {
3737      var _base;
3738      if (once == null) {
3739        once = false;
3740      }
3741      if (this.bindings == null) {
3742        this.bindings = {};
3743      }
3744      if ((_base = this.bindings)[event] == null) {
3745        _base[event] = [];
3746      }
3747      return this.bindings[event].push({
3748        handler: handler,
3749        ctx: ctx,
3750        once: once
3751      });
3752    };
3753
3754    Evented.prototype.once = function(event, handler, ctx) {
3755      return this.on(event, handler, ctx, true);
3756    };
3757
3758    Evented.prototype.off = function(event, handler) {
3759      var i, _ref, _results;
3760      if (((_ref = this.bindings) != null ? _ref[event] : void 0) == null) {
3761        return;
3762      }
3763      if (handler == null) {
3764        return delete this.bindings[event];
3765      } else {
3766        i = 0;
3767        _results = [];
3768        while (i < this.bindings[event].length) {
3769          if (this.bindings[event][i].handler === handler) {
3770            _results.push(this.bindings[event].splice(i, 1));
3771          } else {
3772            _results.push(i++);
3773          }
3774        }
3775        return _results;
3776      }
3777    };
3778
3779    Evented.prototype.trigger = function() {
3780      var args, ctx, event, handler, i, once, _ref, _ref1, _results;
3781      event = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
3782      if ((_ref = this.bindings) != null ? _ref[event] : void 0) {
3783        i = 0;
3784        _results = [];
3785        while (i < this.bindings[event].length) {
3786          _ref1 = this.bindings[event][i], handler = _ref1.handler, ctx = _ref1.ctx, once = _ref1.once;
3787          handler.apply(ctx != null ? ctx : this, args);
3788          if (once) {
3789            _results.push(this.bindings[event].splice(i, 1));
3790          } else {
3791            _results.push(i++);
3792          }
3793        }
3794        return _results;
3795      }
3796    };
3797
3798    return Evented;
3799
3800  })();
3801
3802  this.Tether.Utils = {
3803    getScrollParent: getScrollParent,
3804    getBounds: getBounds,
3805    getOffsetParent: getOffsetParent,
3806    extend: extend,
3807    addClass: addClass,
3808    removeClass: removeClass,
3809    hasClass: hasClass,
3810    updateClasses: updateClasses,
3811    defer: defer,
3812    flush: flush,
3813    uniqueId: uniqueId,
3814    Evented: Evented,
3815    getScrollBarSize: getScrollBarSize
3816  };
3817
3818}).call(this);
3819
3820(function() {
3821  var MIRROR_LR, MIRROR_TB, OFFSET_MAP, Tether, addClass, addOffset, attachmentToOffset, autoToFixedAttachment, defer, extend, flush, getBounds, getOffsetParent, getOuterSize, getScrollBarSize, getScrollParent, getSize, now, offsetToPx, parseAttachment, parseOffset, position, removeClass, tethers, transformKey, updateClasses, within, _Tether, _ref,
3822    __slice = [].slice,
3823    __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
3824
3825  if (this.Tether == null) {
3826    throw new Error("You must include the utils.js file before tether.js");
3827  }
3828
3829  Tether = this.Tether;
3830
3831  _ref = Tether.Utils, getScrollParent = _ref.getScrollParent, getSize = _ref.getSize, getOuterSize = _ref.getOuterSize, getBounds = _ref.getBounds, getOffsetParent = _ref.getOffsetParent, extend = _ref.extend, addClass = _ref.addClass, removeClass = _ref.removeClass, updateClasses = _ref.updateClasses, defer = _ref.defer, flush = _ref.flush, getScrollBarSize = _ref.getScrollBarSize;
3832
3833  within = function(a, b, diff) {
3834    if (diff == null) {
3835      diff = 1;
3836    }
3837    return (a + diff >= b && b >= a - diff);
3838  };
3839
3840  transformKey = (function() {
3841    var el, key, _i, _len, _ref1;
3842    el = document.createElement('div');
3843    _ref1 = ['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform'];
3844    for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
3845      key = _ref1[_i];
3846      if (el.style[key] !== void 0) {
3847        return key;
3848      }
3849    }
3850  })();
3851
3852  tethers = [];
3853
3854  position = function() {
3855    var tether, _i, _len;
3856    for (_i = 0, _len = tethers.length; _i < _len; _i++) {
3857      tether = tethers[_i];
3858      tether.position(false);
3859    }
3860    return flush();
3861  };
3862
3863  now = function() {
3864    var _ref1;
3865    return (_ref1 = typeof performance !== "undefined" && performance !== null ? typeof performance.now === "function" ? performance.now() : void 0 : void 0) != null ? _ref1 : +(new Date);
3866  };
3867
3868  (function() {
3869    var event, lastCall, lastDuration, pendingTimeout, tick, _i, _len, _ref1, _results;
3870    lastCall = null;
3871    lastDuration = null;
3872    pendingTimeout = null;
3873    tick = function() {
3874      if ((lastDuration != null) && lastDuration > 16) {
3875        lastDuration = Math.min(lastDuration - 16, 250);
3876        pendingTimeout = setTimeout(tick, 250);
3877        return;
3878      }
3879      if ((lastCall != null) && (now() - lastCall) < 10) {
3880        return;
3881      }
3882      if (pendingTimeout != null) {
3883        clearTimeout(pendingTimeout);
3884        pendingTimeout = null;
3885      }
3886      lastCall = now();
3887      position();
3888      return lastDuration = now() - lastCall;
3889    };
3890    _ref1 = ['resize', 'scroll', 'touchmove'];
3891    _results = [];
3892    for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
3893      event = _ref1[_i];
3894      _results.push(window.addEventListener(event, tick));
3895    }
3896    return _results;
3897  })();
3898
3899  MIRROR_LR = {
3900    center: 'center',
3901    left: 'right',
3902    right: 'left'
3903  };
3904
3905  MIRROR_TB = {
3906    middle: 'middle',
3907    top: 'bottom',
3908    bottom: 'top'
3909  };
3910
3911  OFFSET_MAP = {
3912    top: 0,
3913    left: 0,
3914    middle: '50%',
3915    center: '50%',
3916    bottom: '100%',
3917    right: '100%'
3918  };
3919
3920  autoToFixedAttachment = function(attachment, relativeToAttachment) {
3921    var left, top;
3922    left = attachment.left, top = attachment.top;
3923    if (left === 'auto') {
3924      left = MIRROR_LR[relativeToAttachment.left];
3925    }
3926    if (top === 'auto') {
3927      top = MIRROR_TB[relativeToAttachment.top];
3928    }
3929    return {
3930      left: left,
3931      top: top
3932    };
3933  };
3934
3935  attachmentToOffset = function(attachment) {
3936    var _ref1, _ref2;
3937    return {
3938      left: (_ref1 = OFFSET_MAP[attachment.left]) != null ? _ref1 : attachment.left,
3939      top: (_ref2 = OFFSET_MAP[attachment.top]) != null ? _ref2 : attachment.top
3940    };
3941  };
3942
3943  addOffset = function() {
3944    var left, offsets, out, top, _i, _len, _ref1;
3945    offsets = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
3946    out = {
3947      top: 0,
3948      left: 0
3949    };
3950    for (_i = 0, _len = offsets.length; _i < _len; _i++) {
3951      _ref1 = offsets[_i], top = _ref1.top, left = _ref1.left;
3952      if (typeof top === 'string') {
3953        top = parseFloat(top, 10);
3954      }
3955      if (typeof left === 'string') {
3956        left = parseFloat(left, 10);
3957      }
3958      out.top += top;
3959      out.left += left;
3960    }
3961    return out;
3962  };
3963
3964  offsetToPx = function(offset, size) {
3965    if (typeof offset.left === 'string' && offset.left.indexOf('%') !== -1) {
3966      offset.left = parseFloat(offset.left, 10) / 100 * size.width;
3967    }
3968    if (typeof offset.top === 'string' && offset.top.indexOf('%') !== -1) {
3969      offset.top = parseFloat(offset.top, 10) / 100 * size.height;
3970    }
3971    return offset;
3972  };
3973
3974  parseAttachment = parseOffset = function(value) {
3975    var left, top, _ref1;
3976    _ref1 = value.split(' '), top = _ref1[0], left = _ref1[1];
3977    return {
3978      top: top,
3979      left: left
3980    };
3981  };
3982
3983  _Tether = (function() {
3984    _Tether.modules = [];
3985
3986    function _Tether(options) {
3987      this.position = __bind(this.position, this);
3988      var module, _i, _len;
3989      tethers.push(this);
3990      this.history = [];
3991      this.setOptions(options, false);
3992      
3993      for (_i = 0, _len = Tether.modules.length; _i < _len; _i++) {
3994
3995        module = Tether.modules[_i];
3996
3997        if(module.hasOwnProperty('initialize') === false){
3998        	continue;
3999        }
4000
4001        module.initialize.call(this);
4002
4003      }
4004
4005      this.position();
4006    }
4007
4008    _Tether.prototype.getClass = function(key) {
4009      var _ref1, _ref2;
4010      if ((_ref1 = this.options.classes) != null ? _ref1[key] : void 0) {
4011        return this.options.classes[key];
4012      } else if (((_ref2 = this.options.classes) != null ? _ref2[key] : void 0) !== false) {
4013        if (this.options.classPrefix) {
4014          return "" + this.options.classPrefix + "-" + key;
4015        } else {
4016          return key;
4017        }
4018      } else {
4019        return '';
4020      }
4021    };
4022
4023    _Tether.prototype.setOptions = function(options, position) {
4024      var defaults, key, _i, _len, _ref1, _ref2;
4025      this.options = options;
4026      if (position == null) {
4027        position = true;
4028      }
4029      defaults = {
4030        offset: '0 0',
4031        targetOffset: '0 0',
4032        targetAttachment: 'auto auto',
4033        classPrefix: 'tether'
4034      };
4035      this.options = extend(defaults, this.options);
4036      _ref1 = this.options, this.element = _ref1.element, this.target = _ref1.target, this.targetModifier = _ref1.targetModifier;
4037      if (this.target === 'viewport') {
4038        this.target = document.body;
4039        this.targetModifier = 'visible';
4040      } else if (this.target === 'scroll-handle') {
4041        this.target = document.body;
4042        this.targetModifier = 'scroll-handle';
4043      }
4044      _ref2 = ['element', 'target'];
4045      for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
4046        key = _ref2[_i];
4047        if (this[key] == null) {
4048          throw new Error("Tether Error: Both element and target must be defined");
4049        }
4050        if (this[key].jquery != null) {
4051          this[key] = this[key][0];
4052        } else if (typeof this[key] === 'string') {
4053          this[key] = document.querySelector(this[key]);
4054        }
4055      }
4056      addClass(this.element, this.getClass('element'));
4057      addClass(this.target, this.getClass('target'));
4058      if (!this.options.attachment) {
4059        throw new Error("Tether Error: You must provide an attachment");
4060      }
4061      this.targetAttachment = parseAttachment(this.options.targetAttachment);
4062      this.attachment = parseAttachment(this.options.attachment);
4063      this.offset = parseOffset(this.options.offset);
4064      this.targetOffset = parseOffset(this.options.targetOffset);
4065      if (this.scrollParent != null) {
4066        this.disable();
4067      }
4068      if (this.targetModifier === 'scroll-handle') {
4069        this.scrollParent = this.target;
4070      } else {
4071        this.scrollParent = getScrollParent(this.target);
4072      }
4073      if (this.options.enabled !== false) {
4074        return this.enable(position);
4075      }
4076    };
4077
4078    _Tether.prototype.getTargetBounds = function() {
4079      var bounds, fitAdj, hasBottomScroll, height, out, scrollBottom, scrollPercentage, style, target;
4080      if (this.targetModifier != null) {
4081        switch (this.targetModifier) {
4082          case 'visible':
4083            if (this.target === document.body) {
4084              return {
4085                top: pageYOffset,
4086                left: pageXOffset,
4087                height: innerHeight,
4088                width: innerWidth
4089              };
4090            } else {
4091              bounds = getBounds(this.target);
4092              out = {
4093                height: bounds.height,
4094                width: bounds.width,
4095                top: bounds.top,
4096                left: bounds.left
4097              };
4098              out.height = Math.min(out.height, bounds.height - (pageYOffset - bounds.top));
4099              out.height = Math.min(out.height, bounds.height - ((bounds.top + bounds.height) - (pageYOffset + innerHeight)));
4100              out.height = Math.min(innerHeight, out.height);
4101              out.height -= 2;
4102              out.width = Math.min(out.width, bounds.width - (pageXOffset - bounds.left));
4103              out.width = Math.min(out.width, bounds.width - ((bounds.left + bounds.width) - (pageXOffset + innerWidth)));
4104              out.width = Math.min(innerWidth, out.width);
4105              out.width -= 2;
4106              if (out.top < pageYOffset) {
4107                out.top = pageYOffset;
4108              }
4109              if (out.left < pageXOffset) {
4110                out.left = pageXOffset;
4111              }
4112              return out;
4113            }
4114            break;
4115          case 'scroll-handle':
4116            target = this.target;
4117            if (target === document.body) {
4118              target = document.documentElement;
4119              bounds = {
4120                left: pageXOffset,
4121                top: pageYOffset,
4122                height: innerHeight,
4123                width: innerWidth
4124              };
4125            } else {
4126              bounds = getBounds(target);
4127            }
4128            style = getComputedStyle(target);
4129            hasBottomScroll = target.scrollWidth > target.clientWidth || 'scroll' === [style.overflow, style.overflowX] || this.target !== document.body;
4130            scrollBottom = 0;
4131            if (hasBottomScroll) {
4132              scrollBottom = 15;
4133            }
4134            height = bounds.height - parseFloat(style.borderTopWidth) - parseFloat(style.borderBottomWidth) - scrollBottom;
4135            out = {
4136              width: 15,
4137              height: height * 0.975 * (height / target.scrollHeight),
4138              left: bounds.left + bounds.width - parseFloat(style.borderLeftWidth) - 15
4139            };
4140            fitAdj = 0;
4141            if (height < 408 && this.target === document.body) {
4142              fitAdj = -0.00011 * Math.pow(height, 2) - 0.00727 * height + 22.58;
4143            }
4144            if (this.target !== document.body) {
4145              out.height = Math.max(out.height, 24);
4146            }
4147            scrollPercentage = this.target.scrollTop / (target.scrollHeight - height);
4148            out.top = scrollPercentage * (height - out.height - fitAdj) + bounds.top + parseFloat(style.borderTopWidth);
4149            if (this.target === document.body) {
4150              out.height = Math.max(out.height, 24);
4151            }
4152            return out;
4153        }
4154      } else {
4155        return getBounds(this.target);
4156      }
4157    };
4158
4159    _Tether.prototype.clearCache = function() {
4160      return this._cache = {};
4161    };
4162
4163    _Tether.prototype.cache = function(k, getter) {
4164      if (this._cache == null) {
4165        this._cache = {};
4166      }
4167      if (this._cache[k] == null) {
4168        this._cache[k] = getter.call(this);
4169      }
4170      return this._cache[k];
4171    };
4172
4173    _Tether.prototype.enable = function(position) {
4174      if (position == null) {
4175        position = true;
4176      }
4177      addClass(this.target, this.getClass('enabled'));
4178      addClass(this.element, this.getClass('enabled'));
4179      this.enabled = true;
4180      if (this.scrollParent !== document) {
4181        this.scrollParent.addEventListener('scroll', this.position);
4182      }
4183      if (position) {
4184        return this.position();
4185      }
4186    };
4187
4188    _Tether.prototype.disable = function() {
4189      removeClass(this.target, this.getClass('enabled'));
4190      removeClass(this.element, this.getClass('enabled'));
4191      this.enabled = false;
4192      if (this.scrollParent != null) {
4193        return this.scrollParent.removeEventListener('scroll', this.position);
4194      }
4195    };
4196
4197    _Tether.prototype.destroy = function() {
4198      var i, tether, _i, _len, _results;
4199      this.disable();
4200
4201      for (_i = 0, _len = Tether.modules.length; _i < _len; _i++) {
4202
4203        module = Tether.modules[_i];
4204
4205        if(module.hasOwnProperty('destroy') === false){
4206        	continue;
4207        }
4208
4209        module.destroy.call(this);
4210
4211      }
4212
4213      _results = [];
4214      for (i = _i = 0, _len = tethers.length; _i < _len; i = ++_i) {
4215        tether = tethers[i];
4216        if (tether === this) {
4217          tethers.splice(i, 1);
4218          break;
4219        } else {
4220          _results.push(void 0);
4221        }
4222      }
4223      return _results;
4224    };
4225
4226    _Tether.prototype.updateAttachClasses = function(elementAttach, targetAttach) {
4227      var add, all, side, sides, _i, _j, _len, _len1, _ref1,
4228        _this = this;
4229      if (elementAttach == null) {
4230        elementAttach = this.attachment;
4231      }
4232      if (targetAttach == null) {
4233        targetAttach = this.targetAttachment;
4234      }
4235      sides = ['left', 'top', 'bottom', 'right', 'middle', 'center'];
4236      if ((_ref1 = this._addAttachClasses) != null ? _ref1.length : void 0) {
4237        this._addAttachClasses.splice(0, this._addAttachClasses.length);
4238      }
4239      add = this._addAttachClasses != null ? this._addAttachClasses : this._addAttachClasses = [];
4240      if (elementAttach.top) {
4241        add.push("" + (this.getClass('element-attached')) + "-" + elementAttach.top);
4242      }
4243      if (elementAttach.left) {
4244        add.push("" + (this.getClass('element-attached')) + "-" + elementAttach.left);
4245      }
4246      if (targetAttach.top) {
4247        add.push("" + (this.getClass('target-attached')) + "-" + targetAttach.top);
4248      }
4249      if (targetAttach.left) {
4250        add.push("" + (this.getClass('target-attached')) + "-" + targetAttach.left);
4251      }
4252      all = [];
4253      for (_i = 0, _len = sides.length; _i < _len; _i++) {
4254        side = sides[_i];
4255        all.push("" + (this.getClass('element-attached')) + "-" + side);
4256      }
4257      for (_j = 0, _len1 = sides.length; _j < _len1; _j++) {
4258        side = sides[_j];
4259        all.push("" + (this.getClass('target-attached')) + "-" + side);
4260      }
4261      return defer(function() {
4262        if (_this._addAttachClasses == null) {
4263          return;
4264        }
4265        updateClasses(_this.element, _this._addAttachClasses, all);
4266        updateClasses(_this.target, _this._addAttachClasses, all);
4267        return _this._addAttachClasses = void 0;
4268      });
4269    };
4270
4271    _Tether.prototype.position = function(flushChanges) {
4272      var elementPos, elementStyle, height, left, manualOffset, manualTargetOffset, module, next, offset, offsetBorder, offsetParent, offsetParentSize, offsetParentStyle, offsetPosition, ret, scrollLeft, scrollTop, scrollbarSize, side, targetAttachment, targetOffset, targetPos, targetSize, top, width, _i, _j, _len, _len1, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6,
4273        _this = this;
4274      if (flushChanges == null) {
4275        flushChanges = true;
4276      }
4277      if (!this.enabled) {
4278        return;
4279      }
4280      this.clearCache();
4281      targetAttachment = autoToFixedAttachment(this.targetAttachment, this.attachment);
4282      this.updateAttachClasses(this.attachment, targetAttachment);
4283      elementPos = this.cache('element-bounds', function() {
4284        return getBounds(_this.element);
4285      });
4286      width = elementPos.width, height = elementPos.height;
4287      if (width === 0 && height === 0 && (this.lastSize != null)) {
4288        _ref1 = this.lastSize, width = _ref1.width, height = _ref1.height;
4289      } else {
4290        this.lastSize = {
4291          width: width,
4292          height: height
4293        };
4294      }
4295      targetSize = targetPos = this.cache('target-bounds', function() {
4296        return _this.getTargetBounds();
4297      });
4298      offset = offsetToPx(attachmentToOffset(this.attachment), {
4299        width: width,
4300        height: height
4301      });
4302      targetOffset = offsetToPx(attachmentToOffset(targetAttachment), targetSize);
4303      manualOffset = offsetToPx(this.offset, {
4304        width: width,
4305        height: height
4306      });
4307      manualTargetOffset = offsetToPx(this.targetOffset, targetSize);
4308      offset = addOffset(offset, manualOffset);
4309      targetOffset = addOffset(targetOffset, manualTargetOffset);
4310      left = targetPos.left + targetOffset.left - offset.left;
4311      top = targetPos.top + targetOffset.top - offset.top;
4312      _ref2 = Tether.modules;
4313      for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
4314        module = _ref2[_i];
4315
4316        if(module.hasOwnProperty('position') === false){
4317        	continue;
4318        }
4319
4320        ret = module.position.call(this, {
4321          left: left,
4322          top: top,
4323          targetAttachment: targetAttachment,
4324          targetPos: targetPos,
4325          attachment: this.attachment,
4326          elementPos: elementPos,
4327          offset: offset,
4328          targetOffset: targetOffset,
4329          manualOffset: manualOffset,
4330          manualTargetOffset: manualTargetOffset,
4331          scrollbarSize: scrollbarSize
4332        });
4333
4334        if ((ret == null) || typeof ret !== 'object') {
4335          continue;
4336        } else if (ret === false) {
4337          return false;
4338        } else {
4339          top = ret.top, left = ret.left;
4340        }
4341      }
4342      next = {
4343        page: {
4344          top: top,
4345          left: left
4346        },
4347        viewport: {
4348          top: top - pageYOffset,
4349          bottom: pageYOffset - top - height + innerHeight,
4350          left: left - pageXOffset,
4351          right: pageXOffset - left - width + innerWidth
4352        }
4353      };
4354      if (document.body.scrollWidth > window.innerWidth) {
4355        scrollbarSize = this.cache('scrollbar-size', getScrollBarSize);
4356        next.viewport.bottom -= scrollbarSize.height;
4357      }
4358      if (document.body.scrollHeight > window.innerHeight) {
4359        scrollbarSize = this.cache('scrollbar-size', getScrollBarSize);
4360        next.viewport.right -= scrollbarSize.width;
4361      }
4362      if (((_ref3 = document.body.style.position) !== '' && _ref3 !== 'static') || ((_ref4 = document.body.parentElement.style.position) !== '' && _ref4 !== 'static')) {
4363        next.page.bottom = document.body.scrollHeight - top - height;
4364        next.page.right = document.body.scrollWidth - left - width;
4365      }
4366      if (((_ref5 = this.options.optimizations) != null ? _ref5.moveElement : void 0) !== false && (this.targetModifier == null)) {
4367        offsetParent = this.cache('target-offsetparent', function() {
4368          return getOffsetParent(_this.target);
4369        });
4370        offsetPosition = this.cache('target-offsetparent-bounds', function() {
4371          return getBounds(offsetParent);
4372        });
4373        offsetParentStyle = getComputedStyle(offsetParent);
4374        elementStyle = getComputedStyle(this.element);
4375        offsetParentSize = offsetPosition;
4376        offsetBorder = {};
4377        _ref6 = ['Top', 'Left', 'Bottom', 'Right'];
4378        for (_j = 0, _len1 = _ref6.length; _j < _len1; _j++) {
4379          side = _ref6[_j];
4380          offsetBorder[side.toLowerCase()] = parseFloat(offsetParentStyle["border" + side + "Width"]);
4381        }
4382        offsetPosition.right = document.body.scrollWidth - offsetPosition.left - offsetParentSize.width + offsetBorder.right;
4383        offsetPosition.bottom = document.body.scrollHeight - offsetPosition.top - offsetParentSize.height + offsetBorder.bottom;
4384        if (next.page.top >= (offsetPosition.top + offsetBorder.top) && next.page.bottom >= offsetPosition.bottom) {
4385          if (next.page.left >= (offsetPosition.left + offsetBorder.left) && next.page.right >= offsetPosition.right) {
4386            scrollTop = offsetParent.scrollTop;
4387            scrollLeft = offsetParent.scrollLeft;
4388            next.offset = {
4389              top: next.page.top - offsetPosition.top + scrollTop - offsetBorder.top,
4390              left: next.page.left - offsetPosition.left + scrollLeft - offsetBorder.left
4391            };
4392          }
4393        }
4394      }
4395      this.move(next);
4396      this.history.unshift(next);
4397      if (this.history.length > 3) {
4398        this.history.pop();
4399      }
4400      if (flushChanges) {
4401        flush();
4402      }
4403      return true;
4404    };
4405
4406    _Tether.prototype.move = function(position) {
4407      var css, elVal, found, key, moved, offsetParent, point, same, transcribe, type, val, write, writeCSS, _i, _len, _ref1, _ref2,
4408        _this = this;
4409      if (this.element.parentNode == null) {
4410        return;
4411      }
4412      same = {};
4413      for (type in position) {
4414        same[type] = {};
4415        for (key in position[type]) {
4416          found = false;
4417          _ref1 = this.history;
4418          for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
4419            point = _ref1[_i];
4420            if (!within((_ref2 = point[type]) != null ? _ref2[key] : void 0, position[type][key])) {
4421              found = true;
4422              break;
4423            }
4424          }
4425          if (!found) {
4426            same[type][key] = true;
4427          }
4428        }
4429      }
4430      css = {
4431        top: '',
4432        left: '',
4433        right: '',
4434        bottom: ''
4435      };
4436      transcribe = function(same, pos) {
4437        var xPos, yPos, _ref3;
4438        if (((_ref3 = _this.options.optimizations) != null ? _ref3.gpu : void 0) !== false) {
4439          if (same.top) {
4440            css.top = 0;
4441            yPos = pos.top;
4442          } else {
4443            css.bottom = 0;
4444            yPos = -pos.bottom;
4445          }
4446          if (same.left) {
4447            css.left = 0;
4448            xPos = pos.left;
4449          } else {
4450            css.right = 0;
4451            xPos = -pos.right;
4452          }
4453          css[transformKey] = "translateX(" + (Math.round(xPos)) + "px) translateY(" + (Math.round(yPos)) + "px)";
4454          if (transformKey !== 'msTransform') {
4455            return css[transformKey] += " translateZ(0)";
4456          }
4457        } else {
4458          if (same.top) {
4459            css.top = "" + pos.top + "px";
4460          } else {
4461            css.bottom = "" + pos.bottom + "px";
4462          }
4463          if (same.left) {
4464            return css.left = "" + pos.left + "px";
4465          } else {
4466            return css.right = "" + pos.right + "px";
4467          }
4468        }
4469      };
4470      moved = false;
4471      if ((same.page.top || same.page.bottom) && (same.page.left || same.page.right)) {
4472        css.position = 'absolute';
4473        transcribe(same.page, position.page);
4474      } else if ((same.viewport.top || same.viewport.bottom) && (same.viewport.left || same.viewport.right)) {
4475        css.position = 'fixed';
4476        transcribe(same.viewport, position.viewport);
4477      } else if ((same.offset != null) && same.offset.top && same.offset.left) {
4478        css.position = 'absolute';
4479        offsetParent = this.cache('target-offsetparent', function() {
4480          return getOffsetParent(_this.target);
4481        });
4482        if (getOffsetParent(this.element) !== offsetParent) {
4483          defer(function() {
4484            _this.element.parentNode.removeChild(_this.element);
4485            return offsetParent.appendChild(_this.element);
4486          });
4487        }
4488        transcribe(same.offset, position.offset);
4489        moved = true;
4490      } else {
4491        css.position = 'absolute';
4492        transcribe({
4493          top: true,
4494          left: true
4495        }, position.page);
4496      }
4497      if (!moved && this.element.parentNode.tagName !== 'BODY') {
4498        this.element.parentNode.removeChild(this.element);
4499        document.body.appendChild(this.element);
4500      }
4501      writeCSS = {};
4502      write = false;
4503      for (key in css) {
4504        val = css[key];
4505        elVal = this.element.style[key];
4506        if (elVal !== '' && val !== '' && (key === 'top' || key === 'left' || key === 'bottom' || key === 'right')) {
4507          elVal = parseFloat(elVal);
4508          val = parseFloat(val);
4509        }
4510        if (elVal !== val) {
4511          write = true;
4512          writeCSS[key] = css[key];
4513        }
4514      }
4515      if (write) {
4516        return defer(function() {
4517          return extend(_this.element.style, writeCSS);
4518        });
4519      }
4520    };
4521
4522    return _Tether;
4523
4524  })();
4525
4526  Tether.position = position;
4527
4528  this.Tether = extend(_Tether, Tether);
4529
4530}).call(this);
4531
4532(function() {
4533  var BOUNDS_FORMAT, MIRROR_ATTACH, defer, extend, getBoundingRect, getBounds, getOuterSize, getSize, updateClasses, _ref,
4534    __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
4535
4536  _ref = this.Tether.Utils, getOuterSize = _ref.getOuterSize, getBounds = _ref.getBounds, getSize = _ref.getSize, extend = _ref.extend, updateClasses = _ref.updateClasses, defer = _ref.defer;
4537
4538  MIRROR_ATTACH = {
4539    left: 'right',
4540    right: 'left',
4541    top: 'bottom',
4542    bottom: 'top',
4543    middle: 'middle'
4544  };
4545
4546  BOUNDS_FORMAT = ['left', 'top', 'right', 'bottom'];
4547
4548  getBoundingRect = function(tether, to) {
4549    var i, pos, side, size, style, _i, _len;
4550    if (to === 'scrollParent') {
4551      to = tether.scrollParent;
4552    } else if (to === 'window') {
4553      to = [pageXOffset, pageYOffset, innerWidth + pageXOffset, innerHeight + pageYOffset];
4554    }
4555    if (to === document) {
4556      to = to.documentElement;
4557    }
4558    if (to.nodeType != null) {
4559      pos = size = getBounds(to);
4560      style = getComputedStyle(to);
4561      to = [pos.left, pos.top, size.width + pos.left, size.height + pos.top];
4562      for (i = _i = 0, _len = BOUNDS_FORMAT.length; _i < _len; i = ++_i) {
4563        side = BOUNDS_FORMAT[i];
4564        side = side[0].toUpperCase() + side.substr(1);
4565        if (side === 'Top' || side === 'Left') {
4566          to[i] += parseFloat(style["border" + side + "Width"]);
4567        } else {
4568          to[i] -= parseFloat(style["border" + side + "Width"]);
4569        }
4570      }
4571    }
4572    return to;
4573  };
4574
4575  this.Tether.modules.push({
4576    position: function(_arg) {
4577      var addClasses, allClasses, attachment, bounds, changeAttachX, changeAttachY, cls, constraint, eAttachment, height, left, oob, oobClass, p, pin, pinned, pinnedClass, removeClass, side, tAttachment, targetAttachment, targetHeight, targetSize, targetWidth, to, top, width, _i, _j, _k, _l, _len, _len1, _len2, _len3, _len4, _len5, _m, _n, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8,
4578        _this = this;
4579      top = _arg.top, left = _arg.left, targetAttachment = _arg.targetAttachment;
4580      if (!this.options.constraints) {
4581        return true;
4582      }
4583      removeClass = function(prefix) {
4584        var side, _i, _len, _results;
4585        _this.removeClass(prefix);
4586        _results = [];
4587        for (_i = 0, _len = BOUNDS_FORMAT.length; _i < _len; _i++) {
4588          side = BOUNDS_FORMAT[_i];
4589          _results.push(_this.removeClass("" + prefix + "-" + side));
4590        }
4591        return _results;
4592      };
4593      _ref1 = this.cache('element-bounds', function() {
4594        return getBounds(_this.element);
4595      }), height = _ref1.height, width = _ref1.width;
4596      if (width === 0 && height === 0 && (this.lastSize != null)) {
4597        _ref2 = this.lastSize, width = _ref2.width, height = _ref2.height;
4598      }
4599      targetSize = this.cache('target-bounds', function() {
4600        return _this.getTargetBounds();
4601      });
4602      targetHeight = targetSize.height;
4603      targetWidth = targetSize.width;
4604      tAttachment = {};
4605      eAttachment = {};
4606      allClasses = [this.getClass('pinned'), this.getClass('out-of-bounds')];
4607      _ref3 = this.options.constraints;
4608      for (_i = 0, _len = _ref3.length; _i < _len; _i++) {
4609        constraint = _ref3[_i];
4610        if (constraint.outOfBoundsClass) {
4611          allClasses.push(constraint.outOfBoundsClass);
4612        }
4613        if (constraint.pinnedClass) {
4614          allClasses.push(constraint.pinnedClass);
4615        }
4616      }
4617      for (_j = 0, _len1 = allClasses.length; _j < _len1; _j++) {
4618        cls = allClasses[_j];
4619        _ref4 = ['left', 'top', 'right', 'bottom'];
4620        for (_k = 0, _len2 = _ref4.length; _k < _len2; _k++) {
4621          side = _ref4[_k];
4622          allClasses.push("" + cls + "-" + side);
4623        }
4624      }
4625      addClasses = [];
4626      tAttachment = extend({}, targetAttachment);
4627      eAttachment = extend({}, this.attachment);
4628      _ref5 = this.options.constraints;
4629      for (_l = 0, _len3 = _ref5.length; _l < _len3; _l++) {
4630        constraint = _ref5[_l];
4631        to = constraint.to, attachment = constraint.attachment, pin = constraint.pin;
4632        if (attachment == null) {
4633          attachment = '';
4634        }
4635        if (__indexOf.call(attachment, ' ') >= 0) {
4636          _ref6 = attachment.split(' '), changeAttachY = _ref6[0], changeAttachX = _ref6[1];
4637        } else {
4638          changeAttachX = changeAttachY = attachment;
4639        }
4640        bounds = getBoundingRect(this, to);
4641        if (changeAttachY === 'target' || changeAttachY === 'both') {
4642          if (top < bounds[1] && tAttachment.top === 'top') {
4643            top += targetHeight;
4644            tAttachment.top = 'bottom';
4645          }
4646          if (top + height > bounds[3] && tAttachment.top === 'bottom') {
4647            top -= targetHeight;
4648            tAttachment.top = 'top';
4649          }
4650        }
4651        if (changeAttachY === 'together') {
4652          if (top < bounds[1] && tAttachment.top === 'top') {
4653            if (eAttachment.top === 'bottom') {
4654              top += targetHeight;
4655              tAttachment.top = 'bottom';
4656              top += height;
4657              eAttachment.top = 'top';
4658            } else if (eAttachment.top === 'top') {
4659              top += targetHeight;
4660              tAttachment.top = 'bottom';
4661              top -= height;
4662              eAttachment.top = 'bottom';
4663            }
4664          }
4665          if (top + height > bounds[3] && tAttachment.top === 'bottom') {
4666            if (eAttachment.top === 'top') {
4667              top -= targetHeight;
4668              tAttachment.top = 'top';
4669              top -= height;
4670              eAttachment.top = 'bottom';
4671            } else if (eAttachment.top === 'bottom') {
4672              top -= targetHeight;
4673              tAttachment.top = 'top';
4674              top += height;
4675              eAttachment.top = 'top';
4676            }
4677          }
4678          if (tAttachment.top === 'middle') {
4679            if (top + height > bounds[3] && eAttachment.top === 'top') {
4680              top -= height;
4681              eAttachment.top = 'bottom';
4682            } else if (top < bounds[1] && eAttachment.top === 'bottom') {
4683              top += height;
4684              eAttachment.top = 'top';
4685            }
4686          }
4687        }
4688        if (changeAttachX === 'target' || changeAttachX === 'both') {
4689          if (left < bounds[0] && tAttachment.left === 'left') {
4690            left += targetWidth;
4691            tAttachment.left = 'right';
4692          }
4693          if (left + width > bounds[2] && tAttachment.left === 'right') {
4694            left -= targetWidth;
4695            tAttachment.left = 'left';
4696          }
4697        }
4698        if (changeAttachX === 'together') {
4699          if (left < bounds[0] && tAttachment.left === 'left') {
4700            if (eAttachment.left === 'right') {
4701              left += targetWidth;
4702              tAttachment.left = 'right';
4703              left += width;
4704              eAttachment.left = 'left';
4705            } else if (eAttachment.left === 'left') {
4706              left += targetWidth;
4707              tAttachment.left = 'right';
4708              left -= width;
4709              eAttachment.left = 'right';
4710            }
4711          } else if (left + width > bounds[2] && tAttachment.left === 'right') {
4712            if (eAttachment.left === 'left') {
4713              left -= targetWidth;
4714              tAttachment.left = 'left';
4715              left -= width;
4716              eAttachment.left = 'right';
4717            } else if (eAttachment.left === 'right') {
4718              left -= targetWidth;
4719              tAttachment.left = 'left';
4720              left += width;
4721              eAttachment.left = 'left';
4722            }
4723          } else if (tAttachment.left === 'center') {
4724            if (left + width > bounds[2] && eAttachment.left === 'left') {
4725              left -= width;
4726              eAttachment.left = 'right';
4727            } else if (left < bounds[0] && eAttachment.left === 'right') {
4728              left += width;
4729              eAttachment.left = 'left';
4730            }
4731          }
4732        }
4733        if (changeAttachY === 'element' || changeAttachY === 'both') {
4734          if (top < bounds[1] && eAttachment.top === 'bottom') {
4735            top += height;
4736            eAttachment.top = 'top';
4737          }
4738          if (top + height > bounds[3] && eAttachment.top === 'top') {
4739            top -= height;
4740            eAttachment.top = 'bottom';
4741          }
4742        }
4743        if (changeAttachX === 'element' || changeAttachX === 'both') {
4744          if (left < bounds[0] && eAttachment.left === 'right') {
4745            left += width;
4746            eAttachment.left = 'left';
4747          }
4748          if (left + width > bounds[2] && eAttachment.left === 'left') {
4749            left -= width;
4750            eAttachment.left = 'right';
4751          }
4752        }
4753        if (typeof pin === 'string') {
4754          pin = (function() {
4755            var _len4, _m, _ref7, _results;
4756            _ref7 = pin.split(',');
4757            _results = [];
4758            for (_m = 0, _len4 = _ref7.length; _m < _len4; _m++) {
4759              p = _ref7[_m];
4760              _results.push(p.trim());
4761            }
4762            return _results;
4763          })();
4764        } else if (pin === true) {
4765          pin = ['top', 'left', 'right', 'bottom'];
4766        }
4767        pin || (pin = []);
4768        pinned = [];
4769        oob = [];
4770        if (top < bounds[1]) {
4771          if (__indexOf.call(pin, 'top') >= 0) {
4772            top = bounds[1];
4773            pinned.push('top');
4774          } else {
4775            oob.push('top');
4776          }
4777        }
4778        if (top + height > bounds[3]) {
4779          if (__indexOf.call(pin, 'bottom') >= 0) {
4780            top = bounds[3] - height;
4781            pinned.push('bottom');
4782          } else {
4783            oob.push('bottom');
4784          }
4785        }
4786        if (left < bounds[0]) {
4787          if (__indexOf.call(pin, 'left') >= 0) {
4788            left = bounds[0];
4789            pinned.push('left');
4790          } else {
4791            oob.push('left');
4792          }
4793        }
4794        if (left + width > bounds[2]) {
4795          if (__indexOf.call(pin, 'right') >= 0) {
4796            left = bounds[2] - width;
4797            pinned.push('right');
4798          } else {
4799            oob.push('right');
4800          }
4801        }
4802        if (pinned.length) {
4803          pinnedClass = (_ref7 = this.options.pinnedClass) != null ? _ref7 : this.getClass('pinned');
4804          addClasses.push(pinnedClass);
4805          for (_m = 0, _len4 = pinned.length; _m < _len4; _m++) {
4806            side = pinned[_m];
4807            addClasses.push("" + pinnedClass + "-" + side);
4808          }
4809        }
4810        if (oob.length) {
4811          oobClass = (_ref8 = this.options.outOfBoundsClass) != null ? _ref8 : this.getClass('out-of-bounds');
4812          addClasses.push(oobClass);
4813          for (_n = 0, _len5 = oob.length; _n < _len5; _n++) {
4814            side = oob[_n];
4815            addClasses.push("" + oobClass + "-" + side);
4816          }
4817        }
4818        if (__indexOf.call(pinned, 'left') >= 0 || __indexOf.call(pinned, 'right') >= 0) {
4819          eAttachment.left = tAttachment.left = false;
4820        }
4821        if (__indexOf.call(pinned, 'top') >= 0 || __indexOf.call(pinned, 'bottom') >= 0) {
4822          eAttachment.top = tAttachment.top = false;
4823        }
4824        if (tAttachment.top !== targetAttachment.top || tAttachment.left !== targetAttachment.left || eAttachment.top !== this.attachment.top || eAttachment.left !== this.attachment.left) {
4825          this.updateAttachClasses(eAttachment, tAttachment);
4826        }
4827      }
4828      defer(function() {
4829        updateClasses(_this.target, addClasses, allClasses);
4830        return updateClasses(_this.element, addClasses, allClasses);
4831      });
4832      return {
4833        top: top,
4834        left: left
4835      };
4836    }
4837  });
4838
4839}).call(this);
4840
4841(function() {
4842  var defer, getBounds, updateClasses, _ref;
4843
4844  _ref = this.Tether.Utils, getBounds = _ref.getBounds, updateClasses = _ref.updateClasses, defer = _ref.defer;
4845
4846  this.Tether.modules.push({
4847    position: function(_arg) {
4848      var abutted, addClasses, allClasses, bottom, height, left, right, side, sides, targetPos, top, width, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref1, _ref2, _ref3, _ref4, _ref5,
4849        _this = this;
4850      top = _arg.top, left = _arg.left;
4851      _ref1 = this.cache('element-bounds', function() {
4852        return getBounds(_this.element);
4853      }), height = _ref1.height, width = _ref1.width;
4854      targetPos = this.getTargetBounds();
4855      bottom = top + height;
4856      right = left + width;
4857      abutted = [];
4858      if (top <= targetPos.bottom && bottom >= targetPos.top) {
4859        _ref2 = ['left', 'right'];
4860        for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
4861          side = _ref2[_i];
4862          if ((_ref3 = targetPos[side]) === left || _ref3 === right) {
4863            abutted.push(side);
4864          }
4865        }
4866      }
4867      if (left <= targetPos.right && right >= targetPos.left) {
4868        _ref4 = ['top', 'bottom'];
4869        for (_j = 0, _len1 = _ref4.length; _j < _len1; _j++) {
4870          side = _ref4[_j];
4871          if ((_ref5 = targetPos[side]) === top || _ref5 === bottom) {
4872            abutted.push(side);
4873          }
4874        }
4875      }
4876      allClasses = [];
4877      addClasses = [];
4878      sides = ['left', 'top', 'right', 'bottom'];
4879      allClasses.push(this.getClass('abutted'));
4880      for (_k = 0, _len2 = sides.length; _k < _len2; _k++) {
4881        side = sides[_k];
4882        allClasses.push("" + (this.getClass('abutted')) + "-" + side);
4883      }
4884      if (abutted.length) {
4885        addClasses.push(this.getClass('abutted'));
4886      }
4887      for (_l = 0, _len3 = abutted.length; _l < _len3; _l++) {
4888        side = abutted[_l];
4889        addClasses.push("" + (this.getClass('abutted')) + "-" + side);
4890      }
4891      defer(function() {
4892        updateClasses(_this.target, addClasses, allClasses);
4893        return updateClasses(_this.element, addClasses, allClasses);
4894      });
4895      return true;
4896    }
4897  });
4898
4899}).call(this);
4900
4901(function() {
4902  this.Tether.modules.push({
4903    position: function(_arg) {
4904      var left, result, shift, shiftLeft, shiftTop, top, _ref;
4905      top = _arg.top, left = _arg.left;
4906      if (!this.options.shift) {
4907        return;
4908      }
4909      result = function(val) {
4910        if (typeof val === 'function') {
4911          return val.call(this, {
4912            top: top,
4913            left: left
4914          });
4915        } else {
4916          return val;
4917        }
4918      };
4919      shift = result(this.options.shift);
4920      if (typeof shift === 'string') {
4921        shift = shift.split(' ');
4922        shift[1] || (shift[1] = shift[0]);
4923        shiftTop = shift[0], shiftLeft = shift[1];
4924        shiftTop = parseFloat(shiftTop, 10);
4925        shiftLeft = parseFloat(shiftLeft, 10);
4926      } else {
4927        _ref = [shift.top, shift.left], shiftTop = _ref[0], shiftLeft = _ref[1];
4928      }
4929      top += shiftTop;
4930      left += shiftLeft;
4931      return {
4932        top: top,
4933        left: left
4934      };
4935    }
4936  });
4937
4938}).call(this);
4939
4940return this.Tether;
4941
4942}));
4943
4944
4945CargoEditor.addPlugin('tether', function(CargoEditor) {
4946
4947	'use strict'
4948
4949	var _this = this;
4950
4951	Tether.modules.push({
4952		position: function(_arg) {
4953
4954			if(this.options.coverParent === true){
4955
4956				var w = _arg.targetPos.width, 
4957					h = _arg.targetPos.height; 
4958
4959				if(this.options.hasOwnProperty('padding')){
4960					var padding = parseInt(this.options.padding);
4961
4962					if(!isNaN(padding)){
4963						w += padding;
4964						h += padding;
4965					}
4966				}
4967
4968				this.element.style.width = w + 'px';
4969				this.element.style.height = h + 'px';
4970
4971			}
4972			
4973		},
4974		initialize: function() {
4975
4976			var instance = this;
4977
4978			instance.contentChangeEvent = _this.events.on('editor-content-change', function(){
4979				instance.position();
4980			});
4981
4982			instance.globalRepositionEvent = _this.events.on('global-reposition-tethers', function(){
4983				instance.position();
4984			});
4985
4986		},
4987		destroy: function() {
4988
4989			_this.events.off(this.contentChangeEvent);
4990			_this.events.off(this.globalRepositionEvent);
4991
4992		}
4993	});
4994
4995	this.addTether = function(element, target, options){
4996
4997		if(typeof element === "string"){
4998
4999			// element is probably an HTML string. Create nodes.
5000			var htmlNodes = _this.helpers.stringToHTMLNodes(element);
5001
5002			if(htmlNodes.length === 1){
5003				element = htmlNodes[0];
5004			} else {
5005				// multiple nodes, need to wrap them in a parent div.
5006				
5007				element = document.createElement('div');
5008
5009				// append nodes.
5010				[].forEach.call(html, function(rootNode){
5011					element.appendChild(rootNode);
5012				});
5013
5014			}
5015
5016			document.body.appendChild(element);
5017
5018		} else if(typeof element === "object"){
5019			// tether doesn't work with detached nodes.
5020			
5021			if(typeof jQuery === "function" && element instanceof jQuery){
5022				// element is jQuery array.
5023				if(document.contains(element[0]) === false){
5024					document.body.appendChild(element[0]);
5025				}
5026			} else {
5027				// element is proper DOM node.
5028				if(document.contains(element) === false){
5029					document.body.appendChild(element);
5030				}
5031			}
5032
5033		}
5034
5035		var coverParent = false,
5036			offset = '0px 0px',
5037			padding = undefined;
5038
5039		// handle options.
5040		if(typeof options === "object"){
5041
5042			if(options.hasOwnProperty('events') && options.propertyIsEnumerable('events')){
5043				var event;
5044
5045				for(event in options.events){
5046					element.addEventListener(event, options.events[event], false);
5047				}
5048			}
5049
5050			if(options.hasOwnProperty('ignoreEvents')){
5051				if(options.ignoreEvents === true){
5052					element.style.pointerEvents = "none";
5053				}
5054			}
5055
5056			if(options.hasOwnProperty('coverParent')){
5057				if(options.coverParent === true){
5058					coverParent = true;
5059
5060					if(options.hasOwnProperty('padding')){
5061						var padding = parseInt(options.padding);
5062
5063						if(!isNaN(padding)){
5064							offset = (padding / 2) + 'px ' + (padding / 2) + 'px';
5065						}
5066					}
5067				}
5068			}
5069
5070		}
5071
5072		var t = new Tether({
5073			element: element,
5074			target: target,
5075			attachment: 'top left',
5076			targetAttachment: 'top left',
5077			classPrefix: 'ce-tether',
5078			offset: offset,
5079			padding: padding,
5080			coverParent: coverParent
5081		});
5082
5083		var dragster = new CargoEditor.Dragster( element );
5084
5085		element.addEventListener( "dragster:enter", function (event) {
5086
5087			event.dataTransfer = event.detail.dataTransfer;
5088
5089			_this.events.trigger('tether-dragin', t, event);
5090
5091		}, false );
5092
5093		element.addEventListener( "dragster:leave", function (event) {
5094
5095			event.dataTransfer = event.detail.dataTransfer;
5096
5097			_this.events.trigger('tether-dragout', t, event);
5098
5099		}, false );
5100
5101		element.addEventListener( "mouseenter", function (event) {
5102
5103			_this.events.trigger('tether-mouseenter', t, event);
5104
5105		}, false );
5106
5107		element.addEventListener( "mouseleave", function (event) {
5108
5109			_this.events.trigger('tether-mouseleave', t, event);
5110
5111		}, false );
5112
5113		return t;
5114
5115	}
5116
5117});
5118
5119/*
5120(function(){
5121	'use strict'
5122
5123	if(typeof MutationObserver === 'undefined'){
5124		throw 'Unable to init undoManager. MutationObservers are not supported in this browser.';
5125	}
5126
5127	var observer = new MutationObserver(function(mutations) {
5128		mutations.forEach(function(mutation) {
5129			var attr = mutation.attributeName,
5130				attrVal = mutation.target.getAttribute(attr);
5131
5132				if(attrVal === null){ // attribute is not there
5133					detach(mutation.target);
5134
5135				} else if(attrVal.trim().toLowerCase() === 'false' || attrVal === '0') { 
5136
5137					detach(mutation.target);
5138				} else { // all else is considered true
5139					attach(mutation.target);
5140				}
5141
5142		});
5143	});
5144
5145	observer.observe(document.body, {
5146		childList: false,
5147		attributes: true,
5148		attributeOldValue: false,
5149		characterData: false,
5150		characterDataOldValue: false,
5151		subtree: true,
5152		attributeFilter: ['undoscope']
5153	});
5154
5155	function attach(scope) {
5156		//console.log('attaching', scope);
5157	}
5158*/
5159	/**
5160	 * When the undoscope content attribute is removed from an editing host or an element that is not editable, 
5161	 * the user agent must remove all entries in the undo transaction history of the corresponding undo scope without 
5162	 * unapplying or reapplying them and disconnect the corresponding UndoManager for the scope. 
5163	 * 
5164	 * After the removal, the node from which the content attribute is removed and their descendent nodes, 
5165	 * excluding undo scope hosts and their descendents, belong to the undo scope of the closest ancestor 
5166	 * with the undoscope content attribute or of the document.
5167	 */
5168	/*
5169	
5170	function detach(scope) {
5171		//console.log('detaching', scope);
5172	}
5173	
5174}).call(window.undoManager = window.undoManager || {});
5175
5176*/
5177
5178
5179CargoEditor.addPlugin('undo', function(CargoEditor) {
5180
5181	'use strict';
5182
5183	/**
5184	 * Undo.js - A undo/redo framework for JavaScript. {@link https://github.com/jzaefferer/undo}
5185	 *
5186	 * Copyright (c) 2011 Jörn Zaefferer, MIT licensed.
5187	 *
5188	 * @name CargoEditor.Undo
5189	 * @namespace
5190	 * @memberOf CargoEditor
5191	 * @access public
5192	 */
5193	
5194	(function() {
5195
5196		// based on Backbone.js' inherits	      
5197		var ctor = function() {};
5198		var inherits = function(parent, protoProps) {
5199			var child;
5200
5201			if (protoProps && protoProps.hasOwnProperty('constructor')) {
5202				child = protoProps.constructor;
5203			} else {
5204				child = function() {
5205					return parent.apply(this, arguments);
5206				};
5207			}
5208
5209			ctor.prototype = parent.prototype;
5210			child.prototype = new ctor();
5211
5212			if (protoProps) extend(child.prototype, protoProps);
5213
5214			child.prototype.constructor = child;
5215			child.__super__ = parent.prototype;
5216			return child;
5217		};
5218
5219		function extend(target, ref) {
5220			var name, value;
5221			for (name in ref) {
5222				value = ref[name];
5223				if (value !== undefined) {
5224					target[name] = value;
5225				}
5226			}
5227			return target;
5228		};
5229
5230		var Undo;
5231
5232		Undo = this.Undo = {};
5233
5234		/**
5235		 * A Stack is a list of commands. See {@link Undo.Command} for the documentation on these commands.
5236		 *
5237		 * @constructor
5238		 * @name CargoEditor.Undo.Stack
5239		 * @memberOf CargoEditor.Undo
5240		 * @access public
5241		 */
5242
5243		Undo.Stack = function() {
5244			this.commands = [];
5245			this.stackPosition = -1;
5246			this.savePosition = -1;
5247		};
5248
5249		extend(Undo.Stack.prototype, {
5250			execute: function(command) {
5251
5252				this.commands.splice(this.stackPosition + 1, this.commands.length - this.stackPosition);
5253
5254				command.execute();
5255
5256				this.commands.push(command);
5257				this.stackPosition = this.commands.length - 1;
5258				this.changed();
5259			},
5260			undo: function() {
5261				var command = this.commands[this.stackPosition];
5262				if (!command) {
5263					return false;
5264				}
5265				command.undo();
5266
5267				this.stackPosition--;
5268				this.changed();
5269			},
5270			canUndo: function() {
5271				return this.stackPosition !== -1;
5272			},
5273			redo: function() {
5274				var command = this.commands[this.stackPosition + 1];
5275				if (!command) {
5276					return false;
5277				}
5278				command.redo();
5279				this.stackPosition++;
5280				this.changed();
5281			},
5282			canRedo: function() {
5283				return this.stackPosition < (this.commands.length - 1);
5284			},
5285			save: function() {
5286				this.savePosition = this.stackPosition;
5287				this.changed();
5288			},
5289			dirty: function() {
5290				return this.stackPosition != this.savePosition;
5291			},
5292			changed: function() {
5293				// do nothing, override
5294			},
5295			clear: function() {
5296				this.commands = [];
5297				this.stackPosition = -1;
5298				this.savePosition = -1;
5299			},
5300			getStackPosition: function() {
5301				return this.stackPosition;
5302			}
5303		});
5304
5305		/**
5306		 * Command is an abstract class that needs to be implemented. See {@link CargoEditor.EditorInstance.SnapShot} for our implementation
5307		 *
5308		 * @constructor
5309		 * @abstract
5310		 * @name CargoEditor.Undo.Command
5311		 * @memberOf CargoEditor.Undo
5312		 * @access public
5313		 */
5314
5315		Undo.Command = function(name) {
5316			this.name = name;
5317		}
5318
5319		var up = new Error("override me!");
5320
5321		extend(Undo.Command.prototype, {
5322			execute: function() {
5323				throw up;
5324			},
5325			undo: function() {
5326				throw up;
5327			},
5328			redo: function() {
5329				this.execute();
5330			}
5331		});
5332
5333		Undo.Command.extend = function(protoProps) {
5334			var child = inherits(this, protoProps);
5335			child.extend = Undo.Command.extend;
5336			return child;
5337		};
5338
5339	}).call(this);
5340
5341	var _this = this,
5342		undoRange;
5343
5344	function getRangeSummary(){
5345
5346		if(document.getSelection().rangeCount > 0){
5347
5348			var range = getSelection().getRangeAt(0);
5349
5350			if(range !== null){
5351
5352				return {
5353					startContainer: range.startContainer,
5354					startOffset: range.startOffset,
5355					endContainer: range.endContainer,
5356					endOffset: range.endOffset,
5357					collapsed: range.collapsed
5358				}
5359
5360			}
5361
5362		}
5363		
5364	};
5365
5366	var isClean = true,
5367		ignoreChanges = false;
5368
5369	function checkClean(editor){
5370		
5371		if(editor.undoStack.dirty() && isClean){
5372			isClean = false;
5373			_this.events.trigger('editor-dirty', _this);
5374		}
5375
5376		if(!isClean && !editor.undoStack.dirty()){
5377			isClean = true;
5378			_this.events.trigger('editor-clean', _this);
5379		}
5380
5381	}
5382
5383	function attachToEditor(editor){
5384
5385		if(typeof editor.undoStack !== 'object') {
5386
5387			// set up an undo stack for this editor
5388			editor.undoStack = new _this.Undo.Stack();
5389
5390			// Undo.
5391			editor.addKeyListener('ctrl+z/cmd+z/f2', function(event) {
5392
5393				event.preventDefault();
5394				
5395				// Report any queued edits that have not been reported yet
5396				editor.generateMutationSummary();
5397
5398				ignoreChanges = true; // don't report 
5399				
5400				editor.undoStack.undo();
5401				checkClean(editor);
5402
5403				editor.generateMutationSummary();
5404
5405				ignoreChanges = false;
5406
5407			});
5408
5409			// Redo 
5410			editor.addKeyListener('cmd+shift+z/ctrl+shift+z/ctrl+y/f3', function(event) {
5411
5412				event.preventDefault();
5413
5414				// Report any queued edits that have not been reported yet
5415				editor.generateMutationSummary();
5416
5417				ignoreChanges = true;
5418				
5419				editor.undoStack.redo();
5420				checkClean(editor);
5421
5422				editor.generateMutationSummary();
5423				
5424				ignoreChanges = false;
5425
5426			});
5427
5428			editor.addKeyListener('cmd+x/ctrl+x/cmd+v/ctrl+v', function(event) {
5429				//undoRange = getRangeSummary();
5430			});
5431
5432		}
5433
5434	}
5435
5436	_this.events.on('mutation-start', function(editor, ignoreHistory) {
5437		
5438		if(!editor.historyTimeoutInEffect){
5439			undoRange = getRangeSummary();
5440		}
5441
5442	});
5443
5444	/*_this.events.on('cursor-activity', function(editor) {
5445		// Cursor activity changes the range, snapshot it in case a mutation happens directly after it.
5446		
5447		if(!editor.ignoreKeystrokes){
5448			undoRange = getRangeSummary();
5449		}
5450
5451	});*/
5452
5453	_this.events.on('editor-mark-clean', function(editor) {
5454		editor.undoStack.save();
5455		checkClean(editor);
5456	});
5457
5458	_this.events.on('editor-destroyed', function(editor){
5459
5460		// remove undo stuff
5461		editor.undoStack.clear();
5462		editor.undoStack = null;
5463
5464		// key listeners are removed by the editor itself
5465
5466		// this plugin and the events are global, so no need to
5467		// unbind anything as they don't pertain to this specific
5468		// editor. 
5469	});
5470
5471	// create a snapshot when a change is made inside an editor.
5472	_this.events.on('editor-content-change', function(editor, summary, userGenerated) {
5473
5474		if(ignoreChanges) {
5475			return;
5476		}
5477
5478		// store a snapshot for every change that has been made. It's important
5479		// snapshots are made for every node change. This is because snapshots are incremental, they 
5480		// rely on relative locations of DOM nodes in the previous state. One element out of order can cause a massive
5481		// butterfly effect later on.
5482		// 
5483		// We're ignoring attributes as they don't affect DOM positioning but can change a lot (class changes for animations and such)
5484
5485		if( summary.added.length === 0 && 
5486			summary.removed.length === 0 && 
5487			summary.reordered.length === 0 &&
5488			summary.reparented.length === 0 &&
5489			summary.characterDataChanged.length === 0
5490			){
5491
5492			return;
5493		}
5494
5495		editor.undoStack.execute(
5496
5497			new _this.Snapshot(
5498				editor,
5499				summary,
5500				window.scrollY, 
5501				undoRange,
5502				getRangeSummary()
5503			)
5504
5505		);
5506		
5507		checkClean(editor);
5508
5509		undoRange = getRangeSummary();
5510
5511	});
5512
5513	/**
5514	 * This class extends {@link CargoEditor.Undo.Command}.
5515	 * @class
5516	 * @memberOf CargoEditor.plugins.undo
5517	 */
5518		
5519	this.Snapshot = this.Undo.Command.extend({
5520
5521		/**
5522		 * Sets all parameters for this snapshot.
5523		 * @memberOf CargoEditor.EditorInstance.Snapshot
5524		 */
5525
5526		constructor: function(editor, summary, scrollPos, _undoRange, _redoRange) {
5527
5528			//console.log('new undo state');
5529
5530			this.editor = editor;
5531			this.summary = summary;
5532
5533			this.scrollPos = scrollPos;
5534			
5535			this.undoRange = _undoRange;
5536			this.redoRange = _redoRange;
5537
5538			this.rootNode = summary.projection.rootNode;
5539
5540			this.undoSummary = new MutationSummary.NodeMap();
5541			this.redoSummary = new MutationSummary.NodeMap();
5542
5543			this.redoParentCache = new MutationSummary.NodeMap();
5544			this.redoCharacterData = new MutationSummary.NodeMap();
5545
5546			this.redoPreviousSiblingCache = new MutationSummary.NodeMap();
5547			this.undoPreviousSiblingCache = new MutationSummary.NodeMap();
5548
5549			var _this = this;
5550
5551			/**
5552			 * Categorize changes by their parent node
5553			 */
5554
5555			function addNodeToMap(map, key, property, node) {
5556
5557				if (!map.has(key)) {
5558					map.set(key, {
5559						'removed': [],
5560						'added': [],
5561						'moved': [],
5562						'characterDataChanged': []
5563					});
5564				}
5565
5566				var val = map.get(key);
5567
5568				if (!val.hasOwnProperty(property)) {
5569					val[property] = [];
5570				}
5571
5572				val[property].push(node);
5573			}
5574
5575			summary.removed.forEach(function(node) {
5576
5577				addNodeToMap(_this.undoSummary, summary.getOldParentNode(node), 'removed', node);
5578
5579				_this.undoPreviousSiblingCache.set(node, summary.getOldPreviousSibling(node));
5580
5581			});
5582
5583			summary.added.forEach(function(node) {
5584
5585				addNodeToMap(_this.undoSummary, node.parentNode, 'added', node);
5586				addNodeToMap(_this.redoSummary, node.parentNode, 'added', node);
5587
5588				_this.redoPreviousSiblingCache.set(node, node.previousSibling);
5589				_this.redoParentCache.set(node, node.parentNode);
5590
5591			});
5592
5593			summary.reparented.concat(summary.reordered).forEach(function(node) {
5594
5595				addNodeToMap(_this.undoSummary, summary.getOldParentNode(node), 'moved', node);
5596				addNodeToMap(_this.redoSummary, node.parentNode, 'moved', node);
5597
5598				_this.redoPreviousSiblingCache.set(node, node.previousSibling);
5599				_this.undoPreviousSiblingCache.set(node, summary.getOldPreviousSibling(node));
5600				_this.redoParentCache.set(node, node.parentNode);
5601
5602			});
5603
5604			summary.characterDataChanged.forEach(function(node) {
5605
5606				_this.redoCharacterData.set(node, node.data);
5607
5608			});
5609
5610		},
5611		
5612		execute: function() {
5613
5614			//console.log('save');
5615
5616		},
5617
5618		/**
5619		 * Replaces the element's HTML with the HTML as it was one operation before this snapshot was made.
5620		 * Also restores the scroll position and selection at that point in t
5620ime.
5621		 *
5622		 * @memberOf CargoEditor.EditorInstance.Snapshot
5623		 */
5624
5625		undo: function() {
5626
5627			window.scrollTo(0, this.scrollPos);
5628
5629			var _this = this,
5630				oldPreviousSibling,
5631				changesForNode,
5632				referenceIndex;
5633
5634			function restoreNodesFromTopDown(currentNode) {
5635
5636				// Check if anything happened to this node's contents.
5637				if (_this.undoSummary.has(currentNode)) {
5638
5639					// retrieve the changes
5640					changesForNode = _this.undoSummary.get(currentNode);
5641
5642					// Delete added nodes
5643					changesForNode.added.forEach(function(node) {
5644						if (node.parentNode !== null)
5645							node.parentNode.removeChild(node);
5646					});
5647
5648					// insert nodes
5649					changesForNode.moved.concat(changesForNode.removed).forEach(function(node, i, context) {
5650
5651						oldPreviousSibling = _this.undoPreviousSiblingCache.get(node),
5652						referenceIndex = context.indexOf(oldPreviousSibling);
5653
5654						// The array is not in the right order. Switch the reference and current node 
5655						// to ensure nodes are inserted after their reference nodes have been inserted
5656						// in the right place
5657						while (referenceIndex > i) {
5658
5659							node = context[referenceIndex];
5660							oldPreviousSibling = _this.undoPreviousSiblingCache.get(node);
5661
5662							// swap the nodes around
5663							context[referenceIndex] = context[i];
5664							context[i] = node;
5665
5666							// check the index of the new reference node
5667							referenceIndex = context.indexOf(oldPreviousSibling);
5668
5669						}
5670
5671						currentNode.insertBefore(node, oldPreviousSibling ? oldPreviousSibling.nextSibling : currentNode.firstChild);
5672
5673					});
5674
5675				}
5676
5677				// Loop through all child nodes to check for changes.
5678				for (var i = 0; i < currentNode.childNodes.length; i++) {
5679					restoreNodesFromTopDown(currentNode.childNodes[i]);
5680				}
5681
5682			}
5683
5684			restoreNodesFromTopDown(_this.rootNode);
5685
5686			_this.summary.characterDataChanged.forEach(function(node) {
5687				node.data = _this.summary.getOldCharacterData(node);
5688			});
5689
5690			//console.log('restoring undo range');
5691			_this.restoreRange(this.undoRange);
5692
5693		},
5694
5695		/**
5696		 * Replaces the element's HTML with the HTML as it was when this snapshot was made.
5697		 * Also restores the scroll position and selection at that point in time.
5698		 *
5699		 * @memberOf CargoEditor.EditorInstance.Snapshot
5700		 */
5701
5702		redo: function() {
5703
5704			window.scrollTo(0, this.scrollPos);
5705
5706			var _this = this,
5707				oldPreviousSibling,
5708				changesForNode,
5709				referenceIndex;
5710
5711			// Delete added nodes
5712			_this.summary.removed.forEach(function(node) {
5713				if (node.parentNode !== null)
5714					node.parentNode.removeChild(node);
5715			});
5716
5717			function restoreNodesFromTopDown(currentNode) {
5718
5719				// Check if anything happened to this node's contents.
5720				if (_this.redoSummary.has(currentNode)) {
5721
5722					// retrieve the changes
5723					changesForNode = _this.redoSummary.get(currentNode);
5724
5725					// insert nodes
5726					changesForNode.moved.concat(changesForNode.added).forEach(function(node, i, context) {
5727
5728						oldPreviousSibling = _this.redoPreviousSiblingCache.get(node),
5729						referenceIndex = context.indexOf(oldPreviousSibling);
5730
5731						// The array is not in the right order. Switch the reference and current node 
5732						// to ensure nodes are inserted after their reference nodes have been inserted
5733						// in the right place.
5734						while (referenceIndex > i) {
5735
5736							node = context[referenceIndex];
5737							oldPreviousSibling = _this.redoPreviousSiblingCache.get(node);
5738
5739							// swap the nodes around
5740							context[referenceIndex] = context[i];
5741							context[i] = node;
5742
5743							// check the index of the new reference node
5744							referenceIndex = context.indexOf(oldPreviousSibling);
5745
5746						}
5747
5748						currentNode.insertBefore(node, oldPreviousSibling ? oldPreviousSibling.nextSibling : currentNode.firstChild);
5749
5750					});
5751				}
5752
5753				// Loop through all child nodes to check for changes.
5754				for (var i = 0; i < currentNode.childNodes.length; i++) {
5755					restoreNodesFromTopDown(currentNode.childNodes[i]);
5756				}
5757
5758			}
5759
5760			restoreNodesFromTopDown(_this.rootNode);
5761
5762			// Restore characterData
5763			_this.summary.characterDataChanged.forEach(function(node) {
5764				node.data = _this.redoCharacterData.get(node);
5765			});
5766
5767			//console.log('restoring redo range');
5768			_this.restoreRange(this.redoRange);
5769
5770		},
5771
5772		restoreRange: function(oldRange) {
5773
5774			if(oldRange === undefined){
5775				return;
5776			}
5777
5778			var currRange = getRangeSummary();
5779
5780			if(currRange === undefined) {
5781				return;
5782			}
5783
5784			// Don't want a botched selection restore attempt to break the whole script.
5785			try {
5786				var selection = getSelection(),
5787					range = document.createRange();
5788
5789				range.setStart(oldRange.startContainer, oldRange.startOffset);
5790				range.setEnd(oldRange.endContainer, oldRange.endOffset);
5791
5792				if(oldRange.collapsed) {
5793					range.collapse();
5794				}
5795
5796				selection.removeAllRanges();
5797				selection.addRange(range);
5798
5799				// fire cursor activity if the range has changed.
5800				if( oldRange.startOffset !== currRange.startOffset || 
5801					oldRange.endOffset !== currRange.endOffset ||
5802					oldRange.startContainer !== currRange.startContainer || 
5803					oldRange.endOffset !== currRange.endOffset ){
5804
5805						_this.events.trigger('cursor-activity', this.editor);
5806
5807				} 
5808
5809			} catch (e) {
5810				console.warn(e);
5811			}
5812
5813			undoRange = currRange;
5814
5815		}
5816
5817	});
5818
5819	// Attach the plugin to any editors that might have instanciated already
5820	CargoEditor.getEditorInstances().forEach(function(editor){
5821		attachToEditor.call(_this, editor);
5822	});
5823
5824	// attach the plugin to editors that instanciate at a later time
5825	_this.events.on('editor-instanciated', function(editor) {
5826		attachToEditor.call(_this, editor);
5827	});
5828
5829});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.