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>&nbsp;&nbsp;<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 &apos;, so use &#39; 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