1/* DOKUWIKI:include script/jquery.fileDownload.js */ 2/* DOKUWIKI:include script/nspicker.js */ 3 4/** 5 * Storage object for an array with a selection of pages 6 * 7 * @param key 8 * @constructor 9 */ 10function Storage(key) { 11 this.localStorageKey = key; 12 this._storage = []; 13} 14/** 15 * Is pageid in stored selection 16 * 17 * @param {string} pageid 18 * @returns {boolean} 19 */ 20Storage.prototype.isSelected = function(pageid) { 21 return this._storage.indexOf(pageid) !== -1; 22}; 23 24/** 25 * Insert pageid at given position 26 * 27 * @param {string} pageid 28 * @param {Number} position 29 */ 30Storage.prototype.addPage = function(pageid, position) { 31 if(typeof position === 'undefined') { 32 this._storage.push(pageid); //add to the end 33 } else { 34 this._storage.splice(position, 0, pageid); 35 } 36 37 this._save(); 38}; 39 40/** 41 * Move pageid inside selection to given position 42 * 43 * @param {string} pageid 44 * @param {Number} position 45 */ 46Storage.prototype.movePage = function(pageid, position) { 47 if(!this._deletePage(pageid).length) return; 48 49 this.addPage(pageid, position); 50}; 51 52/** 53 * Delete pageid from selection 54 * @param pageid 55 */ 56Storage.prototype.deletePage = function(pageid) { 57 this._deletePage(pageid); 58 this._save(); 59}; 60 61/** 62 * Delete given pageid from storage 63 * 64 * @param pageid 65 * @returns {Array} empty or with deleted entry 66 * @private 67 */ 68Storage.prototype._deletePage = function(pageid) { 69 let pos = this._storage.indexOf(pageid); 70 if(pos === -1) return []; 71 72 return this._storage.splice(pos, 1); 73}; 74 75/** 76 * Empty the store 77 */ 78Storage.prototype.clearAll = function() { 79 this._storage = []; 80 this._save(); 81}; 82 83/** 84 * Returns array with pageids of selected books 85 * 86 * @returns {Array} 87 */ 88Storage.prototype.getSelection = function() { 89 return this._storage; 90}; 91 92/** 93 * Set a new selection at once and save 94 * 95 * @param {Array} selection 96 */ 97Storage.prototype.setSelection = function(selection) { 98 this._storage = selection; 99 this._save(); 100}; 101 102/** 103 * Count number of selected pages 104 * 105 * @returns {Number} 106 */ 107Storage.prototype.count = function() { 108 return this._storage.length; 109}; 110 111/** 112 * Save current selection in browser's localStorage 113 * 114 * @private 115 */ 116Storage.prototype._save = function() { 117 window.localStorage.setItem(this.localStorageKey, JSON.stringify(this._storage)); 118}; 119 120/** 121 * Load selection from browser's localStorage 122 */ 123Storage.prototype.load = function() { 124 let source = window.localStorage.getItem(this.localStorageKey); 125 126 try { 127 this._storage = JSON.parse(source) || []; 128 } catch (E) { 129 this._storage = []; 130 } 131}; 132 133/** 134 * Storage object for an property in browser's localStorage 135 * 136 * @param key 137 * @constructor 138 */ 139function CachedProperty(key) { 140 this.localStorageKey = key; 141} 142 143CachedProperty.prototype.set = function(value) { 144 window.localStorage.setItem(this.localStorageKey, value); 145}; 146 147CachedProperty.prototype.get = function() { 148 return window.localStorage.getItem(this.localStorageKey); 149}; 150 151/** 152 * Performs bookcreator functionality at wiki pages 153 */ 154let Bookcreator = { 155 156 157 selectedpages: new Storage('bookcreator_selectedpages'), 158 isCurrentPageSelected: false, 159 160 /** 161 * Handle click at page add/remove buttons 162 */ 163 clickAddRemoveButton: function(e) { 164 e.preventDefault(); 165 166 Bookcreator.toggleSelectionCurrentPage(); 167 Bookcreator.updatePage(); 168 }, 169 170 /** 171 * Sets up a storage change observer 172 */ 173 setupUpdateObserver: function() { 174 jQuery(window).on('storage', function() { 175 Bookcreator.init(); 176 Bookcreator.updatePage(); 177 }); 178 179 //// handle cached navigation 180 //if ('addEventListener' in window) { 181 // window.addEventListener('pageshow', function(event) { 182 // if (event.persisted) { 183 // Bookcreator.load(); 184 // } 185 // }, false); 186 //} 187 }, 188 189 /** 190 * Initiate storage 191 */ 192 init: function() { 193 this.selectedpages.load(); 194 this.isCurrentPageSelected = this.selectedpages.isSelected(JSINFO.id); 195 }, 196 197 /** 198 * Delete or add current page from selection 199 */ 200 toggleSelectionCurrentPage: function() { 201 if(this.isCurrentPageSelected) { 202 this.selectedpages.deletePage(JSINFO.id); 203 } else { 204 this.selectedpages.addPage(JSINFO.id); 205 } 206 this.isCurrentPageSelected = this.selectedpages.isSelected(JSINFO.id); 207 }, 208 209 /** 210 * Update the interface to current selection 211 */ 212 updatePage: function() { 213 //$addtobookBtn2 = jQuery('.plugin_bookcreator_addtobook,a.plugin_bookcreator_addtobook'), 214 let $addtobookBtn = jQuery('.plugin_bookcreator__addtobook'), 215 $bookbar = jQuery('.bookcreator__bookbar'); 216 217 //pagetool add/remove button 218 // /* TODO DEPRECATED 2017 */ 219 // $addtobookBtn2.css( "display", "block"); 220 // if ($addtobookBtn2.length) { //exists the addtobook link 221 // var text = LANG.plugins.bookcreator['btn_' + (this.isCurrentPageSelected ? 'remove' : 'add') + 'tobook']; 222 // 223 // $addtobookBtn2 224 // .toggleClass('remove', this.isCurrentPageSelected) 225 // .attr('title', text) 226 // .children('span').html(text); 227 // } 228 229 //pagetool add/remove button 230 if ($addtobookBtn.length) { //exists the addtobook item in pagetools? 231 let text = LANG.plugins.bookcreator['btn_' + (this.isCurrentPageSelected ? 'remove' : 'add') + 'tobook']; 232 233 let $a = $addtobookBtn.find('a') 234 .toggleClass('remove', this.isCurrentPageSelected) 235 .attr('title', text).trigger('blur'); 236 let $span = $a.children('span'); 237 if($span.length) { 238 $span.html(text); 239 } else { 240 //e.g vector template does not use svg, so has no span 241 $a.html(text); 242 } 243 244 // Workaround for Bootstrap3 Template uses <a> instead of <li> 245 jQuery('a.plugin_bookcreator__addtobook') 246 .toggleClass('remove', this.isCurrentPageSelected) 247 .attr('title', text).trigger('blur') 248 .children('span').html(text); 249 } 250 251 //bookbar with add/remove button 252 if(this.isBookbarVisible()) { 253 jQuery("#bookcreator__add").toggle(!this.isCurrentPageSelected); 254 jQuery("#bookcreator__remove").toggle(this.isCurrentPageSelected); 255 256 jQuery("#bookcreator__pages").html(this.selectedpages.count()); 257 } 258 $bookbar.toggle(this.isBookbarVisible()) 259 }, 260 261 /** 262 * Is bookbar visible 263 * 264 * @returns {boolean} 265 */ 266 isBookbarVisible: function() { 267 268 if(!JSINFO.bookcreator.areToolsVisible) { 269 //permissions, skip page 270 return false; 271 } 272 273 return JSINFO.bookcreator.showBookbar === 'always' 274 || JSINFO.bookcreator.showBookbar === 'noempty' && this.selectedpages.count() > 0; 275 } 276}; 277 278let BookManager = { 279 cache: {}, 280 booktitle: new CachedProperty('bookcreator_booktitle'), 281 deletedpages: new Storage('bookcreator_deletedpages'), 282 283 284 init: function() { 285 this.deletedpages.load(); 286 Bookcreator.init(); 287 }, 288 289 setupUpdateObserver: function(){ 290 jQuery(window).on('storage', function(event) { 291 if(event.key === Bookcreator.selectedpages.localStorageKey) { 292 BookManager.init(); 293 BookManager.updateListsFromStorage(); 294 } 295 if(event.key === BookManager.booktitle.localStorageKey) { 296 BookManager.fillTitle(); 297 } 298 }) 299 }, 300 301 /** 302 * Retrieve missing pages and add to the page cache 303 */ 304 updateListsFromStorage: function() { 305 //get selection changes 306 let notcachedpages = jQuery(Bookcreator.selectedpages.getSelection()).not(Object.keys(this.cache)).get(); 307 notcachedpages = notcachedpages.concat(jQuery(BookManager.deletedpages.getSelection()).not(Object.keys(this.cache)).get()); 308 309 //add to list at page and to cache 310 function processRetrievedPages(pages) { 311 if(pages.hasOwnProperty('selection')) { 312 jQuery.extend(BookManager.cache, pages.selection); 313 314 BookManager.updateLists(); 315 } 316 317 } 318 319 //retrieve data 320 if(notcachedpages.length > 0) { 321 jQuery.post( 322 DOKU_BASE + 'lib/exe/ajax.php', 323 { 324 call: 'plugin_bookcreator_call', 325 action: 'retrievePageinfo', 326 selection: JSON.stringify(notcachedpages), 327 sectok: jQuery('input[name="sectok"]').val() 328 }, 329 processRetrievedPages, 330 'json' 331 ); 332 } else { 333 this.updateLists(); 334 } 335 }, 336 337 /** 338 * Use updated selected pages selection for updating deleted pages selection and gui 339 */ 340 updateLists: function() { 341 let $ul_deleted = jQuery('ul.pagelist.deleted'), 342 $ul_selected = jQuery('ul.pagelist.selected'), 343 deletedpages = BookManager.deletedpages.getSelection(), 344 selectedpages = Bookcreator.selectedpages.getSelection(); 345 346 BookManager.refillList($ul_selected, selectedpages); 347 348 //deleted pages selection could still contain re-added pages 349 let filtereddeletedpages = jQuery(deletedpages).not(selectedpages).get(); 350 BookManager.deletedpages.setSelection(filtereddeletedpages); 351 352 BookManager.refillList($ul_deleted, filtereddeletedpages); 353 }, 354 355 /** 356 * Empty the list in the gui and fill with pages from the stored selection 357 * 358 * @param {jQuery} $ul_selection the unordered list element, to fill with pages 359 * @param {Array} selection array with the pageids of selected or deleted pages 360 */ 361 refillList: function($ul_selection, selection) { 362 //just empty 363 $ul_selection.empty(); 364 365 //recreate li items 366 let liopen1 = "<li class='level1' id='pg__", 367 liopen2 = "' title='" + LANG.plugins.bookcreator.sortable + "'>" + 368 "<a class='action remove' title='" + LANG.plugins.bookcreator['remove'] + "'>" + 369 '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M17,3H7A2,2 0 0,0 5,5V21L12,18L19,21V5A2,2 0 0,0 17,3M15,11H9V9H15V11Z"></path></svg>' + 370 "</a><a class='action include' title='" + LANG.plugins.bookcreator['include'] + "'>" + 371 '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M17,3A2,2 0 0,1 19,5V21L12,18L5,21V5C5,3.89 5.9,3 7,3H17M11,7V9H9V11H11V13H13V11H15V9H13V7H11Z"></path></svg>' + 372 "</a> <a href='", 373 liopen3 = "' title='" + LANG.plugins.bookcreator.showpage + "'>", 374 liclose = "</a></li>"; 375 376 let i = 0, 377 itemsToInsert = []; 378 379 jQuery.each(selection, function(index, page){ 380 if(BookManager.cache[page]) { 381 itemsToInsert[i++] = liopen1; 382 itemsToInsert[i++] = page; 383 itemsToInsert[i++] = liopen2; 384 itemsToInsert[i++] = BookManager.cache[page][0]; 385 itemsToInsert[i++] = liopen3; 386 itemsToInsert[i++] = BookManager.cache[page][1]; 387 itemsToInsert[i++] = liclose; 388 } else { 389 console.log('Not in cache: ' + page); 390 } 391 }); 392 //add to list 393 $ul_selection.append(itemsToInsert.join('')); 394 395 //update gui 396 //jQuery('div.bookcreator__pagelist').find('ul.pagelist.selected,ul.pagelist.deleted').sortable('refresh'); 397 $ul_selection.sortable('refresh'); 398 }, 399 400 /** 401 * Returns pageids from selection which are not in list at page 402 * 403 * @param {string} selectionname 404 * @returns {Array} 405 */ 406 getNotdisplayedPages: function(selectionname) { 407 let sortedIDs, 408 selection; 409 410 sortedIDs = jQuery('div.bookcreator__pagelist').find('ul.pagelist.'+selectionname).sortable('toArray'); 411 //remove 'pg__' 412 sortedIDs = jQuery.map(sortedIDs, function(id){ 413 return id.substr(4); 414 }); 415 416 if(selectionname === 'selected') { 417 selection = Bookcreator.selectedpages.getSelection(); 418 } else { 419 selection = BookManager.deletedpages.getSelection(); 420 } 421 422 return jQuery(selection).not(sortedIDs).get(); 423 }, 424 425 /** 426 * Move between selections 427 * 428 * @param {string} pageid 429 * @param {boolean} isAddAction 430 * @param {Number} position or skip 431 */ 432 toggleSelectedPage: function (pageid, isAddAction, position) { 433 if (isAddAction) { 434 Bookcreator.selectedpages.addPage(pageid, position); 435 BookManager.deletedpages.deletePage(pageid); 436 } else { 437 Bookcreator.selectedpages.deletePage(pageid); 438 BookManager.deletedpages.addPage(pageid, position); 439 } 440 }, 441 442 /** 443 * handler for move buttons on the list items 444 */ 445 movePage: function() { 446 let $a = jQuery(this), 447 $li = $a.parent(), 448 pageid = $li.attr('id').substr(4); 449 450 //true=add to selected list, false=remove from selected list 451 let isAddAction = $li.parent().hasClass('deleted'); 452 453 //move page to other list 454 let listclass = isAddAction ? 'selected' : 'deleted'; 455 $li.appendTo(jQuery('div.bookcreator__pagelist ul.pagelist.' + listclass)); 456 457 BookManager.toggleSelectedPage(pageid, isAddAction); 458 }, 459 460 461 /** 462 * List item is dropped in other pagelist 463 * 464 * @param event 465 * @param ui 466 */ 467 receivedFromOtherSelection: function (event, ui) { 468 let pageid = ui.item.attr('id').substr(4), 469 isAddAction = ui.item.parent().hasClass('selected'), 470 position = ui.item.index(); 471 472 //store new status 473 BookManager.toggleSelectedPage(pageid, isAddAction, position); 474 }, 475 476 /** 477 * Store start position of moved list item 478 * 479 * @param event 480 * @param ui 481 */ 482 startSort: function(event, ui) { 483 ui.item.data("startindex", ui.item.index()); 484 }, 485 486 /** 487 * Store whether list item is sorted within list, or from outside 488 * 489 * @param event 490 * @param ui 491 */ 492 updateSort: function(event, ui) { 493 isItemSortedWithinList = !ui.sender; 494 }, 495 496 /** 497 * Handle sorting within list 498 * 499 * @param event 500 * @param ui 501 */ 502 stopSort: function(event, ui) { 503 let isAddAction = ui.item.parent().hasClass('selected'), 504 pageid = ui.item.attr('id').substr(4), 505 startindex = ui.item.data("startindex"), 506 endindex = ui.item.index(); 507 508 if(isItemSortedWithinList) { 509 if(startindex !== endindex) { 510 if(isAddAction) { 511 Bookcreator.selectedpages.movePage(pageid, endindex); 512 } else { 513 BookManager.deletedpages.movePage(pageid, endindex); 514 } 515 } 516 517 isItemSortedWithinList = false; 518 } 519 }, 520 521 /** 522 * click handler: Delete all selections of selected and deleted pages 523 */ 524 clearSelections: function() { 525 BookManager.deletedpages.clearAll(); 526 jQuery('ul.pagelist.deleted').empty(); 527 528 Bookcreator.selectedpages.clearAll(); 529 jQuery('ul.pagelist.selected').empty(); 530 }, 531 532 /** 533 * click handler: load or delete saved selections 534 */ 535 handleSavedselectionAction: function() { 536 let $this = jQuery(this), 537 action = ($this.hasClass('delete') ? 'delete' : 'load'), 538 pageid = $this.parent().data('pageId'); 539 540 //confirm dialog 541 let msg, 542 comfirmed = false; 543 if (action === "delete") { 544 msg = LANG.plugins.bookcreator.confirmdel; 545 } else { 546 if (Bookcreator.selectedpages.count() === 0) { 547 comfirmed = true; 548 } 549 msg = LANG.plugins.bookcreator.confirmload; 550 } 551 if (!comfirmed) { 552 comfirmed = confirm(msg); 553 } 554 555 if (comfirmed) { 556 function processResponse(data) { 557 let $msg = $this.parent().parent().parent().find('.message'); //get $msg before deletion of the li elem 558 559 //action: loadSavedSelection 560 if (data.hasOwnProperty('selection')) { 561 BookManager.clearSelections(); 562 Bookcreator.selectedpages.setSelection(data.selection); 563 BookManager.updateListsFromStorage(); 564 jQuery('input[name="book_title"]').val(data.title).trigger('change'); 565 } 566 //action: deleteSavedSelection 567 if (data.hasOwnProperty('deletedpage')) { 568 jQuery('.bkctrsavsel__' + data.deletedpage).remove(); 569 } 570 571 BookManager.setMessage($msg, data); 572 } 573 574 jQuery.post( 575 DOKU_BASE + 'lib/exe/ajax.php', 576 { 577 call: 'plugin_bookcreator_call', 578 action: (action === 'load' ? 'loadSavedSelection' : 'deleteSavedSelection'), 579 savedselectionname: pageid, 580 sectok: jQuery('input[name="sectok"]').val() 581 }, 582 processResponse, 583 'json' 584 ); 585 } 586 }, 587 588 /** 589 * Save selection at a wiki page 590 */ 591 saveSelection: function($this) { 592 let $fieldset = $this.parent(), 593 $title = $fieldset.find('input[name="bookcreator_title"]'), 594 title = $title.val(); 595 596 function processResponse(data) { 597 if (data.hasOwnProperty('item')) { 598 jQuery('.bookcreator__selections__list').find('ul').prepend(data.item); 599 $title.val(''); 600 } 601 602 let $msg = $fieldset.find('.message'); 603 BookManager.setMessage($msg, data); 604 } 605 606 jQuery.post( 607 DOKU_BASE + 'lib/exe/ajax.php', 608 { 609 call: 'plugin_bookcreator_call', 610 action: 'saveSelection', 611 savedselectionname: title, 612 selection: JSON.stringify(Bookcreator.selectedpages.getSelection()), 613 sectok: jQuery('input[name="sectok"]').val() 614 }, 615 processResponse, 616 'json' 617 ); 618 }, 619 620 /** 621 * Show temporary the succes or error message 622 * 623 * @param {jQuery} $msg 624 * @param {Object} data 625 */ 626 setMessage: function($msg, data) { 627 let msg = false, 628 state; 629 630 function setMsg($msg, msg, state) { 631 $msg.html(msg) 632 .toggleClass('error', state === -1) 633 .toggleClass('success', state === 1); 634 } 635 636 if(data.hasOwnProperty('error')) { 637 msg = data.error; 638 state = -1; 639 } else if(data.hasOwnProperty('success')) { 640 msg = data.success; 641 state = 1; 642 } 643 644 if (msg) { 645 setMsg($msg, msg, state); 646 setTimeout(function () { 647 setMsg($msg, '', 0); 648 }, 1000 * 10); 649 } 650 }, 651 652 /** 653 * 654 */ 655 fillTitle: function() { 656 let title = BookManager.booktitle.get(); 657 jQuery('input[name="book_title"]').val(title); 658 }, 659 660 /** 661 * Download the requested file 662 * 663 * @param event 664 */ 665 downloadSelection: function(event) { 666 let $this = jQuery(this), 667 do_action = $this.find('select[name="do"]').val(); 668 669 if(do_action === 'export_html' || do_action === 'export_text') { 670 //just extend the form 671 $this.append( 672 '<input type="hidden" name="selection" value="' 673 + BookManager.htmlSpecialCharsEntityEncode(JSON.stringify(Bookcreator.selectedpages.getSelection())) 674 + '" />' 675 ); 676 } else { 677 //download in background and shows dialog 678 let formdata = $this.serializeArray(); 679 formdata.push({ 680 name: 'selection', 681 value: JSON.stringify(Bookcreator.selectedpages.getSelection()) 682 }); 683 684 let $preparingFileModal = jQuery("#preparing-file-modal"); 685 $preparingFileModal.dialog({ modal: true }); 686 687 jQuery.fileDownload( 688 window.location.href, 689 { 690 successCallback: function (url) { 691 $preparingFileModal.dialog('close'); 692 }, 693 failCallback: function (responseHtml, url) { 694 $preparingFileModal.dialog('close'); 695 jQuery("#error-modal") 696 .dialog({ modal: true }) 697 .find('.downloadresponse').html(responseHtml); 698 }, 699 httpMethod: "POST", 700 data: formdata 701 } 702 ); 703 704 event.preventDefault(); //otherwise a normal form submit would occur 705 } 706 707 }, 708 709 htmlSpecialCharsEntityEncode: function (str) { 710 let htmlSpecialCharsRegEx = /[<>&\r\n"']/gm; 711 let htmlSpecialCharsPlaceHolders = { 712 '<': 'lt;', 713 '>': 'gt;', 714 '&': 'amp;', 715 '\r': "#13;", 716 '\n': "#10;", 717 '"': 'quot;', 718 "'": '#39;' /*single quotes just to be safe, IE8 doesn't support ', so use ' instead */ 719 }; 720 return str.replace(htmlSpecialCharsRegEx, function(match) { 721 return '&' + htmlSpecialCharsPlaceHolders[match]; 722 }); 723 } 724 725}; 726 727 728jQuery(function () { 729 //Tools for selecting a page 730 if(JSINFO.bookcreator.areToolsVisible) { 731 Bookcreator.init(); 732 Bookcreator.setupUpdateObserver(); 733 734 //bookbar buttons 735 jQuery('a.bookcreator__tglPgSelection').on('click', Bookcreator.clickAddRemoveButton); 736 // //pagetool button TODO DEPRECATED 2017 737 // jQuery('.plugin_bookcreator_addtobook').click(Bookcreator.clickAddRemoveButton); 738 //pagetool button 739 jQuery('.plugin_bookcreator__addtobook').on('click', Bookcreator.clickAddRemoveButton); 740 //gui 741 Bookcreator.updatePage(); 742 } else { 743 //hide addtobook button from pagetool 744 jQuery('.plugin_bookcreator__addtobook').hide(); 745 } 746 747 //bookmanager 748 let $pagelist = jQuery('div.bookcreator__pagelist'); 749 if ($pagelist.length) { 750 BookManager.init(); 751 752 //buttons at page lists 753 $pagelist.find('ul') 754 .on('click', 'a.action', BookManager.movePage); 755 756 //sorting and drag-and-drop 757 isItemSortedWithinList = false; //use in closure in stop and update handler 758 $pagelist.find('ul.pagelist.selected,ul.pagelist.deleted') 759 .sortable({ 760 connectWith: "div.bookcreator__pagelist ul.pagelist", 761 receive: BookManager.receivedFromOtherSelection, 762 start: BookManager.startSort, 763 stop: BookManager.stopSort, 764 update: BookManager.updateSort, 765 distance: 5 766 }); 767 768 //clear selection button 769 jQuery('form.clearactive button').on('click', function(event) { 770 event.preventDefault(); 771 BookManager.clearSelections(); 772 }); 773 774 //add namespace to selection button 775 bc_nspicker.init(jQuery('.dokuwiki:first')); 776 jQuery('form.selectnamespace button').on('click', function(event) { 777 bc_nspicker.val = null; 778 779 //place dialog near the button 780 let offset = jQuery(this).offset(); 781 let offsetparent = jQuery(this).parent().parent().offset(); 782 bc_nspicker.$picker.css({ 783 'top': offset.top + 'px', 784 'left': offsetparent.left - 0 + 'px', 785 }); 786 787 bc_nspicker.toggle(); 788 event.preventDefault(); 789 return 'bc__nspicker'; 790 }); 791 792 BookManager.updateListsFromStorage(); 793 BookManager.fillTitle(); 794 BookManager.setupUpdateObserver(); 795 796 //save selection 797 jQuery('form.saveselection button').on('click', function(event) { 798 event.preventDefault(); 799 BookManager.saveSelection(jQuery(this)); 800 }); 801 802 jQuery('input[name="book_title"]').on('change', function() { 803 let value = jQuery(this).val(); 804 BookManager.booktitle.set(value); 805 }); 806 807 jQuery('form.downloadselection').on('submit', BookManager.downloadSelection); 808 } 809 810 //saved selection list 811 jQuery('.bookcreator__selections__list').find('ul') 812 .on('click', 'a.action', BookManager.handleSavedselectionAction); 813}); 814