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, ' '); 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.