1 (function (GCN) { 2 3 'use strict'; 4 5 /** 6 * @private 7 * @const 8 * @type {string} 9 */ 10 var GCN_REPOSITORY_ID = 'com.gentics.aloha.GCN.Page'; 11 12 /** 13 * @private 14 * @const 15 * @type {object.<string, boolean>} Default page settings. 16 */ 17 var DEFAULT_SETTINGS = { 18 // Load folder information 19 folder: true, 20 21 // Lock page when loading it 22 update: true, 23 24 // Have language variants be included in the page response. 25 langvars: true, 26 27 // Have page variants be included in the page response. 28 pagevars: true 29 }; 30 31 /** 32 * Searches for the an Aloha editable object of the given id. 33 * 34 * @TODO: Once Aloha.getEditableById() is patched to not cause an 35 * JavaScript exception if the element for the given ID is not found 36 * then we can deprecate this function and use Aloha's instead. 37 * 38 * @static 39 * @param {string} id Id of Aloha.Editable object to find. 40 * @return {Aloha.Editable=} The editable object, if wound; otherwise null. 41 */ 42 function getAlohaEditableById(id) { 43 var Aloha = (typeof window !== 'undefined') && window.Aloha; 44 if (!Aloha) { 45 return null; 46 } 47 48 // If the element is a textarea then route to the editable div. 49 var element = jQuery('#' + id); 50 if (element.length && 51 element[0].nodeName.toLowerCase() === 'textarea') { 52 id += '-aloha'; 53 } 54 55 var editables = Aloha.editables; 56 var j = editables.length; 57 while (j) { 58 if (editables[--j].getId() === id) { 59 return editables[j]; 60 } 61 } 62 63 return null; 64 } 65 66 /** 67 * Checks whether the given tag is a magic link block. 68 * 69 * @private 70 * @static 71 * @param {GCN.Tag} tag Must be a tag that has already been fetched. 72 * @param {object} constructs Set of constructs. 73 * @return {boolean} True if the given tag has the magic link constructId. 74 */ 75 function isMagicLinkTag(tag, constructs) { 76 return !!(constructs[GCN.settings.MAGIC_LINK] && 77 (constructs[GCN.settings.MAGIC_LINK].constructId === 78 tag.prop('constructId'))); 79 } 80 81 /** 82 * Checks whether or not the given block has a corresponding element in the 83 * document DOM. 84 * 85 * @private 86 * @static 87 * @param {object} 88 * @return {boolean} True if an inline element for this block exists. 89 */ 90 function hasInlineElement(block) { 91 return 0 < jQuery('#' + block.element).length; 92 } 93 94 /** 95 * For a given list of editables and a list of blocks, determine in which 96 * editable each block was rendered. The result is map with a list of 97 * blocks mapped against the id of the rendered editable element. 98 * 99 * @param {string} content The rendered content in which the list of 100 * editables, and blocks are contained. 101 * @param {Array.<object>} editables A list of editables contained in the 102 * rendered content. 103 * @param {Array.<object>} blocks A list of blocks containd in content 104 * string string. 105 * @return {object<string, <Array>} A object whose keys are editable ids, 106 * and whose values are Array of block 107 * contained in the corresponding 108 * editable. 109 */ 110 function getMapOfBlocksToEditables(content, editables, blocks) { 111 var i; 112 var $content = jQuery('<div>' + content + '</div>'); 113 var map = {}; 114 var editableElementId; 115 116 var selection = []; 117 for (i = 0; i < editables.length; i++) { 118 selection.push('#' + editables[i].element); 119 } 120 121 var markerClass = 'aloha-editable-tmp-marker__'; 122 var $editables = $content.find(selection.join(',')); 123 $editables.addClass(markerClass); 124 125 var $block; 126 var $parent; 127 for (i = 0; i < blocks.length; i++) { 128 $block = $content.find('#' + blocks[i].element); 129 if ($block.length) { 130 $parent = $block.parents('.' + markerClass); 131 if ($parent.length) { 132 editableElementId = $parent.attr('id'); 133 if (editableElementId) { 134 if (!map[editableElementId]) { 135 map[editableElementId] = []; 136 } 137 map[editableElementId].push(blocks[i]); 138 } 139 } 140 } 141 } 142 143 return map; 144 } 145 146 /** 147 * @private 148 * @const 149 * @type {number} 150 */ 151 //var TYPE_ID = 10007; 152 153 /** 154 * @private 155 * @const 156 * @type {Enum} 157 */ 158 var STATUS = { 159 160 // page was not found in the database 161 NOTFOUND: -1, 162 163 // page is locally modified and not yet (re-)published 164 MODIFIED: 0, 165 166 // page is marked to be published (dirty) 167 TOPUBLISH: 1, 168 169 // page is published and online 170 PUBLISHED: 2, 171 172 // Page is offline 173 OFFLINE: 3, 174 175 // Page is in the queue (publishing of the page needs to be affirmed) 176 QUEUE: 4, 177 178 // page is in timemanagement and outside of the defined timespan 179 // (currently offline) 180 TIMEMANAGEMENT: 5, 181 182 // page is to be published at a given time (not yet) 183 TOPUBLISH_AT: 6 184 }; 185 186 /** 187 * Given a link, will read the data-gentics-aloha-object-id attribute and 188 * from it, will determine the backend object that was selected by the 189 * repository browser. 190 * 191 * @param {jQuery.<HTMLElement>} link A link in an editable. 192 * @return {Object} 193 * A map containing the gtxalohapagelink part keyword and 194 * value. The keyword may be either be 'url' or 'fileurl' 195 * depending on the type of object linked to. The value may 196 * be a string url ('http://...') for external links or an 197 * integer for internal links. 198 */ 199 function getRepositoryLinkAsPart(link) { 200 var data = link.attr('data-gentics-aloha-object-id'); 201 var value; 202 var keyword; 203 if (data) { 204 var parts = data.split('.'); 205 if (parts.length === 2) { 206 if (parts[0] === '10008' || parts[0] === '10011') { 207 keyword = 'fileurl'; 208 } else { // assumes 10007 for page links 209 keyword = 'url'; 210 } 211 value = parts[1]; 212 } else { 213 keyword = 'url'; 214 value = data; 215 } 216 if (null !== value && typeof value !== 'undefined') { 217 value = parseInt(value, 10); 218 } 219 } else { 220 keyword = 'url'; 221 value = link.attr('data-gentics-gcn-url'); 222 } 223 return { 224 keyword: keyword, 225 value: value 226 }; 227 } 228 229 /** 230 * @class 231 * @name PageAPI 232 * @extends ContentObjectAPI 233 * @extends TagContainerAPI 234 * 235 * Page object information can be extened using the default REST-API. 236 * options: 237 * 238 * - update: true 239 * Whether the page should be locked in the backend when loading it. 240 * default: true 241 * 242 * - template: true 243 * Whether the template information should be embedded in the page object. 244 * default: true 245 * 246 * - folder: true, 247 * Whether the folder information should be embedded in the page object. 248 * default: true 249 * WARNING: do not turn this option off - it will leave the API in a broken 250 * state. 251 * 252 * - langvars: false, 253 * When the language variants shall be embedded in the page response. 254 * default: false 255 256 * - workflow: false, 257 * When the workflow information shall be embedded in the page response. 258 * default: false 259 260 * - pagevars: false, 261 * When the page variants shall be embedded in the page response. Page 262 * variants will contain folder information. 263 * default: false 264 * 265 * - translationstatus: false 266 * Will return information on the page's translation status. 267 * default: false 268 */ 269 var PageAPI = GCN.defineChainback({ 270 /** @lends PageAPI */ 271 272 __chainbacktype__: 'PageAPI', 273 _extends: [ GCN.ContentObjectAPI, GCN.TagContainerAPI ], 274 _type: 'page', 275 276 /** 277 * @private 278 * @type {Array.<object>} A hash set of block tags belonging to this 279 * content object. This set is added to when 280 * this page's tags are rendered. 281 */ 282 _blocks: {}, 283 284 /** 285 * @private 286 * @type {Array.<object>} A hash set of editable tags belonging to this 287 * content object. This set is added to when 288 * this page's tags are rendered. 289 */ 290 _editables: {}, 291 292 /** 293 * @type {Array.string} Writable properties for the page object. 294 */ 295 WRITEABLE_PROPS: ['cdate', 296 'description', 297 'fileName', 298 'folderId', // @TODO Check if moving a page is 299 // implemented correctly. 300 'name', 301 'priority', 302 'templateId'], 303 304 /** 305 * Gets all blocks in this page. Will return an array of all block 306 * objects found in the page AFTER they have been rendered using an 307 * `edit()' call for a contenttag. 308 * NOTE: If you have just loaded the page and not used the `edit()' 309 * method for any tag the array will be empty. Only those blocks 310 * that have been initialized using `edit()' will be available. 311 * 312 * @return {Array.<object>} Array of block objects. 313 */ 314 '!blocks': function () { 315 return this._blocks; 316 }, 317 318 /** 319 * Looks for a block with the given id in the `_blocks' array. 320 * 321 * @private 322 * @param {string} id The block's id. 323 * @return {?object} The block data object. 324 */ 325 '!_getBlockById': function (id) { 326 return this._blocks[id]; 327 }, 328 329 /** 330 * Maps the received editables into this content object's `_editable' 331 * hash. 332 * 333 * @private 334 * @param {Array.<object>} editables An set of object representing 335 * editable tags that have been 336 * rendered. 337 */ 338 '!_storeRenderedEditables': function (editables) { 339 if (!this.hasOwnProperty('_editables')) { 340 this._editables = {}; 341 } 342 343 var j = editables && editables.length; 344 345 while (j) { 346 this._editables[editables[--j].element] = editables[j]; 347 } 348 }, 349 350 /** 351 * Maps received blocks of this content object into the `_blocks' hash. 352 * 353 * @private 354 * @param {Array.<object>} blocks An set of object representing 355 * block tags that have been rendered. 356 */ 357 '!_storeRenderedBlocks': function (blocks) { 358 if (!this.hasOwnProperty('_blocks')) { 359 this._blocks = {}; 360 } 361 362 var j = blocks && blocks.length; 363 364 while (j) { 365 this._blocks[blocks[--j].element] = blocks[j]; 366 } 367 }, 368 369 /** 370 * @override 371 */ 372 '!_processRenderedTags': function (data) { 373 var tags = this._getEditablesAndBlocks(data); 374 var map = getMapOfBlocksToEditables(data.content, tags.editables, 375 tags.blocks); 376 377 this._storeRenderedEditables(tags.editables); 378 this._storeRenderedBlocks(tags.blocks); 379 380 var editableId; 381 for (editableId in map) { 382 if (map.hasOwnProperty(editableId) && 383 this._editables[editableId]) { 384 if (!this._editables[editableId]._gcnContainedBlocks) { 385 this._editables[editableId]._gcnContainedBlocks = 386 map[editableId]; 387 } else { 388 this._editables[editableId]._gcnContainedBlocks = 389 this._editables[editableId]._gcnContainedBlocks 390 .concat(map[editableId]); 391 } 392 } 393 } 394 395 return tags; 396 }, 397 398 /** 399 * Processes rendered tags, and update the `_blocks' and `_editables' 400 * array accordingly. This function is called during pre-saving to 401 * update this page's editable tags. 402 * 403 * @private 404 */ 405 '!_prepareTagsForSaving': function (success, error) { 406 if (!this.hasOwnProperty('_deletedBlocks')) { 407 this._deletedBlocks = []; 408 } 409 410 var editableId; 411 var containedBlocks = []; 412 for (editableId in this._editables) { 413 if (this._editables.hasOwnProperty(editableId)) { 414 if (this._editables[editableId]._gcnContainedBlocks) { 415 containedBlocks = containedBlocks.concat( 416 this._editables[editableId]._gcnContainedBlocks 417 ); 418 } 419 } 420 } 421 422 var blocks = []; 423 var j = containedBlocks.length; 424 while (j--) { 425 if (this._blocks[containedBlocks[j]]) { 426 blocks.push(containedBlocks[j]); 427 } 428 } 429 430 var that = this; 431 this._addNewLinkBlocks(function () { 432 GCN.Admin.constructs(function (constructs) { 433 that._removeOldLinkBlocks(blocks, constructs, function () { 434 that._removeUnusedLinkBlocks(blocks, constructs, function () { 435 that._updateEditableBlocks(); 436 if (success) { 437 success(); 438 } 439 }, error); 440 }, error); 441 }, error); 442 }, error); 443 }, 444 445 /** 446 * Removes any link blocks that existed in rendered tags, but have 447 * since been removed by the user while editing. 448 * 449 * @private 450 * @param {Array.<object>} blocks An array of blocks belonging to this 451 * page. 452 * @param {object} constrcts A set of constructs. 453 * @param {function} success 454 * @param {function(GCNError):boolean=} error Optional custom error 455 * handler. 456 */ 457 '!_removeUnusedLinkBlocks': function (blocks, constructs, success, 458 error) { 459 if (0 === blocks.length) { 460 if (success) { 461 success(); 462 } 463 return; 464 } 465 466 var j = blocks.length; 467 var numToProcess = j; 468 var that = this; 469 470 var onProcess = function () { 471 if (0 === --numToProcess) { 472 if (success) { 473 success(); 474 } 475 } 476 }; 477 478 var onError = function (GCNError) { 479 if (error) { 480 error(GCNError); 481 } 482 return; 483 }; 484 485 var createBlockTagProcessor = function (block) { 486 return function (tag) { 487 if (isMagicLinkTag(tag, constructs) && 488 !hasInlineElement(block)) { 489 that._deletedBlocks.push(block.tagname); 490 } 491 onProcess(); 492 }; 493 }; 494 495 while (j--) { 496 this.tag(blocks[j].tagname, 497 createBlockTagProcessor(blocks[j]), onError); 498 } 499 }, 500 501 /** 502 * Adds any newly created link blocks into this page object. This is 503 * done by looking for all link blocks that do not have corresponding 504 * tag in this object's `_blocks' list. For each anchor tag we find, 505 * create a tag for it and, add it in the list of tags. 506 * 507 * @private 508 * @param {function} success Function to invoke if this function 509 * successeds. 510 * @param {function(GCNError):boolean=} error Optional custom error 511 * handler. 512 */ 513 '!_addNewLinkBlocks': function (success, error) { 514 // Limit the search for links to be done only withing rendered 515 // editables. 516 var id; 517 var $editables = jQuery(); 518 for (id in this._editables) { 519 if (this._editables.hasOwnProperty(id)) { 520 $editables = $editables.add('#' + id); 521 } 522 } 523 524 var selector = [ 525 'a[data-gentics-aloha-repository="com.gentics.aloha.GCN.Page"]', 526 'a[data-GENTICS-aloha-repository="com.gentics.aloha.GCN.Page"]', 527 'a[data-gentics-gcn-url]' 528 ].join(','); 529 530 var links = $editables.find(selector); 531 if (0 === links.length) { 532 if (success) { 533 success(); 534 } 535 return; 536 } 537 538 var link; 539 var j = links.length; 540 var numToProcess = j; 541 var that = this; 542 543 var onProcessed = function () { 544 if (0 === --numToProcess) { 545 success(); 546 } 547 }; 548 549 var onError = function (GCNError) { 550 if (error) { 551 error(GCNError); 552 } 553 return; 554 }; 555 556 var createOnEditHandler = function (link) { 557 return function (html, tag) { 558 link.attr('id', jQuery(html).attr('id')); 559 var part = getRepositoryLinkAsPart(link); 560 tag.part(part.keyword, part.value); 561 onProcessed(); 562 }; 563 }; 564 565 var tag; 566 567 while (j--) { 568 link = links.eq(j); 569 if (link.attr('data-gcnignore') === true) { 570 onProcessed(); 571 } else if (this._getBlockById(link.attr('id'))) { 572 tag = this.tag(this._getBlockById(link.attr('id')).tagname); 573 tag.part('text', link.html()); 574 var part = getRepositoryLinkAsPart(link); 575 tag.part(part.keyword, part.value); 576 onProcessed(); 577 } else { 578 this.createTag(GCN.settings.MAGIC_LINK, link.html()) 579 .edit(createOnEditHandler(link), onError); 580 } 581 } 582 }, 583 584 /** 585 * Any links that change from internal GCN links to external links will 586 * have their corresponding blocks added to the '_deletedBlocks' list 587 * since they these links no longer need to be tracked. Any tags in 588 * this list will be removed during saving. 589 * 590 * @private 591 * @param {Array.<object>} blocks An array of blocks belonging to this 592 * page. 593 * @param {object} constrcts A set of constructs. 594 * @param {function} success 595 * @param {function(GCNError):boolean=} error Optional custom error 596 * handler. 597 */ 598 '!_removeOldLinkBlocks': function (blocks, constructs, success, error) { 599 if (0 === blocks.length) { 600 if (success) { 601 success(); 602 } 603 return; 604 } 605 606 var $links = jQuery('a'); 607 var j = blocks.length; 608 var numToProcess = j; 609 var that = this; 610 611 var onProcess = function () { 612 if (0 === --numToProcess) { 613 if (success) { 614 success(); 615 } 616 } 617 }; 618 619 var onError = function (GCNError) { 620 if (error) { 621 error(GCNError); 622 } 623 return; 624 }; 625 626 var createBlockTagProcessor = function (block) { 627 return function (tag) { 628 if (!isMagicLinkTag(tag, constructs)) { 629 onProcess(); 630 return; 631 } 632 633 var a = $links.filter('#' + block.element); 634 635 if (a.length) { 636 var isExternal = (GCN_REPOSITORY_ID !== 637 a.attr('data-gentics-aloha-repository')) && 638 !a.attr('data-gentics-gcn-url'); 639 640 // An external tag was found. Stop tracking it and 641 // remove it from the list of blocks. 642 if (isExternal) { 643 a.removeAttr('id'); 644 that._deletedBlocks.push(block.tagname); 645 delete that._blocks[block.element]; 646 } 647 648 // No anchor tag was found for this block. Add it to the 649 // "delete" list. 650 } else { 651 that._deletedBlocks.push(block.tagname); 652 delete that._blocks[block.element]; 653 } 654 655 onProcess(); 656 }; 657 }; 658 659 while (j) { 660 this.tag(blocks[--j].tagname, 661 createBlockTagProcessor(blocks[j]), onError); 662 } 663 }, 664 665 /** 666 * Writes the contents of editables back into their corresponding tags. 667 * If a corresponding tag cannot be found for an editable, a new one 668 * will be created for it. 669 * 670 * A reference for each editable tag is then added to the `_shadow' 671 * object in order that the tag will be sent with the save request. 672 * 673 * @private 674 */ 675 '!_updateEditableBlocks': function () { 676 var element; 677 var elementId; 678 var editable; 679 var editables = this._editables; 680 var tags = this._data.tags; 681 var tagname; 682 var html; 683 var alohaEditable; 684 var cleanElement; 685 var customSerializer; 686 687 // ASSERT(tag) 688 689 for (elementId in editables) { 690 if (editables.hasOwnProperty(elementId)) { 691 editable = editables[elementId]; 692 element = jQuery('#' + elementId); 693 694 // If this editable has no element that was placed in the 695 // DOM, then do not attempt to update it. 696 if (0 === element.length) { 697 continue; 698 } 699 700 tagname = editable.tagname; 701 702 if (!tags[tagname]) { 703 tags[tagname] = { 704 name : tagname, 705 active : true, 706 properties : {} 707 }; 708 } else { 709 // make sure, that all tags (which relate to editables) 710 // are activated 711 tags[tagname].active = true; 712 } 713 714 // If the editable element has been `aloha()'fied, then we 715 // need to use `getContents()' from is corresponding 716 // Aloha.Editable object in order to get clean HTML. 717 718 alohaEditable = getAlohaEditableById(elementId); 719 720 if (alohaEditable) { 721 // Avoid the unnecessary overhead of custom editable 722 // serialization by calling html ourselves. 723 cleanElement = jQuery('<div>') 724 .append(alohaEditable.getContents(true)); 725 alohaEditable.setUnmodified(); 726 // Apply the custom editable serialization as the last step. 727 customSerializer = window.Aloha.Editable.getContentSerializer(); 728 html = this.encode(cleanElement, customSerializer); 729 } else { 730 html = this.encode(element); 731 } 732 733 tags[tagname].properties[editable.partname] = { 734 stringValue: html, 735 type: 'RICHTEXT' 736 }; 737 738 this._update('tags.' + GCN.escapePropertyName(tagname), 739 tags[tagname]); 740 } 741 } 742 }, 743 744 /** 745 * @see ContentObjectAPI.!_loadParams 746 */ 747 '!_loadParams': function () { 748 return jQuery.extend(DEFAULT_SETTINGS, this._settings); 749 }, 750 751 /** 752 * Get this page's template. 753 * 754 * @public 755 * @function 756 * @name template 757 * @memberOf PageAPI 758 * @param {funtion(TemplateAPI)=} success Optional callback to receive 759 * a {@link TemplateAPI} object 760 * as the only argument. 761 * @param {function(GCNError):boolean=} error Optional custom error 762 * handler. 763 * @return {TemplateAPI} This page's parent template. 764 */ 765 '!template': function (success, error) { 766 var id = this._fetched ? this.prop('templateId') : null; 767 return this._continue(GCN.TemplateAPI, id, success, error); 768 }, 769 770 /** 771 * @override 772 * @see ContentObjectAPI._save 773 */ 774 '!_save': function (settings, success, error) { 775 var that = this; 776 this._fulfill(function () { 777 that._read(function () { 778 var fork = that._fork(); 779 fork._prepareTagsForSaving(function () { 780 fork._persist(settings, function () { 781 if (success) { 782 that._invoke(success, [that]); 783 } 784 fork._merge(); 785 }, error); 786 }, error); 787 }, error); 788 }, error); 789 }, 790 791 //--------------------------------------------------------------------- 792 // Surface the tag container methods that are applicable for GCN page 793 // objects. 794 //--------------------------------------------------------------------- 795 796 /** 797 * Creates a tag of a given tagtype in this page. 798 * 799 * Exmaple: 800 * <pre> 801 * createTag('link', 'http://www.gentics.com', onSuccess, onError); 802 * </pre> 803 * or 804 * <pre> 805 * createTag('link', onSuccess, onError); 806 * </pre> 807 * 808 * @public 809 * @function 810 * @name createTag 811 * @memberOf PageAPI 812 * @param {string|number} construct The name of the construct on which 813 * the tag to be created should be 814 * derived from. Or the id of that 815 * @param {string=} magicValue Optional property that will override the 816 * default values of this tag type. 817 * @param {function(TagAPI)=} success Optional callback that will 818 * receive the newly created tag as 819 * its only argument. 820 * @param {function(GCNError):boolean=} error Optional custom error 821 * handler. 822 * @return {TagAPI} The newly created tag. 823 * @throws INVALID_ARGUMENTS 824 */ 825 '!createTag': function () { 826 return this._createTag.apply(this, arguments); 827 }, 828 829 /** 830 * Deletes the specified tag from this page. 831 * 832 * @public 833 * @function 834 * @name removeTag 835 * @memberOf PageAPI 836 * @param {string} id The id of the tag to be deleted. 837 * @param {function(PageAPI)=} success Optional callback that receive 838 * this object as its only 839 * argument. 840 * @param {function(GCNError):boolean=} error Optional custom error 841 * handler. 842 */ 843 removeTag: function () { 844 this._removeTag.apply(this, arguments); 845 }, 846 847 /** 848 * Deletes a set of tags from this page. 849 * 850 * @public 851 * @function 852 * @name removeTags 853 * @memberOf PageAPI 854 * @param {Array.<string>} ids The ids of the set of tags to be 855 * deleted. 856 * @param {function(PageAPI)=} success Optional callback that receive 857 * this object as its only 858 * argument. 859 * @param {function(GCNError):boolean=} error Optional custom error 860 * handler. 861 */ 862 removeTags: function () { 863 this._removeTags.apply(this, arguments); 864 }, 865 866 /** 867 * Marks the page as to be taken offline. This method will change the 868 * state of the page object. 869 * 870 * @public 871 * @function 872 * @name takeOffline 873 * @memberOf PageAPI 874 * @param {funtion(PageAPI)=} success Optional callback to receive this 875 * page object as the only argument. 876 * @param {function(GCNError):boolean=} error Optional custom error 877 * handler. 878 */ 879 takeOffline: function (success, error) { 880 var that = this; 881 882 this._read(function () { 883 that._update('status', STATUS.OFFLINE, error); 884 if (success) { 885 that._save(null, success, error); 886 } 887 }, error); 888 }, 889 890 /** 891 * Trigger publish process for the page. 892 * 893 * @public 894 * @function 895 * @name publish 896 * @memberOf PageAPI 897 * @param {funtion(PageAPI)=} success Optional callback to receive this 898 * page object as the only argument. 899 * @param {function(GCNError):boolean=} error Optional custom error 900 * handler. 901 */ 902 publish: function (success, error) { 903 var that = this; 904 this._fulfill(function () { 905 that._authAjax({ 906 url: GCN.settings.BACKEND_PATH + '/rest/' + that._type + 907 '/publish/' + that.id(), 908 type: 'POST', 909 json: {}, // There needs to be at least empty content 910 // because of a bug in Jersey. 911 success: function (response) { 912 that._data.status = STATUS.PUBLISHED; 913 if (success) { 914 that._invoke(success, [that]); 915 } 916 }, 917 error: error 918 }); 919 }); 920 }, 921 922 /** 923 * Renders a preview of the current page. 924 * 925 * @public 926 * @function 927 * @name preview 928 * @memberOf PageAPI 929 * @param {function(string, PageAPI)} success Callback to receive the 930 * rendered page preview as 931 * the first argument, and 932 * this page object as the 933 * second. 934 * @param {function(GCNError):boolean=} error Optional custom error 935 * handler. 936 */ 937 preview: function (success, error) { 938 var that = this; 939 940 this._read(function () { 941 that._authAjax({ 942 url: GCN.settings.BACKEND_PATH + '/rest/' + that._type + 943 '/preview/', 944 json: { 945 page: that._data // @FIXME Shouldn't this a be merge of 946 // the `_shadow' object and the 947 // `_data'. 948 }, 949 type: 'POST', 950 error: error, 951 success: function (response) { 952 if (success) { 953 GCN._handleContentRendered(response.preview, that, 954 function (html) { 955 that._invoke(success, [html, that]); 956 }); 957 } 958 } 959 }); 960 }, error); 961 }, 962 963 /** 964 * Unlocks the page when finishing editing 965 * 966 * @public 967 * @function 968 * @name unlock 969 * @memberOf PageAPI 970 * @param {funtion(PageAPI)=} success Optional callback to receive this 971 * page object as the only argument. 972 * @param {function(GCNError):boolean=} error Optional custom error 973 * handler. 974 */ 975 unlock: function (success, error) { 976 var that = this; 977 this._fulfill(function () { 978 that._authAjax({ 979 url: GCN.settings.BACKEND_PATH + '/rest/' + that._type + 980 '/cancel/' + that.id(), 981 type: 'POST', 982 json: {}, // There needs to be at least empty content 983 // because of a bug in Jersey. 984 error: error, 985 success: function (response) { 986 if (success) { 987 that._invoke(success, [that]); 988 } 989 } 990 }); 991 }); 992 }, 993 994 /** 995 * @see GCN.ContentObjectAPI._processResponse 996 */ 997 '!_processResponse': function (data) { 998 jQuery.extend(this._data, data[this._type]); 999 1000 // if data contains page variants turn them into page objects 1001 if (this._data.pageVariants) { 1002 var pagevars = []; 1003 var i; 1004 for (i = 0; i < this._data.pageVariants.length; i++) { 1005 pagevars.push(this._continue(GCN.PageAPI, 1006 this._data.pageVariants[i])); 1007 } 1008 this._data.pageVariants = pagevars; 1009 } 1010 }, 1011 1012 /** 1013 * @override 1014 */ 1015 '!_removeAssociatedTagData': function (tagid) { 1016 var block; 1017 for (block in this._blocks) { 1018 if (this._blocks.hasOwnProperty(block) && 1019 this._blocks[block].tagname === tagid) { 1020 delete this._blocks[block]; 1021 } 1022 } 1023 1024 var editable; 1025 for (editable in this._editables) { 1026 if (this._editables.hasOwnProperty(editable) && 1027 this._editables[editable].tagname === tagid) { 1028 delete this._editables[editable]; 1029 } 1030 } 1031 } 1032 1033 }); 1034 1035 GCN.page = GCN.exposeAPI(PageAPI); 1036 GCN.PageAPI = PageAPI; 1037 1038 }(GCN)); 1039