<commit_before>function annotatorMeltdownZotero(user_options) {
    var _t = annotator.util.gettext;
    var _app;
    var options = {
        // default disabled message (can be overriden)
        disabled_message: 'Zotero functionality is not available.',
    };
    $.extend(options, user_options);
    var zotero, zotero_initialized = false,
        disabled = !((options.user_id && options.token) || options.group_id);
    // if zotero credentials are not supplied, then run in disabled mode

    console.log('disabled ? ' + disabled);

    // bootstrap modal for zotero lookup
    var modal_template = $([
        '
',
        '

',
        // no close button for zotero lookup, for now
        // '
<button type="button" class="close" data-dismiss="modal" aria-label="Close">×</button>',
            '
',
                'Z',
                '<input id="zotero-lookup"/>',
                '',
        '

',
        '

'].join('\n'));

    // bootstrap modal with disabled message
    var modal_template_disabled = $([
        '
',
        '

',
        '
<button type="button" class="close" data-dismiss="modal" aria-label="Close">×</button>',
            '
',
                '',
                '
' + options.disabled_message + '
',
        '

',
        '

'].join('\n'));

    function customize_meltdown() {
        // add a Zotero citation action to default meltdown controls
        var controls = $.meltdown.defaults.controls,
            fs_index = controls.indexOf('fullscreen');
        // insert zotero before fullscreen
        controls.splice(fs_index, 0, 'zotero');
        // configure display and functionality for zotero
        $.meltdown.controlDefs.zotero = {
            label: "Z",
            altText: "Insert Zotero citation",  // based on examples in meltdown code
            click: function(meltdown /*, def, control, execAction */) {
                zotero_lookup(meltdown);
            }
        };
    }

    function has_citations(text) {
        // check if editor content includes a works cited section
        return text !== undefined && text.indexOf('### Works Cited') != '-1';
    }

    function zotero_lookup(meltdown) {
        if (! zotero_initialized) {
            $('#zotero-modal').on('shown.bs.modal', function () {
                // focus on the autocomplete lookup input, and select
                // any existing input from previous lookups for easy re-entry
                $('#zotero-lookup').focus().select();

                // make sure zotero modal shows up over the meltdown window
                $('#zotero-modal').zIndex(parseInt($('.meltdown_wrap').zIndex()) + 1);

                // for simplicity, hide the editor while the zotero lookup is active
                $('.annotator-editor').hide();

                // FIXME: would be nicer not to do this when meltdown is
                // fullscreen, which is easy to check;
                // however, zotero-modal does not show up *over* the fullscreen
                // meltdown window (and it does not seem to be a z-index issue)
            });
            $('#zotero-modal').on('hidden.bs.modal', function () {
                // restore editor visibility
                $('.annotator-editor').show();
            });

            $( "#zotero-lookup" ).autocomplete({
                autoFocus: true,
                source: function( request, response ) {
                    $('#zotero-modal .in-progress').show();
                    // execute a zotero autocomplete search on the user term
                    // for use with the autocomplete response method
                    zotero.autocomplete_search(request.term, function(data) {
                        $('#zotero-modal .in-progress').hide();
                        response(data);
                    });
                },
                open: function(event, ui) {
                    /* annotator purposely sets the editor at a higher z-index
                    than anything else on the page; make sure the autocomplete
                    menu is still higher so it isn't obscured by annotator buttons */
                    $('.ui-autocomplete').zIndex($(event.target).zIndex() + 1);
                },
                select: function(event, ui) {
                    // on select, put the citation id into the annotation text
                    // and hide the modal
                    if (meltdown) {  // requires access to meltdown editor
                        // get the zotero citation as html and add to the end of the text
                        // format json, include bib

                        zotero.get_item(ui.item.id, 'json', 'bib,data', function(data) {
                            // construct in-text citation with internal link;
                            // using chicago style for now (zotero default)
                            var text_label;
                            // zotero handles one, two, multiple authors for us
                            if (data.meta.creatorSummary) {
                                text_label = data.meta.creatorSummary;
                            // otherwise, cite in text by title (e.g. website)
                            } else if (data.data.shortTitle) {
                                text_label = data.data.shortTitle;
                            } else {
                                text_label = data.data.title;
                            }
                            // include first 4 letters (year) of date, if available
                            if (data.meta.parsedDate) {
                                text_label += ' ' + data.meta.parsedDate.substring(0, 4);
                            }

                            // construct parenthetical markdown internal link;
                            // using zotero item key as identifier
                            var text_citation = '([' + text_label + '](./#' + data.key + '))';
                            // insert in-text citation where the cursor is
                            meltdown.editor.replaceSelectedText(text_citation,
                                "select");

                            // parse the html citation from the zotero result
                            // and pull out just the entry
                            var citation = $($.parseXML(data.bib)).find("[class=csl-entry]");

                            var textarea = $(meltdown.editor.context);
                            var ed_content = textarea.val();

                            // add a new citations section if not already present
                            if (! has_citations(ed_content)) {
                                ed_content += '\n\n---\n### Works Cited\n';
                            }
                            // add the citation at the end of the annotation content
                            // using formatted citation from Zotero, but adding a named
                            // anchor for linking to in-text citation
                            ed_content += '\n* ' + citation.html();
                            textarea.val(ed_content);
                        });

                    }
                    // close the modal
                    $('#zotero-modal').modal('hide');
                },
            })
            .autocomplete( "instance" )._renderItem = function( ul, item ) {
                // customized display for zotero item
                return $( "
" )
                    .append("" + item.title + "")
                    .append('
' + item.description + '
')
                    .appendTo( ul );
            };
        // set initialized so init doesn't happen again
        zotero_initialized = true;
        }

        // show the modal
        $('#zotero-modal').modal();
    }

    // convert zotero citation references into full markdown citations
    function update_citations(annotation) {
        var name_re = new RegExp('a name="([^"]+)"', 'gm');
        var matches = [], found;

        console.log('update citations, annotation text is ' + annotation.text);
        console.log(annotation);

        // if annotation includes citations, scan for zotero ids
        // and attach additional data to the annotation
        if (has_citations(annotation.text)) {
            // list of promises, one for each api request to be done
            var promises = [];

            // Look for name anchors like those created by adding
            // citations in order to find all possible citation ids
            while (found = name_re.exec(annotation.text)) {
                matches.push(found[1]);   // 0 is full match, 1 is first group
            }

            // retrieve & store the tei citation information as extra data
            // on the annotation

            // clear out any stored citations to ensure content and tei
            // citations stay in sync
            annotation.citations = [];
            for(var i = 0; i < matches.length; i++) {
                // get tei record for full metadata & easy export
                // NOTE: tei record includes zotero item id, so not
                // fetching or storing id separately

                promises.push(new Promise(function(resolve, reject) {
                    zotero.get_item(matches[i], 'tei', '', function(data) {
                        console.log('fetched tei');
                        // zotero returns the citation wrapped in a listBibl
                        // element that we don't need; just extract the bibl itself
                        var tei_data = $($.parseXML(data)).find("biblStruct");
                        var tei_bibl = tei_data.get(0).outerHTML;
                        // don't duplicate an entry (could happen due to delayed processing)
                        if (annotation.citations.indexOf(tei_bibl) == -1) {
                            annotation.citations.push(tei_bibl);
                        }
                        // resolve the promise after the api request is processed
                        resolve();
                    });

                }));
            }  // for loop on matches

            // return a promise that will resolve when all promises resolve,
            // so annotator will wait to save the annotation until
            // all citations are processed and data is added

          } // has citations

          return Promise.all(promises);
        }; // update citations

    return {

        start: function (app) {
            // store a reference to the annotator app
            _app = app;

            // any startup config / warnings needed?

            // check for meltdown
            if (!jQuery.meltdown || typeof jQuery.meltdown !== 'object') {
                console.warn(_t("To use the annotator-meltdown-zotero Editor " +
                    "extension, you must include meltdown in the page."));
                return;
            }

            // also check for annotator-meltdown?  zotero client? jquery.rest ?

            // add zotero button to meltdown editor control panel
            customize_meltdown();

            // if required options are not present, disable with a message
            if (disabled) {
                console.log('disabling zotero');
                // load the disabled message modal template
                $('body').append(modal_template_disabled);

                // add a handler to disable zotero button after
                // meltdown editor is initalized the first time
                // (the html does not yet exist until that point)
                $(document).on('annotator-meltdown:meltdown-initialized', function() {
                    $('.meltdown_control-zotero').addClass('zotero-disabled');
                });
            } else {

                // dynamically add modal for zotero autocomplete into the document
                // using bootstrap modal markup
                // zotero word CWYW modal has a red border with zotero z label for input bot
                $('body').append(modal_template);

                // init zotero rest api client with provided id/token
                zotero = new ZoteroClient(options);
            }

            // for testing: uncomment to launch zotero lookup on page load
            // zotero_lookup();

        },

        // NOTE: ideally, update_annotations should be run on
        // beforeAnnotationCreated, but that event seems to fire too soon
        // (before the annotation text is available to be updated).

        annotationCreated: function (annotation) {
            // Check for and update citations after a brand new annotation
            // is created, and re-save the annotation if necessary.

            if (disabled) { return true; }   // if zotero is disabled, do nothing
            // use citation count to check for changes; on a new
            // annotation we expect this to be zero no matter what
            var citation_count = 0;
            if (annotation.citations) {
                citation_count = annotation.citations.length;
            }

            // var update_promise = update_citations(annotation);
            // update_promise.then(function(){

            update_citations(annotation).then(function(){
                console.log("update promise success");
                if (annotation.citations && citation_count != annotation.citations.length) {
                    console.log('citation counts differ, updating');
                    _app.registry.utilities.storage.update(annotation);
                }
            }, function() {
                // promise failure - no action
            });

        },

        beforeAnnotationUpdated: function (annotation) {
            if (disabled) { return true; }   // if zotero is disabled, do nothing
            // FIXME: sometimes seems to be called when annotation is
            // loaded for edit, not when the editor is closed
            return update_citations(annotation);
        }
    };
};

<commit_msg>Trigger a meltdown preview update after adding citation text<commit_after>function annotatorMeltdownZotero(user_options) {
    var _t = annotator.util.gettext;
    var _app;
    var options = {
        // default disabled message (can be overriden)
        disabled_message: 'Zotero functionality is not available.',
    };
    $.extend(options, user_options);
    var zotero, zotero_initialized = false,
        disabled = !((options.user_id && options.token) || options.group_id);
    // if zotero credentials are not supplied, then run in disabled mode

    console.log('disabled ? ' + disabled);

    // bootstrap modal for zotero lookup
    var modal_template = $([
        '
',
        '

',
        // no close button for zotero lookup, for now
        // '
<button type="button" class="close" data-dismiss="modal" aria-label="Close">×</button>',
            '
',
                'Z',
                '<input id="zotero-lookup"/>',
                '',
        '

',
        '

'].join('\n'));

    // bootstrap modal with disabled message
    var modal_template_disabled = $([
        '
',
        '

',
        '
<button type="button" class="close" data-dismiss="modal" aria-label="Close">×</button>',
            '
',
                '',
                '
' + options.disabled_message + '
',
        '

',
        '

'].join('\n'));

    function customize_meltdown() {
        // add a Zotero citation action to default meltdown controls
        var controls = $.meltdown.defaults.controls,
            fs_index = controls.indexOf('fullscreen');
        // insert zotero before fullscreen
        controls.splice(fs_index, 0, 'zotero');
        // configure display and functionality for zotero
        $.meltdown.controlDefs.zotero = {
            label: "Z",
            altText: "Insert Zotero citation",  // based on examples in meltdown code
            click: function(meltdown /*, def, control, execAction */) {
                zotero_lookup(meltdown);
            }
        };
    }

    function has_citations(text) {
        // check if editor content includes a works cited section
        return text !== undefined && text.indexOf('### Works Cited') != '-1';
    }

    function zotero_lookup(meltdown) {
        if (! zotero_initialized) {
            $('#zotero-modal').on('shown.bs.modal', function () {
                // focus on the autocomplete lookup input, and select
                // any existing input from previous lookups for easy re-entry
                $('#zotero-lookup').focus().select();

                // make sure zotero modal shows up over the meltdown window
                $('#zotero-modal').zIndex(parseInt($('.meltdown_wrap').zIndex()) + 1);

                // for simplicity, hide the editor while the zotero lookup is active
                $('.annotator-editor').hide();

                // FIXME: would be nicer not to do this when meltdown is
                // fullscreen, which is easy to check;
                // however, zotero-modal does not show up *over* the fullscreen
                // meltdown window (and it does not seem to be a z-index issue)
            });
            $('#zotero-modal').on('hidden.bs.modal', function () {
                // restore editor visibility
                $('.annotator-editor').show();
            });

            $( "#zotero-lookup" ).autocomplete({
                autoFocus: true,
                source: function( request, response ) {
                    $('#zotero-modal .in-progress').show();
                    // execute a zotero autocomplete search on the user term
                    // for use with the autocomplete response method
                    zotero.autocomplete_search(request.term, function(data) {
                        $('#zotero-modal .in-progress').hide();
                        response(data);
                    });
                },
                open: function(event, ui) {
                    /* annotator purposely sets the editor at a higher z-index
                    than anything else on the page; make sure the autocomplete
                    menu is still higher so it isn't obscured by annotator buttons */
                    $('.ui-autocomplete').zIndex($(event.target).zIndex() + 1);
                },
                select: function(event, ui) {
                    // on select, put the citation id into the annotation text
                    // and hide the modal
                    if (meltdown) {  // requires access to meltdown editor
                        // get the zotero citation as html and add to the end of the text
                        // format json, include bib

                        zotero.get_item(ui.item.id, 'json', 'bib,data', function(data) {
                            // construct in-text citation with internal link;
                            // using chicago style for now (zotero default)
                            var text_label;
                            // zotero handles one, two, multiple authors for us
                            if (data.meta.creatorSummary) {
                                text_label = data.meta.creatorSummary;
                            // otherwise, cite in text by title (e.g. website)
                            } else if (data.data.shortTitle) {
                                text_label = data.data.shortTitle;
                            } else {
                                text_label = data.data.title;
                            }
                            // include first 4 letters (year) of date, if available
                            if (data.meta.parsedDate) {
                                text_label += ' ' + data.meta.parsedDate.substring(0, 4);
                            }

                            // construct parenthetical markdown internal link;
                            // using zotero item key as identifier
                            var text_citation = '([' + text_label + '](./#' + data.key + '))';
                            // insert in-text citation where the cursor is
                            meltdown.editor.replaceSelectedText(text_citation,
                                "select");

                            // parse the html citation from the zotero result
                            // and pull out just the entry
                            var citation = $($.parseXML(data.bib)).find("[class=csl-entry]");

                            var textarea = $(meltdown.editor.context);
                            var ed_content = textarea.val();

                            // add a new citations section if not already present
                            if (! has_citations(ed_content)) {
                                ed_content += '\n\n---\n### Works Cited\n';
                            }
                            // add the citation at the end of the annotation content
                            // using formatted citation from Zotero, but adding a named
                            // anchor for linking to in-text citation
                            ed_content += '\n* ' + citation.html();
                            textarea.val(ed_content);
                            // force meltdown to update the preview window
                            // (otherwise not triggered until user makes another keystroke)
                            meltdown.update();
                        });

                    }
                    // close the modal
                    $('#zotero-modal').modal('hide');
                },
            })
            .autocomplete( "instance" )._renderItem = function( ul, item ) {
                // customized display for zotero item
                return $( "
" )
                    .append("" + item.title + "")
                    .append('
' + item.description + '
')
                    .appendTo( ul );
            };
        // set initialized so init doesn't happen again
        zotero_initialized = true;
        }

        // show the modal
        $('#zotero-modal').modal();
    }

    // convert zotero citation references into full markdown citations
    function update_citations(annotation) {
        var name_re = new RegExp('a name="([^"]+)"', 'gm');
        var matches = [], found;

        console.log('update citations, annotation text is ' + annotation.text);
        console.log(annotation);

        // if annotation includes citations, scan for zotero ids
        // and attach additional data to the annotation
        if (has_citations(annotation.text)) {
            // list of promises, one for each api request to be done
            var promises = [];

            // Look for name anchors like those created by adding
            // citations in order to find all possible citation ids
            while (found = name_re.exec(annotation.text)) {
                matches.push(found[1]);   // 0 is full match, 1 is first group
            }

            // retrieve & store the tei citation information as extra data
            // on the annotation

            // clear out any stored citations to ensure content and tei
            // citations stay in sync
            annotation.citations = [];
            for(var i = 0; i < matches.length; i++) {
                // get tei record for full metadata & easy export
                // NOTE: tei record includes zotero item id, so not
                // fetching or storing id separately

                promises.push(new Promise(function(resolve, reject) {
                    zotero.get_item(matches[i], 'tei', '', function(data) {
                        console.log('fetched tei');
                        // zotero returns the citation wrapped in a listBibl
                        // element that we don't need; just extract the bibl itself
                        var tei_data = $($.parseXML(data)).find("biblStruct");
                        var tei_bibl = tei_data.get(0).outerHTML;
                        // don't duplicate an entry (could happen due to delayed processing)
                        if (annotation.citations.indexOf(tei_bibl) == -1) {
                            annotation.citations.push(tei_bibl);
                        }
                        // resolve the promise after the api request is processed
                        resolve();
                    });

                }));
            }  // for loop on matches

            // return a promise that will resolve when all promises resolve,
            // so annotator will wait to save the annotation until
            // all citations are processed and data is added

          } // has citations

          return Promise.all(promises);
        }; // update citations

    return {

        start: function (app) {
            // store a reference to the annotator app
            _app = app;

            // any startup config / warnings needed?

            // check for meltdown
            if (!jQuery.meltdown || typeof jQuery.meltdown !== 'object') {
                console.warn(_t("To use the annotator-meltdown-zotero Editor " +
                    "extension, you must include meltdown in the page."));
                return;
            }

            // also check for annotator-meltdown?  zotero client? jquery.rest ?

            // add zotero button to meltdown editor control panel
            customize_meltdown();

            // if required options are not present, disable with a message
            if (disabled) {
                console.log('disabling zotero');
                // load the disabled message modal template
                $('body').append(modal_template_disabled);

                // add a handler to disable zotero button after
                // meltdown editor is initalized the first time
                // (the html does not yet exist until that point)
                $(document).on('annotator-meltdown:meltdown-initialized', function() {
                    $('.meltdown_control-zotero').addClass('zotero-disabled');
                });
            } else {

                // dynamically add modal for zotero autocomplete into the document
                // using bootstrap modal markup
                // zotero word CWYW modal has a red border with zotero z label for input bot
                $('body').append(modal_template);

                // init zotero rest api client with provided id/token
                zotero = new ZoteroClient(options);
            }

            // for testing: uncomment to launch zotero lookup on page load
            // zotero_lookup();

        },

        // NOTE: ideally, update_annotations should be run on
        // beforeAnnotationCreated, but that event seems to fire too soon
        // (before the annotation text is available to be updated).

        annotationCreated: function (annotation) {
            // Check for and update citations after a brand new annotation
            // is created, and re-save the annotation if necessary.

            if (disabled) { return true; }   // if zotero is disabled, do nothing
            // use citation count to check for changes; on a new
            // annotation we expect this to be zero no matter what
            var citation_count = 0;
            if (annotation.citations) {
                citation_count = annotation.citations.length;
            }

            // var update_promise = update_citations(annotation);
            // update_promise.then(function(){

            update_citations(annotation).then(function(){
                console.log("update promise success");
                if (annotation.citations && citation_count != annotation.citations.length) {
                    console.log('citation counts differ, updating');
                    _app.registry.utilities.storage.update(annotation);
                }
            }, function() {
                // promise failure - no action
            });

        },

        beforeAnnotationUpdated: function (annotation) {
            if (disabled) { return true; }   // if zotero is disabled, do nothing
            // FIXME: sometimes seems to be called when annotation is
            // loaded for edit, not when the editor is closed
            return update_citations(annotation);
        }
    };
};

<|endoftext|>


<commit_before>/**
 * @constructor
 * @mixes DataSourceValidationNotifierMixin
 */
function BaseDataSourceBuilder() {
}

_.extend(BaseDataSourceBuilder.prototype, /** @lends BaseDataSourceBuilder.prototype */ {
    build: function (context, args) {
        var dataSource = this.createDataSource(args.parentView);
        dataSource.suspendUpdate();

        this.applyMetadata(args.builder, args.parentView, args.metadata, dataSource);
        this.initFileProvider(dataSource, args.metadata);

        dataSource.resumeUpdate();

        /*if(args.parentView.onLoading){
         args.parentView.onLoading(function () {
         //dataSource.resumeUpdate();
         dataSource.updateItems();
         });
         }else{
         //dataSource.resumeUpdate();
         dataSource.updateItems();
         }*/

        return dataSource;
    },

    applyMetadata: function (builder, parentView, metadata, dataSource) {
        var idProperty = metadata.IdProperty;
        if (idProperty) {
            dataSource.setIdProperty(idProperty);
        }

        dataSource.setName(metadata.Name);
        dataSource.setFillCreatedItem(metadata.FillCreatedItem);
        dataSource.setPageSize(metadata.PageSize || 15);
        dataSource.setPageNumber(metadata.PageNumber || 0);

        if('Query' in metadata){
            dataSource.setFilter(metadata['Query']);
        }

        if('IsLazy' in metadata){
            dataSource.setIsLazy(metadata['IsLazy']);
        }

        this.initValidation(parentView, dataSource, metadata);
        this.initNotifyValidation(dataSource);
        this.initScriptsHandlers(parentView, metadata, dataSource);
    },

    createDataSource: function (parent) {
        throw 'BaseDataSourceBuilder.createDataSource В потомке BaseDataSourceBuilder не переопределен метод createDataSource.';
    },

    /**
     * @protected
     * @description Инициализация обработчиков для валидации данных
     * @param parentView
     * @param dataSource
     * @param metadata
     */
    initValidation: function (parentView, dataSource, metadata) {
        if (metadata.ValidationErrors) {
            dataSource.setErrorValidator(function (context, args) {
                return new ScriptExecutor(parentView).executeScript(metadata.ValidationErrors.Name || metadata.ValidationErrors, args);
            });
        }

        if (metadata.ValidationWarnings) {
            dataSource.setWarningValidator(function (context, args) {
                return new ScriptExecutor(parentView).executeScript(metadata.ValidationWarnings.Name || metadata.ValidationWarnings, args);
            });
        }
    },

    initScriptsHandlers: function (parentView, metadata, dataSource) {
        //Скриптовые обработчики на события
        if (parentView && metadata.OnSelectedItemChanged) {
            dataSource.onSelectedItemChanged(function () {
                new ScriptExecutor(parentView).executeScript(metadata.OnSelectedItemChanged.Name || metadata.OnSelectedItemChanged);
            });
        }

        if (parentView && metadata.OnItemsUpdated) {
            dataSource.onItemsUpdated(function () {
                new ScriptExecutor(parentView).executeScript(metadata.OnItemsUpdated.Name || metadata.OnItemsUpdated);
            });
        }

        if (parentView && metadata.OnSelectedItemModified) {
            dataSource.onSelectedItemModified(function () {
                new ScriptExecutor(parentView).executeScript(metadata.OnSelectedItemModified.Name || metadata.OnSelectedItemModified);
            });
        }

        if (parentView && metadata.OnItemDeleted) {
            dataSource.onItemDeleted(function () {
                new ScriptExecutor(parentView).executeScript(metadata.OnItemDeleted.Name || metadata.OnItemDeleted);
            });
        }
    },

    initFileProvider: function (dataSource, metadata) {

    }
});

_.extend(BaseDataSourceBuilder.prototype, DataSourceValidationNotifierMixin);<commit_msg>binding builder in base DS<commit_after>/**
 * @constructor
 * @mixes DataSourceValidationNotifierMixin
 */
function BaseDataSourceBuilder() {
}

_.extend(BaseDataSourceBuilder.prototype, /** @lends BaseDataSourceBuilder.prototype */ {
    build: function (context, args) {
        var dataSource = this.createDataSource(args.parentView);
        dataSource.suspendUpdate();

        var bindingBuilder = this.buildBindingBuilder(args);
        dataSource.setBindingBuilder(bindingBuilder);

        this.applyMetadata(args.builder, args.parentView, args.metadata, dataSource);
        this.initFileProvider(dataSource, args.metadata);

        dataSource.resumeUpdate();

        /*if(args.parentView.onLoading){
         args.parentView.onLoading(function () {
         //dataSource.resumeUpdate();
         dataSource.updateItems();
         });
         }else{
         //dataSource.resumeUpdate();
         dataSource.updateItems();
         }*/

        return dataSource;
    },

    applyMetadata: function (builder, parentView, metadata, dataSource) {
        var idProperty = metadata.IdProperty;
        if (idProperty) {
            dataSource.setIdProperty(idProperty);
        }

        dataSource.setName(metadata.Name);
        dataSource.setFillCreatedItem(metadata.FillCreatedItem);
        dataSource.setPageSize(metadata.PageSize || 15);
        dataSource.setPageNumber(metadata.PageNumber || 0);

        if('Query' in metadata){
            dataSource.setFilter(metadata['Query']);
        }

        if('IsLazy' in metadata){
            dataSource.setIsLazy(metadata['IsLazy']);
        }

        this.initValidation(parentView, dataSource, metadata);
        this.initNotifyValidation(dataSource);
        this.initScriptsHandlers(parentView, metadata, dataSource);
    },

    createDataSource: function (parent) {
        throw 'BaseDataSourceBuilder.createDataSource В потомке BaseDataSourceBuilder не переопределен метод createDataSource.';
    },

    /**
     * @protected
     * @description Инициализация обработчиков для валидации данных
     * @param parentView
     * @param dataSource
     * @param metadata
     */
    initValidation: function (parentView, dataSource, metadata) {
        if (metadata.ValidationErrors) {
            dataSource.setErrorValidator(function (context, args) {
                return new ScriptExecutor(parentView).executeScript(metadata.ValidationErrors.Name || metadata.ValidationErrors, args);
            });
        }

        if (metadata.ValidationWarnings) {
            dataSource.setWarningValidator(function (context, args) {
                return new ScriptExecutor(parentView).executeScript(metadata.ValidationWarnings.Name || metadata.ValidationWarnings, args);
            });
        }
    },

    initScriptsHandlers: function (parentView, metadata, dataSource) {
        //Скриптовые обработчики на события
        if (parentView && metadata.OnSelectedItemChanged) {
            dataSource.onSelectedItemChanged(function () {
                new ScriptExecutor(parentView).executeScript(metadata.OnSelectedItemChanged.Name || metadata.OnSelectedItemChanged);
            });
        }

        if (parentView && metadata.OnItemsUpdated) {
            dataSource.onItemsUpdated(function () {
                new ScriptExecutor(parentView).executeScript(metadata.OnItemsUpdated.Name || metadata.OnItemsUpdated);
            });
        }

        if (parentView && metadata.OnSelectedItemModified) {
            dataSource.onSelectedItemModified(function () {
                new ScriptExecutor(parentView).executeScript(metadata.OnSelectedItemModified.Name || metadata.OnSelectedItemModified);
            });
        }

        if (parentView && metadata.OnItemDeleted) {
            dataSource.onItemDeleted(function () {
                new ScriptExecutor(parentView).executeScript(metadata.OnItemDeleted.Name || metadata.OnItemDeleted);
            });
        }
    },

    buildBindingBuilder: function(params){

        return function(bindingMetadata){
            return params.builder.buildBinding(bindingMetadata, {
                parentView: params.parentView,
                basePathOfProperty: params.basePathOfProperty
            });
        };
    },

    initFileProvider: function (dataSource, metadata) {

    }
});

_.extend(BaseDataSourceBuilder.prototype, DataSourceValidationNotifierMixin);<|endoftext|>


<commit_before>import inherited from 'mastodon/locales/ja.json';

const messages = {
  "getting_started.open_source_notice": "Glitchsocは{Mastodon}によるフリーなオープンソースソフトウェアです。誰でもGitHub（{github}）から開発に參加したり、問題を報告したりできます。",
  "layout.auto": "自動",
  "layout.current_is": "あなたの現在のレイアウト:",
  "layout.desktop": "Desktop",
  "layout.mobile": "Mobile",
  "navigation_bar.app_settings": "アプリ設定",
  "getting_started.onboarding": "解説を表示",
  "onboarding.page_one.federation": "{domain}はMastodonのインスタンスです。Mastodonとは、独立したサーバが連携して作るソーシャルネットワークです。これらのサーバーをインスタンスと呼びます。",
  "onboarding.page_one.welcome": "{domain}へようこそ！",
  "onboarding.page_six.github": "{domain}はGlitchsocを使用しています。Glitchsocは{Mastodon}のフレンドリーな{fork}で、どんなMastodonアプリやインスタンスとも互換性があります。Glitchsocは完全に無料で、オープンソースです。{github}でバグ報告や機能要望あるいは貢獻をすることが可能です。",
  "settings.auto_collapse": "自動折りたたみ",
  "settings.auto_collapse_all": "すべて",
  "settings.auto_collapse_lengthy": "長いトゥート",
  "settings.auto_collapse_media": "メディア付きトゥート",
  "settings.auto_collapse_notifications": "通知",
  "settings.auto_collapse_reblogs": "ブースト",
  "settings.auto_collapse_replies": "返信",
  "settings.close": "閉じる",
  "settings.collapsed_statuses": "トゥート",
  "settings.enable_collapsed": "トゥート折りたたみを有効にする",
  "settings.general": "一般",
  "settings.image_backgrounds": "画像背景",
  "settings.image_backgrounds_media": "折りたまれたメディア付きテゥートをプレビュー",
  "settings.image_backgrounds_users": "折りたまれたトゥートの背景を変更する",
  "settings.media": "メディア",
  "settings.media_letterbox": "メディアをレターボックス式で表示",
  "settings.media_fullwidth": "全幅メディアプリビュー",
  "settings.preferences": "ユーザー設定",
  "settings.wide_view": "ワイドビュー(Desktopレイアウトのみ)",
  "settings.navbar_under": "ナビを画面下部に移動させる(Mobileレイアウトのみ)",
  "settings.compose_box_opts": "コンポーズボックス設定",
  "settings.side_arm": "セカンダリートゥートボタン",
  "settings.layout": "レイアウト",
  "status.collapse": "折りたたむ",
  "status.uncollapse": "折りたたみを解除",

  "notification.markForDeletion": "選択",
  "notifications.clear": "通知を全てクリアする",
  "notifications.marked_clear_confirmation": "削除した全ての通知を完全に削除してもよろしいですか？",
  "notifications.marked_clear": "選択した通知を削除する",

  "notification_purge.btn_all": "すべて\n選択",
  "notification_purge.btn_none": "選択\n解除",
  "notification_purge.btn_invert": "選択を\n反転",
  "notification_purge.btn_apply": "選択したものを\n削除"
};

export default Object.assign({}, inherited, messages);
<commit_msg>" => '<commit_after>import inherited from 'mastodon/locales/ja.json';

const messages = {
  'getting_started.open_source_notice': 'Glitchsocは{Mastodon}によるフリーなオープンソースソフトウェアです。誰でもGitHub（{github}）から開発に參加したり、問題を報告したりできます。',
  'layout.auto': '自動',
  'layout.current_is': 'あなたの現在のレイアウト:',
  'layout.desktop': 'Desktop',
  'layout.mobile': 'Mobile',
  'navigation_bar.app_settings': 'アプリ設定',
  'getting_started.onboarding': '解説を表示',
  'onboarding.page_one.federation': '{domain}はMastodonのインスタンスです。Mastodonとは、独立したサーバが連携して作るソーシャルネットワークです。これらのサーバーをインスタンスと呼びます。',
  'onboarding.page_one.welcome': '{domain}へようこそ！',
  'onboarding.page_six.github': '{domain}はGlitchsocを使用しています。Glitchsocは{Mastodon}のフレンドリーな{fork}で、どんなMastodonアプリやインスタンスとも互換性があります。Glitchsocは完全に無料で、オープンソースです。{github}でバグ報告や機能要望あるいは貢獻をすることが可能です。',
  'settings.auto_collapse': '自動折りたたみ',
  'settings.auto_collapse_all': 'すべて',
  'settings.auto_collapse_lengthy': '長いトゥート',
  'settings.auto_collapse_media': 'メディア付きトゥート',
  'settings.auto_collapse_notifications': '通知',
  'settings.auto_collapse_reblogs': 'ブースト',
  'settings.auto_collapse_replies': '返信',
  'settings.close': '閉じる',
  'settings.collapsed_statuses': 'トゥート',
  'settings.enable_collapsed': 'トゥート折りたたみを有効にする',
  'settings.general': '一般',
  'settings.image_backgrounds': '画像背景',
  'settings.image_backgrounds_media': '折りたまれたメディア付きテゥートをプレビュー',
  'settings.image_backgrounds_users': '折りたまれたトゥートの背景を変更する',
  'settings.media': 'メディア',
  'settings.media_letterbox': 'メディアをレターボックス式で表示',
  'settings.media_fullwidth': '全幅メディアプリビュー',
  'settings.preferences': 'ユーザー設定',
  'settings.wide_view': 'ワイドビュー(Desktopレイアウトのみ)',
  'settings.navbar_under': 'ナビを画面下部に移動させる(Mobileレイアウトのみ)',
  'settings.compose_box_opts': 'コンポーズボックス設定',
  'settings.side_arm': 'セカンダリートゥートボタン',
  'settings.layout': 'レイアウト',
  'status.collapse': '折りたたむ',
  'status.uncollapse': '折りたたみを解除',

  'notification.markForDeletion': '選択',
  'notifications.clear': '通知を全てクリアする',
  'notifications.marked_clear_confirmation': '削除した全ての通知を完全に削除してもよろしいですか？',
  'notifications.marked_clear': '選択した通知を削除する',

  'notification_purge.btn_all': 'すべて\n選択',
  'notification_purge.btn_none': '選択\n解除',
  'notification_purge.btn_invert': '選択を\n反転',
  'notification_purge.btn_apply': '選択したものを\n削除'
};

export default Object.assign({}, inherited, messages);
<|endoftext|>


<commit_before>#!/usr/bin/env node

/**
 * Git COMMIT-MSG hook for validating commit message
 * See https://docs.google.com/document/d/1rk04jEuGfk9kYzfqCuOlPTSJw3hEDZJTBN5E5f1SALo/edit
 *
 * Installation:
 * >> cd <angular-repo>
 * >> ln -s validate-commit-msg.js .git/hooks/commit-msg
 */
var fs = require('fs');
var util = require('util');

var MAX_LENGTH = 70;
var PATTERN = /^(?:fixup!\s*)?(\w*)(\(([\w\$\.\-\*/]*)\))?\: (.*)$/;
var IGNORED = /^WIP\:/;
var TYPES = {
  feat: true,
  fix: true,
  docs: true,
  style: true,
  refactor: true,
  test: true,
  chore: true,
  revert: true
};

var error = function() {
  // gitx does not display it
  // http://gitx.lighthouseapp.com/projects/17830/tickets/294-feature-display-hook-error-message-when-hook-fails
  // https://groups.google.com/group/gitx/browse_thread/thread/a03bcab60844b812
  console.error('INVALID COMMIT MSG: ' + util.format.apply(null, arguments));
};

var validateMessage = function(message) {
  var isValid = true;

  if (IGNORED.test(message)) {
    console.log('Commit message validation ignored.');
    return true;
  }

  if (message.length > MAX_LENGTH) {
    error('is longer than %d characters !', MAX_LENGTH);
    isValid = false;
  }

  var match = PATTERN.exec(message);

  if (!match) {
    error('does not match "<type>(<scope>): <subject>" ! was: ' + message);
    return false;
  }

  var type = match[1];
  var scope = match[3];
  var subject = match[4];

  if (!TYPES.hasOwnProperty(type)) {
    error('"%s" is not allowed type !', type);
    return false;
  }

  // Some more ideas, do want anything like this ?
  // - allow only specific scopes (eg. fix(docs) should not be allowed ?
  // - auto correct the type to lower case ?
  // - auto correct first letter of the subject to lower case ?
  // - auto add empty line after subject ?
  // - auto remove empty () ?
  // - auto correct typos in type ?
  // - store incorrect messages, so that we can learn

  return isValid;
};

var firstLineFromBuffer = function(buffer) {
  return buffer.toString().split('\n').shift();
};

// publish for testing
exports.validateMessage = validateMessage;

// hacky start if not run by jasmine :-D
if (process.argv.join('').indexOf('jasmine-node') === -1) {
  var commitMsgFile = process.argv[2];
  var incorrectLogFile = commitMsgFile.replace('COMMIT_EDITMSG', 'logs/incorrect-commit-msgs');

  fs.readFile(commitMsgFile, function(err, buffer) {
    var msg = firstLineFromBuffer(buffer);

    if (!validateMessage(msg)) {
      fs.appendFile(incorrectLogFile, msg + '\n', function() {
        process.exit(1);
      });
    } else {
      process.exit(0);
    }
  });
}
<commit_msg>revert(validate-commit-msg): fix incorrect comment<commit_after>#!/usr/bin/env node

/**
 * Git COMMIT-MSG hook for validating commit message
 * See https://docs.google.com/document/d/1rk04jEuGfk9kYzfqCuOlPTSJw3hEDZJTBN5E5f1SALo/edit
 *
 * Installation:
 * >> cd <angular-repo>
 * >> ln -s ../../validate-commit-msg.js .git/hooks/commit-msg
 */
var fs = require('fs');
var util = require('util');

var MAX_LENGTH = 70;
var PATTERN = /^(?:fixup!\s*)?(\w*)(\(([\w\$\.\-\*/]*)\))?\: (.*)$/;
var IGNORED = /^WIP\:/;
var TYPES = {
  feat: true,
  fix: true,
  docs: true,
  style: true,
  refactor: true,
  test: true,
  chore: true,
  revert: true
};

var error = function() {
  // gitx does not display it
  // http://gitx.lighthouseapp.com/projects/17830/tickets/294-feature-display-hook-error-message-when-hook-fails
  // https://groups.google.com/group/gitx/browse_thread/thread/a03bcab60844b812
  console.error('INVALID COMMIT MSG: ' + util.format.apply(null, arguments));
};

var validateMessage = function(message) {
  var isValid = true;

  if (IGNORED.test(message)) {
    console.log('Commit message validation ignored.');
    return true;
  }

  if (message.length > MAX_LENGTH) {
    error('is longer than %d characters !', MAX_LENGTH);
    isValid = false;
  }

  var match = PATTERN.exec(message);

  if (!match) {
    error('does not match "<type>(<scope>): <subject>" ! was: ' + message);
    return false;
  }

  var type = match[1];
  var scope = match[3];
  var subject = match[4];

  if (!TYPES.hasOwnProperty(type)) {
    error('"%s" is not allowed type !', type);
    return false;
  }

  // Some more ideas, do want anything like this ?
  // - allow only specific scopes (eg. fix(docs) should not be allowed ?
  // - auto correct the type to lower case ?
  // - auto correct first letter of the subject to lower case ?
  // - auto add empty line after subject ?
  // - auto remove empty () ?
  // - auto correct typos in type ?
  // - store incorrect messages, so that we can learn

  return isValid;
};

var firstLineFromBuffer = function(buffer) {
  return buffer.toString().split('\n').shift();
};

// publish for testing
exports.validateMessage = validateMessage;

// hacky start if not run by jasmine :-D
if (process.argv.join('').indexOf('jasmine-node') === -1) {
  var commitMsgFile = process.argv[2];
  var incorrectLogFile = commitMsgFile.replace('COMMIT_EDITMSG', 'logs/incorrect-commit-msgs');

  fs.readFile(commitMsgFile, function(err, buffer) {
    var msg = firstLineFromBuffer(buffer);

    if (!validateMessage(msg)) {
      fs.appendFile(incorrectLogFile, msg + '\n', function() {
        process.exit(1);
      });
    } else {
      process.exit(0);
    }
  });
}
<|endoftext|>


<commit_before>Tools = function(self){
  this.calendar = function (params) {
    if (typeof(params.self) !== undefined) {this.self = self = params.self;}
    if (typeof(params.data) !== undefined) {this.data = data = params.data;}
    this.current_date = new Date();
    this.day_labels = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
    this.month_labels = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
    this.month = (isNaN(data.month) || month === null) ? this.current_date.getMonth() : data.month;
    this.year = (isNaN(data.year) || year === null) ? this.current_date.getFullYear() : data.year;
    this.registerButtons = function(){
      var buttons = document.getElementsByClassName('cal-btn');
      for (var i=0;i<buttons.length;i++){
        buttons[i].onclick = function(){
          if (data.type == 'Tickets') {
            self.Tickets.showTickets(data[this.dataset.day]);
          } else {
            bootbox.dialog({
              message: self.Tools.listClients(data[this.dataset.day]),
              title: "Clients (click row to open client)",
              className: "ticket-list",
              buttons: {},
            });
          }
        };
      }
    };
    this.draw = function(ele){
        // get dates
        var startingDay = new Date(this.year, this.month, 1).getDay(),
        endingDate = new Date(this.year, this.month + 1, 0),
        monthLength = endingDate.getDate(),
        inLastRow = 6 - endingDate.getDay();
        if (this.month > 11) {
          this.month -= 12;
          this.year += 1;
        } else if (this.month < 0) {
          this.month += 12;
          this.year -= 1;
        }

        // generate header
        this.table = document.createElement('table');
        this.head = document.createElement('thead');
        this.prev = document.createElement('th');
        this.next = document.createElement('th');
        this.title = document.createElement('th');
        this.days = document.createElement('tr');
        this.body = document.createElement('tbody');
        this.cells = '';
        this.table.setAttribute('class', 'calendar');
        this.table.appendChild(this.head);
        this.head.appendChild(this.prev);
        this.prev.innerHTML = "«";
        this.prev.id = 'prev';
        this.head.appendChild(this.title);
        this.head.appendChild(this.next);
        this.next.innerHTML = "»";
        this.next.id = 'next';
        this.table.appendChild(this.days);
        this.title.innerHTML = this.month_labels[this.month] + " " + this.year;
        this.title.setAttribute('colspan', '5');
        this.days.setAttribute('class', 'header');
        for (var i = 0; i <= 6; i++ ) this.cells += '' + this.day_labels[i] + '	';
        this.days.innerHTML = this.cells;

        //generate calendar
        this.days = [];
        this.rows = [];
        this.rows[0] = document.createElement('tr');
        if ( startingDay ) {
          this.days[0] = this.rows[0].appendChild(document.createElement('td'));
          this.days[0].colSpan = startingDay;
        }
        for (var day = 1; day <= monthLength; day++) {
          var row = this.rows[this.rows.length - 1];
          this.days[day] = row.appendChild(document.createElement('td'));
          var number = this.days[day].appendChild(document.createElement('div'));
          number.innerHTML = day;
          //if data argument present, append key value for current day
          if (data) {
            var today = (this.month+1)+'/'+day+'/'+this.year;
            if (data[today]) {
              this.days[day].innerHTML += '
'+Object.keys(data[today]).length+' '+data.type+'

';
              var button = this.days[day].appendChild(document.createElement('button'));
              button.className = 'btn btn-primary cal-btn';
              button.setAttribute('data-day', today);
              button.innerHTML = 'Show '+data.type;
              this.days[day].innerHTML += '
';
            }
          }
          if ( (day + startingDay) % 7 === 0 && day != monthLength ) this.rows[this.rows.length] = document.createElement('tr');
        }
        if ( inLastRow ) {
          var day = this.days.length - 1, row = this.rows.length - 1;
          this.days[day] = this.rows[row].appendChild(document.createElement('td'));
          this.days[day].colSpan = inLastRow;
        }
        for (var i=0;i<this.rows.length;i++) {this.table.appendChild(this.rows[i]);}
        if (ele) {
          ele.innerHTML = this.table.outerHTML;
        } else {
          return this.table.outerHTML;
        }
        this.registerButtons();
        // save reference to dom node in object
        this.node = ele;
        // save reference to calendar object for enclosure scope
        var cal = this;
        (function(){
          // save calendar object reference for listeners
          var self = this;
          self.prev.addEventListener('click', function(){
            cal.month--;
            cal.draw(cal.node);
            cal.registerButtons();
          });
          self.next.addEventListener('click', function(){
            cal.month++;
            cal.draw(cal.node);
            cal.registerButtons();
          });
          document.onkeydown = function(e) {
            e = e || window.event;
              if (e.keyCode == '37') {
                // left arrow
                cal.month--;
                cal.draw(cal.node);
              } else if (e.keyCode == '39') {
                // right arrow
                cal.month++;
                cal.draw(cal.node);
              }
          };
        })();
      };
  };
  this.bobParse = function(raw){
    var clients = [], data = {type: 'Clients'};
    // exit on null entry
    if ( raw === null ) return;
    for (var cells = raw.replace(/Basic[\r\n\s\ta-z]*Partner ID[\t\s]+/i, '').split(/\t/),
      client = {}, cols = ['Account ID',  'Account Start Date',  'Account Bucket',  'Account Spend',
      'Date of Next Ticket',  'Engaged Status',  'Timezone',  'Welcome Call',  'Keyword Research',
      'Local Profile Tab',  'Obtain or Create Google Logins',  'Create and Verify Google Profile',
      'Obtain Site Access',  'Implement Onsite Edits',  'Create Yahoo Account',  'Create and Verify Yahoo Profile',
      'Create and Verify Bing Profile',  'Install Analytics',  'Company Name',  'Contact Name',  'Website URL',
      'Email Address',  'Phone Number',  'Partner ID',];
      cells.length>0;){
      var keys = Object.keys(client).length;
      if (keys<cols.length-1){
        var param = cols[keys];
        client[param] = cells[0];
        cells.shift();
      } else {
        client.count = keys;
        clients.push(client);
        client = {};
      }
    }
    for (var i=0;i<clients.length;i++){
      var date = clients[i]['Date of Next Ticket'];
      if (data[date] === undefined) data[date] = [];
      data[date].push(clients[i]);
    }
    return data;
  };
  this.todoistExport = function (){
    var view, data,
    taskDate = function(day){
      var days = [new Date(),new Date(),new Date()];
      if (days[0].getDay() == 5) {
        days[1].setDate(days[0].getDate()+3);
        days[2].setDate(days[0].getDate()+4);
      } else if (days[0].getDay() == 4) {
        days[1].setDate(days[0].getDate()+1);
        days[2].setDate(days[0].getDate()+4);
      } else {
        days[1].setDate(days[0].getDate()+1);
        days[2].setDate(days[0].getDate()+2);
      }
      return 'date '+days[day].toDateString().substring(4);
    },
    textarea = document.createElement('textarea'), text = '',
    modal = bootbox.alert({
        size: 'large',
        message: 'placeholder',
        callback: function(){},
    })[0].children[0].children[0].children[0].children[1];
    if (ko.dataFor(window.app) !== undefined){
      view = ko.dataFor(window.app).contentViewModel();
    } else if (ko.dataFor(app) !== undefined){
      view = ko.dataFor(app).contentViewModel();
    }
    if (! view.hasOwnProperty('customersDataTable')){
      modal.remove();
      bootbox.alert('No client data available in active view.
Try running this tool after searching for a client.');
      return 1;
    } else {
      if (typeof(view.customersDataTable) === undefined) {
        console.log('No customersDataTable');
        return;
      } else {
        data = view.customersDataTable.dataSource()[0];
      }
      text += data.CustomerId+' - '+data.Name;
      text += '\n[[NOTE]]: https://launchpad.boostability.com/#customerservice/customersearch/'+data.CustomerId;
      text += '\n...'+data.CustomerId+' Welcome Call [['+taskDate(2)+']]';
      text += '\n......'+data.CustomerId+' 1st Welcome Call [['+taskDate(0)+']]';
      text += '\n......'+data.CustomerId+' 1st Welcome Email [['+taskDate(0)+']]';
      text += '\n......'+data.CustomerId+' 2nd Welcome Call [['+taskDate(1)+']]';
      text += '\n......'+data.CustomerId+' 2nd Welcome Email [['+taskDate(1)+']]';
      text += '\n......'+data.CustomerId+' 3rd Welcome Call [['+taskDate(2)+']]';
      text += '\n......'+data.CustomerId+' 3rd Welcome Email [['+taskDate(2)+']]';
      text += '\n...'+data.CustomerId+' Keyword Research';
      text += '\n...'+data.CustomerId+' Local Profile';
      textarea.value = text;
      modal.parentNode.parentNode.parentNode.style.width = "50em";
      modal.innerHTML = '
'+text+'
';
    }
  };
  this.listClients = function(clients){
    var list = document.createElement('table'),
    head = list.appendChild(document.createElement('thead')),
    body = list.appendChild(document.createElement('tbody'));
    head.innerHTML = '
Account ID	Timezone	Account Bucket	Date of Next Ticket	Account Spend	Engaged Status
';
    for (var i=0;i<clients.length;i++){
      var client = clients[i],
      listItem = document.createElement('tr'),
      content = ''+client['Account ID']+'	'+client['Timezone']+'	';
      content += ''+client['Account Bucket']+'	'+client['Date of Next Ticket']+'	';
      content += ''+client['Account Spend']+'	'+client['Engaged Status']+'	';
      listItem.innerHTML = content;
      listItem.setAttribute('data-client', client['Account ID']);
      listItem.onclick = function(){
        self.Tools.openClient(this.dataset.client);
        var modals = document.getElementsByClassName('bootbox-close-button');
        for (var i=0;i<modals.length;i++){modals[i].click();}
      };
      body.appendChild(listItem);
    }
    sorttable.makeSortable(list);
    return list;
  };
  this.openClient = function(client) {
    var lp = ko.dataFor(app);
    lp.showManageCustomer(client);
  };
  this.closeClient = function() {
    var buttons = document.getElementsByClassName('close'),
    i = buttons.length - 1;
    if (i >= 1) buttons[i - 1].click();
  };
  this.createElement = function(parent, tag, attributes) {
    var element = parent.appendChild(document.createElement(tag));
    for (var attribute in attributes) {
      if (attributes.hasOwnProperty(attribute)) {
        element[attribute] = attributes[attribute];
      }
    }
    return element;
  };
  this.nextTicket = function(tickets){
    self.Tools.closeClient();
    self.Tools.openClient(tickets[0].CustomerId);
  };
  this.httpRequest = function(data){
    var request = new XMLHttpRequest();
    request.open( data.method, data.url, false );
    for (var header in data.headers) {
      if (data.headers.hasOwnProperty(header)) {
          request.setRequestHeader(header, data.headers[header]);
      }
    }
    request.send( data.body );
    return request.responseText;
  };
  this.getClient = function(client){
    var response = this.httpRequest({
      method: 'GET',
      url:'https://launchpad.boostability.com/CustomerApi/Customer_Select?customerId='+client,
      headers: {
        'Accept': '*/*',
        'Accept-Encoding': 'gzip, deflate, sdch',
        'Accept-Language': 'en-US,en;q=0.8',
        'Cookie': document.cookie,
        'Referer': 'https://launchpad.boostability.com/',
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36',
        'X-Requested-With': 'XMLHttpRequest',
      }
    });
    return JSON.parse(response);
  };
  this.getKeywords = function(client){
    var activeResponse = this.httpRequest({
      method: 'GET',
      url:'https://launchpad.boostability.com/WebsiteUrlApi/WebsiteUrl_SelectActiveUrls?customerId='+client,
      headers: {
        'Accept': '*/*',
        'Accept-Encoding': 'gzip, deflate, sdch',
        'Accept-Language': 'en-US,en;q=0.8',
        'Cookie': document.cookie,
        'Referer': 'https://launchpad.boostability.com/',
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36',
        'X-Requested-With': 'XMLHttpRequest',
      }
    }),
    trackingResponse = this.httpRequest({
      method: 'GET',
      url:'https://launchpad.boostability.com/WebsiteKeywordTrackingApi/WebsiteKeywordTracking_Select?customerId='+client,
      headers: {
        'Accept': '*/*',
        'Accept-Encoding': 'gzip, deflate, sdch',
        'Accept-Language': 'en-US,en;q=0.8',
        'Cookie': document.cookie,
        'Referer': 'https://launchpad.boostability.com/',
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36',
        'X-Requested-With': 'XMLHttpRequest',
      }
    });
    return {active: JSON.parse(activeResponse).Data, tracking: JSON.parse(trackingResponse).Data};
  };
};
<commit_msg>Update tools.js<commit_after>Tools = function(self){
  this.calendar = function (params) {
    if (typeof(params.self) !== undefined) {this.self = self = params.self;}
    if (typeof(params.data) !== undefined) {this.data = data = params.data;}
    this.current_date = new Date();
    this.day_labels = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
    this.month_labels = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
    this.month = (isNaN(data.month) || month === null) ? this.current_date.getMonth() : data.month;
    this.year = (isNaN(data.year) || year === null) ? this.current_date.getFullYear() : data.year;
    this.registerButtons = function(){
      var buttons = document.getElementsByClassName('cal-btn');
      for (var i=0;i<buttons.length;i++){
        buttons[i].onclick = function(){
          if (data.type == 'Tickets') {
            self.Tickets.showTickets(data[this.dataset.day]);
          } else {
            bootbox.dialog({
              message: self.Tools.listClients(data[this.dataset.day]),
              title: "Clients (click row to open client)",
              className: "ticket-list",
              buttons: {},
            });
          }
        };
      }
    };
    this.draw = function(ele){
        // get dates
        var startingDay = new Date(this.year, this.month, 1).getDay(),
        endingDate = new Date(this.year, this.month + 1, 0),
        monthLength = endingDate.getDate(),
        inLastRow = 6 - endingDate.getDay();
        if (this.month > 11) {
          this.month -= 12;
          this.year += 1;
        } else if (this.month < 0) {
          this.month += 12;
          this.year -= 1;
        }

        // generate header
        this.table = document.createElement('table');
        this.head = document.createElement('thead');
        this.prev = document.createElement('th');
        this.next = document.createElement('th');
        this.title = document.createElement('th');
        this.days = document.createElement('tr');
        this.body = document.createElement('tbody');
        this.cells = '';
        this.table.setAttribute('class', 'calendar');
        this.table.appendChild(this.head);
        this.head.appendChild(this.prev);
        this.prev.innerHTML = "«";
        this.prev.id = 'prev';
        this.head.appendChild(this.title);
        this.head.appendChild(this.next);
        this.next.innerHTML = "»";
        this.next.id = 'next';
        this.table.appendChild(this.days);
        this.title.innerHTML = this.month_labels[this.month] + " " + this.year;
        this.title.setAttribute('colspan', '5');
        this.days.setAttribute('class', 'header');
        for (var i = 0; i <= 6; i++ ) this.cells += '' + this.day_labels[i] + '	';
        this.days.innerHTML = this.cells;

        //generate calendar
        this.days = [];
        this.rows = [];
        this.rows[0] = document.createElement('tr');
        if ( startingDay ) {
          this.days[0] = this.rows[0].appendChild(document.createElement('td'));
          this.days[0].colSpan = startingDay;
        }
        for (var day = 1; day <= monthLength; day++) {
          var row = this.rows[this.rows.length - 1];
          this.days[day] = row.appendChild(document.createElement('td'));
          var number = this.days[day].appendChild(document.createElement('div'));
          number.innerHTML = day;
          //if data argument present, append key value for current day
          if (data) {
            var today = (this.month+1)+'/'+day+'/'+this.year;
            if (data[today]) {
              this.days[day].innerHTML += '
'+Object.keys(data[today]).length+' '+data.type+'

';
              var button = this.days[day].appendChild(document.createElement('button'));
              button.className = 'btn btn-primary cal-btn';
              button.setAttribute('data-day', today);
              button.innerHTML = 'Show '+data.type;
              this.days[day].innerHTML += '
';
            }
          }
          if ( (day + startingDay) % 7 === 0 && day != monthLength ) this.rows[this.rows.length] = document.createElement('tr');
        }
        if ( inLastRow ) {
          var day = this.days.length - 1, row = this.rows.length - 1;
          this.days[day] = this.rows[row].appendChild(document.createElement('td'));
          this.days[day].colSpan = inLastRow;
        }
        for (var i=0;i<this.rows.length;i++) {this.table.appendChild(this.rows[i]);}
        if (ele) {
          ele.innerHTML = this.table.outerHTML;
        } else {
          return this.table.outerHTML;
        }
        this.registerButtons();
        // save reference to dom node in object
        this.node = ele;
        // save reference to calendar object for enclosure scope
        var cal = this;
        (function(){
          // save calendar object reference for listeners
          var self = this;
          self.prev.addEventListener('click', function(){
            cal.month--;
            cal.draw(cal.node);
            cal.registerButtons();
          });
          self.next.addEventListener('click', function(){
            cal.month++;
            cal.draw(cal.node);
            cal.registerButtons();
          });
          document.onkeydown = function(e) {
            e = e || window.event;
              if (e.keyCode == '37') {
                // left arrow
                cal.month--;
                cal.draw(cal.node);
              } else if (e.keyCode == '39') {
                // right arrow
                cal.month++;
                cal.draw(cal.node);
              }
          };
        })();
      };
  };
  this.bobParse = function(raw){
    var clients = [], data = {type: 'Clients'};
    // exit on null entry
    if ( raw === null ) return;
    for (var cells = raw.replace(/Basic[\r\n\s\ta-z]*Partner ID[\t\s]+/i, '').split(/\t/),
      client = {}, cols = ['Account ID',  'Account Start Date',  'Account Bucket',  'Account Spend',
      'Date of Next Ticket',  'Engaged Status',  'Timezone',  'Welcome Call',  'Keyword Research',
      'Local Profile Tab',  'Obtain or Create Google Logins',  'Create and Verify Google Profile',
      'Obtain Site Access',  'Implement Onsite Edits',  'Create Yahoo Account',  'Create and Verify Yahoo Profile',
      'Create and Verify Bing Profile',  'Install Analytics',  'Company Name',  'Contact Name',  'Website URL',
      'Email Address',  'Phone Number',  'Partner ID',];
      cells.length>0;){
      var keys = Object.keys(client).length;
      if (keys<cols.length-1){
        var param = cols[keys];
        client[param] = cells[0];
        cells.shift();
      } else {
        client.count = keys;
        clients.push(client);
        client = {};
      }
    }
    for (var i=0;i<clients.length;i++){
      var date = clients[i]['Date of Next Ticket'];
      if (data[date] === undefined) data[date] = [];
      data[date].push(clients[i]);
    }
    return data;
  };
  this.todoistExport = function (){
    var view, data,
    taskDate = function(day){
      var days = [new Date(),new Date(),new Date()];
      if (days[0].getDay() == 5) {
        days[1].setDate(days[0].getDate()+3);
        days[2].setDate(days[0].getDate()+4);
      } else if (days[0].getDay() == 4) {
        days[1].setDate(days[0].getDate()+1);
        days[2].setDate(days[0].getDate()+4);
      } else {
        days[1].setDate(days[0].getDate()+1);
        days[2].setDate(days[0].getDate()+2);
      }
      return 'date '+days[day].toDateString().substring(4);
    },
    textarea = document.createElement('textarea'), text = '',
    modal = bootbox.alert({
        size: 'large',
        message: 'placeholder',
        callback: function(){},
    })[0].children[0].children[0].children[0].children[1];
    if (ko.dataFor(window.app) !== undefined){
      view = ko.dataFor(window.app).contentViewModel();
    } else if (ko.dataFor(app) !== undefined){
      view = ko.dataFor(app).contentViewModel();
    }
    if (! view.hasOwnProperty('customersDataTable')){
      modal.remove();
      bootbox.alert('No client data available in active view.
Try running this tool after searching for a client.');
      return 1;
    } else {
      if (typeof(view.customersDataTable) === undefined) {
        console.log('No customersDataTable');
        return;
      } else {
        data = view.customersDataTable.dataSource()[0];
      }
      text += data.CustomerId+' - '+data.Name;
      text += '\n[[NOTE]]: https://launchpad.boostability.com/#customerservice/customersearch/'+data.CustomerId;
      text += '\n...'+data.CustomerId+' Welcome Call [['+taskDate(2)+']]';
      text += '\n......'+data.CustomerId+' 1st Welcome Call [['+taskDate(0)+']]';
      text += '\n......'+data.CustomerId+' 1st Welcome Email [['+taskDate(0)+']]';
      text += '\n......'+data.CustomerId+' 2nd Welcome Call [['+taskDate(1)+']]';
      text += '\n......'+data.CustomerId+' 2nd Welcome Email [['+taskDate(1)+']]';
      text += '\n......'+data.CustomerId+' 3rd Welcome Call [['+taskDate(2)+']]';
      text += '\n......'+data.CustomerId+' 3rd Welcome Email [['+taskDate(2)+']]';
      text += '\n...'+data.CustomerId+' Keyword Research';
      text += '\n...'+data.CustomerId+' Local Profile';
      textarea.value = text;
      modal.parentNode.parentNode.parentNode.style.width = "50em";
      modal.innerHTML = '
'+text+'
';
    }
  };
  this.listClients = function(clients){
    var list = document.createElement('table'),
    head = list.appendChild(document.createElement('thead')),
    body = list.appendChild(document.createElement('tbody'));
    head.innerHTML = '
Account ID	Timezone	Account Bucket	Date of Next Ticket	Account Spend	Engaged Status
';
    for (var i=0;i<clients.length;i++){
      var client = clients[i],
      listItem = document.createElement('tr'),
      content = ''+client['Account ID']+'	'+client['Timezone']+'	';
      content += ''+client['Account Bucket']+'	'+client['Date of Next Ticket']+'	';
      content += ''+client['Account Spend']+'	'+client['Engaged Status']+'	';
      listItem.innerHTML = content;
      listItem.setAttribute('data-client', client['Account ID']);
      listItem.onclick = function(){
        self.Tools.openClient(this.dataset.client);
        var modals = document.getElementsByClassName('bootbox-close-button');
        for (var i=0;i<modals.length;i++){modals[i].click();}
      };
      body.appendChild(listItem);
    }
    sorttable.makeSortable(list);
    return list;
  };
  this.openClient = function(client) {
    var lp = ko.dataFor(app);
    lp.showManageCustomer(client);
  };
  this.closeClient = function() {
    var buttons = document.getElementsByClassName('close'),
    i = buttons.length - 1;
    if (i >= 1) buttons[i - 1].click();
  };
  this.createElement = function(parent, tag, attributes) {
    var element = parent.appendChild(document.createElement(tag));
    for (var attribute in attributes) {
      if (attributes.hasOwnProperty(attribute)) {
        element[attribute] = attributes[attribute];
      }
    }
    return element;
  };
  this.nextTicket = function(tickets){
    self.Tools.closeClient();
    self.Tools.openClient(tickets[0].CustomerId);
  };
  this.httpRequest = function(data){
    var request = new XMLHttpRequest();
    request.open( data.method, data.url, false );
    for (var header in data.headers) {
      if (data.headers.hasOwnProperty(header)) {
          request.setRequestHeader(header, data.headers[header]);
      }
    }
    request.send( data.body );
    return request.responseText;
  };
  this.getClient = function(client){
    var response = this.httpRequest({
      method: 'GET',
      url:'https://launchpad.boostability.com/CustomerApi/Customer_Select?customerId='+client,
      headers: {
        'Accept': '*/*',
        'Accept-Encoding': 'gzip, deflate, sdch',
        'Accept-Language': 'en-US,en;q=0.8',
        'Cookie': document.cookie,
        'Referer': 'https://launchpad.boostability.com/',
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36',
        'X-Requested-With': 'XMLHttpRequest',
      }
    });
    return JSON.parse(response);
  };
  this.getKeywords = function(client){
    var activeResponse = this.httpRequest({
      method: 'GET',
      url:'https://launchpad.boostability.com/WebsiteUrlApi/WebsiteUrl_SelectActiveUrls?customerId='+client,
      headers: {
        'Accept': '*/*',
        'Accept-Encoding': 'gzip, deflate, sdch',
        'Accept-Language': 'en-US,en;q=0.8',
        'Cookie': document.cookie,
        'Referer': 'https://launchpad.boostability.com/',
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36',
        'X-Requested-With': 'XMLHttpRequest',
      }
    }),
    trackingResponse = this.httpRequest({
      method: 'GET',
      url:'https://launchpad.boostability.com/WebsiteKeywordTrackingApi/WebsiteKeywordTracking_Select?customerId='+client,
      headers: {
        'Accept': '*/*',
        'Accept-Encoding': 'gzip, deflate, sdch',
        'Accept-Language': 'en-US,en;q=0.8',
        'Cookie': document.cookie,
        'Referer': 'https://launchpad.boostability.com/',
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36',
        'X-Requested-With': 'XMLHttpRequest',
      }
    });
    return {active: JSON.parse(activeResponse).Data, tracking: JSON.parse(trackingResponse).Data};
  };
  this.checkOpts = function(){
  if (window.location.href.indexOf('?') !== -1) {
    var opts = {},
    params = window.location.href.split('?')[1].split('&');
    for (var i=0;i<params.length;i++) {
      var vals = params[i].split('=');
      opts[vals[0]] = vals[1];
    }
    return opts;
  };
}
};
<|endoftext|>


<commit_before>var http = require('http');
var io = require('socket.io');
var url = require('url');
var path = require('path');
var fs = require('fs');
var jQuery = require('jquery');
var redis = require('redis');

server = http.createServer(function(request, response){
    var uri = url.parse(request.url).pathname;
    if (uri === '/') {
        var filename = path.join(process.cwd(), 'index.html');
    } else {
        var filename = path.join(process.cwd(), uri);
    }
    path.exists(filename, function(exists) {
        if (!exists) {
            response.writeHead(404, {"Content-Type": "text/plain"});
            response.write("404 Not Found\n");
            response.end();
            return;
        }

        fs.readFile(filename, "binary", function(err, file) {
            if (err) {
                response.writeHead(500, {"Content-Type": "text/plain"});
                response.write(err + "\n");
                response.end();
                return;
            }

            response.writeHead(200);
            response.write(file, "binary");
            response.end();
        });
    });
});
server.listen(8002);

// REDIS
var redisClient = redis.createClient();

redisClient.on('error', function (err) {
    console.log('Redis error ' + err);
});

// socket.io
var socket = io.listen(server);

var socketClients = [];

socket.on('connection', function(client) {
    client.identifier = socketClients.length;
    socketClients.push(client);
    console.log("New client " + client.identifier + " connected, client list is now", socketClients.length);

    // Send current data from Redis
    redisClient.hkeys('anon-ontologies', function(err, ontologies) {
        if (ontologies === undefined) {
            return;
        }
        ontologies.forEach(function(ontology, i) {
            redisClient.lrange('anon-' + ontology, 0, -1, function(err, anonItems) {
                anonItems.forEach(function(anonItem, i) {
                    console.log(anonItem);
                    client.send(JSON.parse(anonItem));
                });
            });
        });
    });
    redisClient.hkeys('ontologies', function(err, ontologies) {
        if (ontologies === undefined) {
            return;
        }
        ontologies.forEach(function(ontology, i) {
            redisClient.hkeys(ontology, function(err, itemIdentifiers) {
                itemIdentifiers.forEach(function(itemIdentifier, i) {
                    redisClient.hget(ontology, itemIdentifier, function(err, item) {
                        console.log(item);
                        client.send(JSON.parse(item));
                    });
                });
            });
        });
    });

    client.on('message', function(data) {
        console.log("Got message from client " + client.identifier, data);

        if (typeof data === 'object') {
            if (data['a'] !== undefined) {
                if (data['@'] !== '<undefined>') {
                    // Identified object, save to Redis hash based on type
                    redisClient.hset(data['a'], data['@'], JSON.stringify(data));
                    redisClient.hset('ontologies', data['a'], true);
                } else {
                    // Anonymous entity, save to Redis list based on type
                    redisClient.rpush('anon-' + data['a'], JSON.stringify(data));
                    redisClient.hset('anon-ontologies', data['a'], true);
                }
            }
        }

        jQuery.each(socketClients, function(index, socketClient) {
            if (socketClient == client) {
                return true;
            }
            console.log("Forwarding data to " + socketClient.identifier);

            socketClient.send(data);
        });
    });

    client.on('disconnect', function() {
        socketClients = jQuery.grep(socketClients, function(value) {
            return value != client;
        });

        console.log("Client " + client.identifier + " disconnected, client list is now", socketClients.length);
    })
});
<commit_msg>Switch to express as the HTTP server<commit_after>var express = require('express');
var io = require('socket.io');
var url = require('url');
var path = require('path');
var fs = require('fs');
var jQuery = require('jquery');
var redis = require('redis');

var server = require('express').createServer();

server.use('/js', express.static(process.cwd() + '/js'));
server.use('/styles', express.static(process.cwd() + '/styles'));

server.get('/', function(request, response) {
    var filename = path.join(process.cwd(), 'index.html');
    path.exists(filename, function(exists) {
        if (!exists) {
            response.writeHead(404, {"Content-Type": "text/plain"});
            response.write("404 Not Found\n");
            response.end();
            return;
        }

        fs.readFile(filename, "binary", function(err, file) {
            response.writeHead(200);
            response.write(file, "binary");
            response.end();
        });
    });
});

server.listen(8002);

// REDIS
var redisClient = redis.createClient();

redisClient.on('error', function (err) {
    console.log('Redis error ' + err);
});

// socket.io
var socket = io.listen(server);

var socketClients = [];

socket.on('connection', function(client) {
    client.identifier = socketClients.length;
    socketClients.push(client);
    console.log("New client " + client.identifier + " connected, client list is now", socketClients.length);

    // Send current data from Redis
    redisClient.hkeys('anon-ontologies', function(err, ontologies) {
        if (ontologies === undefined) {
            return;
        }
        ontologies.forEach(function(ontology, i) {
            redisClient.lrange('anon-' + ontology, 0, -1, function(err, anonItems) {
                anonItems.forEach(function(anonItem, i) {
                    console.log(anonItem);
                    client.send(JSON.parse(anonItem));
                });
            });
        });
    });
    redisClient.hkeys('ontologies', function(err, ontologies) {
        if (ontologies === undefined) {
            return;
        }
        ontologies.forEach(function(ontology, i) {
            redisClient.hkeys(ontology, function(err, itemIdentifiers) {
                itemIdentifiers.forEach(function(itemIdentifier, i) {
                    redisClient.hget(ontology, itemIdentifier, function(err, item) {
                        console.log(item);
                        client.send(JSON.parse(item));
                    });
                });
            });
        });
    });

    client.on('message', function(data) {
        console.log("Got message from client " + client.identifier, data);

        if (typeof data === 'object') {
            if (data['a'] !== undefined) {
                if (data['@'] !== '<undefined>') {
                    // Identified object, save to Redis hash based on type
                    redisClient.hset(data['a'], data['@'], JSON.stringify(data));
                    redisClient.hset('ontologies', data['a'], true);
                } else {
                    // Anonymous entity, save to Redis list based on type
                    redisClient.rpush('anon-' + data['a'], JSON.stringify(data));
                    redisClient.hset('anon-ontologies', data['a'], true);
                }
            }
        }

        jQuery.each(socketClients, function(index, socketClient) {
            if (socketClient == client) {
                return true;
            }
            console.log("Forwarding data to " + socketClient.identifier);

            socketClient.send(data);
        });
    });

    client.on('disconnect', function() {
        socketClients = jQuery.grep(socketClients, function(value) {
            return value != client;
        });

        console.log("Client " + client.identifier + " disconnected, client list is now", socketClients.length);
    })
});
<|endoftext|>


<commit_before>import _ from 'lodash';
import React from 'react';
import PropTypes from 'prop-types';
import {
  Callout,
  Pane,
  Paneset,
  ConfirmationModal,
} from '@folio/stripes/components';
import SafeHTMLMessage from '@folio/react-intl-safe-html';
import {
  EditableList,
  UserName
} from '@folio/stripes/smart-components';
import {
  FormattedMessage,
  injectIntl,
  intlShape,
  FormattedDate
} from 'react-intl';
import { Link } from 'react-router-dom';
import { stripesConnect, withStripes } from '@folio/stripes/core';
import { validate } from '../util';
import { Owners } from './FeeFinesTable';

class TransferAccountsSettings extends React.Component {
  static manifest = Object.freeze({
    owners: {
      type: 'okapi',
      records: 'owners',
      path: 'owners?query=cql.allRecords=1 sortby owner&limit=100',
      accumulate: 'true',
      fetch: 'false',
    },
    transferAccounts: {
      type: 'okapi',
      records: 'transfers',
      path: 'transfers',
      PUT: {
        path: 'transfers/%{activeRecord.id}',
      },
      DELETE: {
        path: 'transfers/%{activeRecord.id}',
      },
    },
    activeRecord: {},
  });

  static propTypes = {
    stripes: PropTypes.object,
    mutator: PropTypes.shape({
      owners: PropTypes.shape({
        reset: PropTypes.func,
        GET: PropTypes.func,
      }),
      transferAccounts: PropTypes.shape({
        POST: PropTypes.func,
        PUT: PropTypes.func,
        DELETE: PropTypes.func,
      }),
      activeRecord: PropTypes.shape({
        update: PropTypes.func,
      }),
    }),
    resources: PropTypes.object,
    id: PropTypes.string,
    intl: intlShape.isRequired,
  };

  constructor(props) {
    super(props);

    this.state = {
      showConfirmDialog : false,
      selectedItem : {},
      ownerId:'',
      owners: [],
    };

    this.connectedUserName = props.stripes.connect(UserName);
    this.onChangeOwner = this.onChangeOwner.bind(this);
    this.onCreateItem = this.onCreateItem.bind(this);
    this.onUpdateItem = this.onUpdateItem.bind(this);
    this.metadataCache = {};
  }

  componentDidMount() {
    this.props.mutator.owners.reset();
    this.props.mutator.owners.GET().then(records => {
      const ownerId = records[0].id;
      this.setState({ owners : records, ownerId });
    });
  }

  onCreateItem(item) {
    item.ownerId = this.state.ownerId;
    return this.props.mutator.transferAccounts.POST(item);
  }

  onUpdateItem(item) {
    this.props.mutator.activeRecord.update({ id: item.id });
    return this.props.mutator.transferAccounts.PUT(item);
  }

  onChangeOwner(e) {
    const ownerId = e.target.value;
    this.setState({ ownerId });
  }

  showConfirmDialog = (itemId) => {
    const records = _.get(this.props.resources, ['transferAccounts', 'records'], []);
    const selectedItem = records.find(r => r.id === itemId);
    this.setState({
      showConfirmDialog: true,
      selectedItem,
    });
    return new Promise((resolve, reject) => {
      this.deleteItemResolve = resolve;
      this.deleteItemReject = reject;
    });
  }

  onDeleteItem = () => {
    const { selectedItem } = this.state;
    delete selectedItem.metadata;
    this.props.mutator.activeRecord.update({ id: selectedItem.id });
    return this.props.mutator.transferAccounts.DELETE({ id: selectedItem.id })
      .then(() => {
        this.showDeleteSuccessCallout(selectedItem);
        this.deleteItemResolve();
      }).catch(() => {
        this.deleteItemReject();
      })
      .finally(() => { this.hideConfirmDialog(); });
  }

  showDeleteSuccessCallout = (item) => {
    const { intl: { formatMessage } } = this.props;
    const message = (
      <SafeHTMLMessage
        id="stripes-smart-components.cv.termDeleted"
        values={{
          type: formatMessage({ id: 'ui-users.transfers.singular' }),
          term: item.accountName,
        }}
      />
    );
    this.callout.sendCallout({ message });
  }

  hideConfirmDialog = () => {
    this.setState({ showConfirmDialog: false });
  }

  validate = ({ items }) => {
    const { intl : { formatMessage } } = this.props;
    if (Array.isArray(items)) {
      const errors = [];
      items.forEach((item, index) => {
        const itemError = validate(item, index, items, 'accountName', formatMessage({ id:'ui-users.transfers.singular' }));
        if (!item.accountName) {
          itemError.accountName = formatMessage({ id:'stripes-core.label.missingRequiredField' });
        }
        errors[index] = itemError;
      });
      return { items : errors };
    }
    return {};
  }

  renderLastUpdated = (metadata) => {
    const { stripes } = this.props;

    if (!this.metadataCache[metadata.updatedByUserId]) {
      if (stripes.hasPerm('ui-users.view')) {
        this.metadataCache[metadata.updatedByUserId] = (
          <Link to={`/users/view/${this.props.id}`}>
            <this.connectedUserName stripes={this.props.stripes} id={metadata.updatedByUserId} />
          </Link>
        );
      } else {
        this.metadataCache[metadata.updatedByUserId] = (
          <this.connectedUserName stripes={this.props.stripes} id={metadata.updatedByUserId} />
        );
      }
    }
    return (

        <FormattedMessage
          id="stripes-smart-components.cv.updatedAtAndBy"
          values={{
            date: <FormattedDate value={metadata.updatedDate} />,
            user: this.metadataCache[metadata.updatedByUserId],
          }}
        />

    );
  }

  render() {
    const { resources, intl: { formatMessage } } = this.props;
    const owners = this.state.owners;
    const transfers = _.get(resources, ['transferAccounts', 'records'], []);
    const row = transfers.filter(t => t.ownerId === this.state.ownerId);
    const type = formatMessage({ id: 'ui-users.transfers.singular' });
    const term = this.state.selectedItem.accountName;

    const modalMessage = (
      <SafeHTMLMessage
        id="stripes-smart-components.cv.termWillBeDeleted"
        values={{ type, term }}
      />
    );

    const formatter = {
      'lastUpdated': (item) => {
        if (item.metadata) {
          return this.renderLastUpdated(item.metadata);
        }
        return '-';
      },
    };

    return (
      <Paneset>
        <Pane
          defaultWidth="fill"
          fluidContentWidth
          paneTitle={<FormattedMessage id="ui-users.transfers.label" />}
        >
          <Owners dataOptions={owners} onChange={this.onChangeOwner} />
          <EditableList
            contentData={row}
            formatter={formatter}
            validate={this.validate}
            visibleFields={['accountName', 'desc', 'lastUpdated']}
            columnMapping={{
              accountName: formatMessage({ id: 'ui-users.transfers.columns.name' }) + '*',
              desc: formatMessage({ id: 'ui-users.transfers.columns.desc' }),
              lastUpdated: formatMessage({ id: 'stripes-smart-components.cv.lastUpdated' })
            }}
            readOnlyFields={['lastUpdated']}
            createButtonLabel={formatMessage({ id:'stripes-core.button.new' })}
            onCreate={this.onCreateItem}
            onUpdate={this.onUpdateItem}
            onDelete={this.showConfirmDialog}
            isEmptyMessage={
              formatMessage({ id: 'ui-users.transfers.nodata' },
                { terms: 'label' })
            }
          />
          <ConfirmationModal
            heading={formatMessage({ id: 'ui-users.transfers.modalHeader' })}
            message={modalMessage}
            open={this.state.showConfirmDialog}
            onConfirm={this.onDeleteItem}
            onCancel={this.hideConfirmDialog}
            confirmLabel={formatMessage({ id: 'ui-users.delete' })}
          />
          <Callout
            ref={(ref) => { this.callout = ref; }}
          />
        </Pane>
      </Paneset>
    );
  }
}

export default injectIntl(withStripes(stripesConnect(TransferAccountsSettings)));
<commit_msg>UIU-960: Last Updated column and UIU-1000: Create BigTest Transfer accounts<commit_after>import _ from 'lodash';
import React from 'react';
import PropTypes from 'prop-types';
import {
  Callout,
  Pane,
  Paneset,
  ConfirmationModal,
} from '@folio/stripes/components';
import SafeHTMLMessage from '@folio/react-intl-safe-html';
import {
  EditableList,
  UserName
} from '@folio/stripes/smart-components';
import {
  FormattedMessage,
  injectIntl,
  intlShape,
  FormattedDate
} from 'react-intl';
import { Link } from 'react-router-dom';
import { stripesConnect, withStripes } from '@folio/stripes/core';
import { validate } from '../util';
import { Owners } from './FeeFinesTable';

class TransferAccountsSettings extends React.Component {
  static manifest = Object.freeze({
    owners: {
      type: 'okapi',
      records: 'owners',
      path: 'owners?query=cql.allRecords=1 sortby owner&limit=100',
      accumulate: 'true',
      fetch: 'false',
    },
    transferAccounts: {
      type: 'okapi',
      records: 'transfers',
      path: 'transfers?limit=100',
      PUT: {
        path: 'transfers/%{activeRecord.id}',
      },
      DELETE: {
        path: 'transfers/%{activeRecord.id}',
      },
    },
    activeRecord: {},
  });

  static propTypes = {
    stripes: PropTypes.object,
    mutator: PropTypes.shape({
      owners: PropTypes.shape({
        reset: PropTypes.func,
        GET: PropTypes.func,
      }),
      transferAccounts: PropTypes.shape({
        POST: PropTypes.func,
        PUT: PropTypes.func,
        DELETE: PropTypes.func,
      }),
      activeRecord: PropTypes.shape({
        update: PropTypes.func,
      }),
    }),
    resources: PropTypes.object,
    intl: intlShape.isRequired,
  };

  constructor(props) {
    super(props);

    this.state = {
      showConfirmDialog : false,
      selectedItem : {},
      ownerId:'',
      owners: [],
    };

    this.connectedUserName = props.stripes.connect(UserName);
    this.onChangeOwner = this.onChangeOwner.bind(this);
    this.onCreateItem = this.onCreateItem.bind(this);
    this.onUpdateItem = this.onUpdateItem.bind(this);
    this.metadataCache = {};
  }

  componentDidMount() {
    this.props.mutator.owners.reset();
    this.props.mutator.owners.GET().then(records => {
      const ownerId = records[0].id;
      this.setState({ owners : records, ownerId });
    });
  }

  onCreateItem(item) {
    item.ownerId = this.state.ownerId;
    return this.props.mutator.transferAccounts.POST(item);
  }

  onUpdateItem(item) {
    this.props.mutator.activeRecord.update({ id: item.id });
    return this.props.mutator.transferAccounts.PUT(item);
  }

  onChangeOwner(e) {
    const ownerId = e.target.value;
    this.setState({ ownerId });
  }

  showConfirmDialog = (itemId) => {
    const records = _.get(this.props.resources, ['transferAccounts', 'records'], []);
    const selectedItem = records.find(r => r.id === itemId);
    this.setState({
      showConfirmDialog: true,
      selectedItem,
    });
    return new Promise((resolve, reject) => {
      this.deleteItemResolve = resolve;
      this.deleteItemReject = reject;
    });
  }

  onDeleteItem = () => {
    const { selectedItem } = this.state;
    delete selectedItem.metadata;
    this.props.mutator.activeRecord.update({ id: selectedItem.id });
    return this.props.mutator.transferAccounts.DELETE({ id: selectedItem.id })
      .then(() => {
        this.showDeleteSuccessCallout(selectedItem);
        this.deleteItemResolve();
      })
      .finally(() => { this.hideConfirmDialog(); });
  }

  showDeleteSuccessCallout = (item) => {
    const { intl: { formatMessage } } = this.props;
    const message = (
      <SafeHTMLMessage
        id="stripes-smart-components.cv.termDeleted"
        values={{
          type: formatMessage({ id: 'ui-users.transfers.singular' }),
          term: item.accountName,
        }}
      />
    );
    this.callout.sendCallout({ message });
  }

  hideConfirmDialog = () => {
    this.setState({ showConfirmDialog: false });
  }

  validate = ({ items }) => {
    const { intl : { formatMessage } } = this.props;
    if (Array.isArray(items)) {
      const errors = [];
      items.forEach((item, index) => {
        const itemError = validate(item, index, items, 'accountName', formatMessage({ id:'ui-users.transfers.singular' }));
        if (!item.accountName) {
          itemError.accountName = formatMessage({ id:'stripes-core.label.missingRequiredField' });
        }
        errors[index] = itemError;
      });
      return { items : errors };
    }
    return {};
  }

  renderLastUpdated = (metadata) => {
    if (!this.metadataCache[metadata.updatedByUserId]) {
      this.metadataCache[metadata.updatedByUserId] = (
        <Link to={`/users/view/${metadata.updatedByUserId}`}>
          <this.connectedUserName stripes={this.props.stripes} id={metadata.updatedByUserId} />
        </Link>
      );
    }

    return (

        <FormattedMessage
          id="stripes-smart-components.cv.updatedAtAndBy"
          values={{
            date: <FormattedDate value={metadata.updatedDate} />,
            user: this.metadataCache[metadata.updatedByUserId],
          }}
        />

    );
  }

  render() {
    const { resources, intl: { formatMessage } } = this.props;
    const owners = this.state.owners;
    const transfers = _.get(resources, ['transferAccounts', 'records'], []);
    const row = transfers.filter(t => t.ownerId === this.state.ownerId);
    const type = formatMessage({ id: 'ui-users.transfers.singular' });
    const term = this.state.selectedItem.accountName;

    const modalMessage = (
      <SafeHTMLMessage
        id="stripes-smart-components.cv.termWillBeDeleted"
        values={{ type, term }}
      />
    );

    const formatter = {
      'lastUpdated': (item) => {
        if (item.metadata) {
          return this.renderLastUpdated(item.metadata);
        }
        return '-';
      },
    };

    return (
      <Paneset>
        <Pane
          id="transfers"
          defaultWidth="fill"
          fluidContentWidth
          paneTitle={<FormattedMessage id="ui-users.transfers.label" />}
        >
          <Owners dataOptions={owners} onChange={this.onChangeOwner} />
          <EditableList
            id="settings-transfers"
            contentData={row}
            formatter={formatter}
            validate={this.validate}
            visibleFields={['accountName', 'desc', 'lastUpdated']}
            columnMapping={{
              accountName: formatMessage({ id: 'ui-users.transfers.columns.name' }) + '*',
              desc: formatMessage({ id: 'ui-users.transfers.columns.desc' }),
              lastUpdated: formatMessage({ id: 'stripes-smart-components.cv.lastUpdated' })
            }}
            readOnlyFields={['lastUpdated']}
            createButtonLabel={formatMessage({ id:'stripes-core.button.new' })}
            onCreate={this.onCreateItem}
            onUpdate={this.onUpdateItem}
            onDelete={this.showConfirmDialog}
            isEmptyMessage={
              formatMessage({ id: 'ui-users.transfers.nodata' },
                { terms: 'label' })
            }
          />
          <ConfirmationModal
            id="confirm-delete-transfer"
            heading={formatMessage({ id: 'ui-users.transfers.modalHeader' })}
            message={modalMessage}
            open={this.state.showConfirmDialog}
            onConfirm={this.onDeleteItem}
            onCancel={this.hideConfirmDialog}
            confirmLabel={formatMessage({ id: 'ui-users.delete' })}
          />
          <Callout
            ref={(ref) => { this.callout = ref; }}
          />
        </Pane>
      </Paneset>
    );
  }
}

export default injectIntl(withStripes(stripesConnect(TransferAccountsSettings)));
<|endoftext|>