<commit_before>/**
 * Renders a list of demonstration skills
 */
 Ext.define('SlateAdmin.view.people.details.contacts.List', function() {
    const CLASS_RELATIONSHIP = 'Emergence\\People\\Relationship';
    const CLASS_GUARDIAN = 'Emergence\\People\\GuardianRelationship';

    return {
        extend: 'Ext.view.View',
        xtype: 'people-details-contacts-list',
        requires: [
            'SlateAdmin.view.people.details.contacts.RelationshipEditor',
            'SlateAdmin.widget.field.Person',
            'Slate.store.people.Relationships',
            'Ext.Editor',
        ],

        config: {
            person: null,
            relationshipEditor: true,
            personEditor: true,

            store: {
                xclass: 'Slate.store.people.Relationships',
                remoteSort: true,
                autoSync: false
            },
        },

        // component properties
        componentCls: 'contacts-list',

        // dataview properties
        loadMask: false,
        emptyText: 'No contacts have been linked or created yet.',
        disableSelection: true,
        itemSelector: '.x-dataview-item',
        tpl: [`
            <tpl for=".">
                <div class="x-dataview-item <tpl if="ID < 0">relationship-phantom</tpl>" role="option">

                        <tpl if="ID && !RelatedPerson.phantom">

                        </tpl>
                        <tpl for="RelatedPerson.getData()">{FirstName} {MiddleName} {LastName}</tpl>
                        <tpl if="ID && !RelatedPerson.phantom">

                        </tpl>

                            <tpl if="Class == this.CLASS_GUARDIAN">

                            </tpl>
                            <span class="label-text <tpl if="!Label">label-empty</tpl>">{Label:defaultValue('Click to set')}

                    <tpl for="InverseRelationship">

                            <tpl for="parent.Person.getData()">

                                    {FirstName} {MiddleName} {LastName}

                            </tpl>

                                <tpl if="Class == this.CLASS_GUARDIAN">

                                </tpl>
                                <span class="label-text <tpl if="!Label">label-empty</tpl>">{Label:defaultValue('Click to set')}

                    </tpl>
                    <tpl if="ID > 0">

                            <button type="button" class="relationship-delete-btn" data-action="delete-relationship">

                            </button>

                    </tpl>

            </tpl>

                 Add a related person…

            `,{
                CLASS_RELATIONSHIP,
                CLASS_GUARDIAN,
                getSearchRoute: function (relationship) {
                    var path = ['people', 'search', 'related-to-id:' + relationship.PersonID],
                        relatedPerson = relationship.RelatedPerson,
                        relatedUsername = relatedPerson.get('Username');

                    if (relatedUsername) {
                        path.push(relatedUsername);
                    } else {
                        path.push('?id=' + relatedPerson.getId());
                    }

                    path.push('contacts');

                    return Ext.util.History.encodeRouteArray(path);
                }
            }
        ],

        // config handlers
        updatePerson: function(person) {
            this.getStore().setPerson(person.getId());
        },

        applyRelationshipEditor: function(relationshipEditor, oldRelationshipEditor) {
            if (!relationshipEditor || typeof relationshipEditor == 'boolean') {
                relationshipEditor = {
                    disabled: !relationshipEditor
                };
            }

            return Ext.factory(relationshipEditor, 'SlateAdmin.view.people.details.contacts.RelationshipEditor', oldRelationshipEditor);
        },

        updateRelationshipEditor: function (relationshipEditor) {
            relationshipEditor.on({
                scope: this,
                beforecomplete: 'onBeforeRelationshipEditorComplete',
                complete: 'onRelationshipEditorComplete',
                canceledit: 'onRelationshipEditorCancel'
            });

            relationshipEditor.ownerCmp = this;
        },

        applyPersonEditor: function(personEditor, oldPersonEditor) {
            if (!personEditor || typeof personEditor == 'boolean') {
                personEditor = {
                    disabled: !personEditor
                };
            }

            if (Ext.isSimpleObject(personEditor)) {
                Ext.applyIf(personEditor, {
                    updateEl: false,
                    allowBlur: true,
                    alignment: 'l-l?',
                    autoSize: {
                        width: 'boundEl',
                        height: 'field'
                    },
                    field: {
                        xtype: 'slate-personfield',
                        forceSelection: false,
                        displayField: 'FullName',
                        emptyText: 'Select existing contact, or name a new contact',
                    },
                });
            }

            return Ext.factory(personEditor, 'Ext.Editor', oldPersonEditor);
        },

        updatePersonEditor: function(personEditor) {
            personEditor.field.on('select', 'onPersonSelect', this);

            personEditor.on({
                scope: this,
                beforecomplete: 'onBeforePersonEditorComplete',
                complete: 'onPersonEditorComplete'
            });

            personEditor.ownerCmp = this;
        },

        // dataview lifecycle
        afterRender: function() {
            var me = this;

            me.callParent(arguments);
            me.mon(me.el, 'click', 'onRelationshipCreatorClick', me, { delegate: '.relationship-creator' });
        },

        // disable built-in focus manipulation that can interfere with editing
        onFocusEnter: Ext.emptyFn,
        onFocusLeave: Ext.emptyFn,

        prepareData: function(data) {
            var data = this.callParent(arguments);
            // chain data object before adding properties so we don't modify the actual record
            data = Ext.Object.chain(data);
            data.Person = this.getPerson();
            return data;
        },

        onBeforeItemClick: function(relationship, item, index, ev) {
            var targetEl;

            // clicks on people link should go through
            if (ev.getTarget('a[href^="#people/"')) {
                return false;
            }

            if (ev.getTarget('.relationship-delete')) {
                this.onRelationshipDeleteClick(relationship, ev);
                return false;
            }

            // delegate clicks on editable bits
            var isInverse = Boolean(ev.getTarget('.inverse-relationship'));

            if (targetEl = ev.getTarget('.relationship-label')) {
                this.onRelationshipLabelClick(relationship, isInverse, targetEl, ev);
                return false;
            }

            // default: don't block selection
            return true;
        },

        // contacts-list methods
        onRelationshipDeleteClick: function(relationship, ev) {
            var me = this,
                relatedPerson = relationship.get('RelatedPerson');

            Ext.Msg.confirm('Delete relationship', `Are you sure you want to delete the "${relationship.get('Label')}" relationship with ${relatedPerson.get('FullName')}?`, function (btn) {
                if (btn != 'yes') {
                    return;
                }

                me.setLoading('Deleting relationship…');

                var deleteSession = new Ext.data.Session(),
                    inverseData = relationship.get('InverseRelationship'),
                    inverseRelationship,
                    deleteBatch;

                deleteSession.adopt(relationship);
                relationship.drop();

                if (inverseData) {
                    inverseRelationship = deleteSession.createRecord(relationship.self, inverseData);
                    inverseRelationship.drop();
                }

                deleteBatch = deleteSession.getSaveBatch();
                deleteBatch.on('complete', function() {
                    me.setLoading(false);
                });
                deleteBatch.start();
            });
        },

        onRelationshipLabelClick: function(relationship, isInverse, targetEl, ev) {
            this.startRelationshipLabelEdit(relationship, isInverse, targetEl);
        },

        startRelationshipLabelEdit: function(relationship, isInverse, targetEl, values) {
            var editor = this.getRelationshipEditor();

            if (!targetEl) {
                targetEl = this.getNodeByRecord(relationship);
                targetEl = Ext.fly(targetEl).down(`.${isInverse ? 'inverse' : 'forward'}-relationship .relationship-label`);
            }

            editor.activeRelationship = relationship;
            editor.setIsInverse(isInverse);
            editor.alignment = isInverse ? 'tl-tl?' : 'tr-tr?';
            editor.offsets = isInverse ? [-6, -6] : [5, -6];
            editor.startEdit(targetEl, Ext.apply({
                Class: isInverse
                    ? relationship.get('InverseRelationship').Class
                    : relationship.get('Class'),
                Label: isInverse
                    ? relationship.get('InverseRelationship').Label
                    : relationship.get('Label')
            }, values || {}));
        },

        onRelationshipCreatorClick: function(ev, target) {
            var editor = this.getPersonEditor(),
                editorStore = editor.field.getStore(),
                excludedPeopleIds = this.getStore().collect('RelatedPersonID');

            // add currently loaded person to excluded IDs
            excludedPeopleIds.push(this.getPerson().getId());

            // filter existing related people and currently loaded person from list
            editorStore.clearFilter(true);
            editorStore.addFilter(function (comboRecord) {
                return excludedPeopleIds.indexOf(comboRecord.getId()) == -1;
            });

            // open and position editor
            editor.startEdit(target, editor.field.getSelection());
        },

        onBeforeRelationshipEditorComplete: function(editor, value, startValue) {
            console.info('onBeforeRelationshipEditorComplete(%o, %o, %o)', editor, value, startValue);
        },

        onRelationshipEditorComplete: function(editor, value, startValue) {
            var me = this,
                relationship = editor.activeRelationship,
                isInverse = editor.getIsInverse(),
                currentPersonGender = me.getPerson().get('Gender'),
                templatesStore = editor.field.getLabelField().getStore();

            if (isInverse) {
                const inverseRelationship = relationship.get('InverseRelationship');

                relationship.set('InverseRelationship', Ext.applyIf(value, inverseRelationship));
            } else {
                relationship.set(value);
            }

            if (relationship.dirty && relationship.isValid()) {
                // when switching between two stock values for forward+inverse, suggest matching change to inverse
                if (!editor.getIsInverse()) {
                    const originalLabel = relationship.getModified('Label');
                    const originalTemplate = templatesStore.findRecord('label', originalLabel, 0, false, false, true);

                    if (originalTemplate) {
                        const originInverseLabel = originalTemplate.getInverseLabel(currentPersonGender);
                        const matchedTemplate = templatesStore.findRecord('label', value.Label, 0, false, false, true);

                        // if inverse matches original
                        if (relationship.get('InverseRelationship').Label == originInverseLabel
                            && matchedTemplate
                        ) {
                            const patch = {
                                Label: matchedTemplate.getInverseLabel(currentPersonGender)
                            };

                            const templateClass = matchedTemplate.get('InverseRelationship').Class;
                            if (templateClass) {
                                patch.Class = templateClass;
                            }

                            // skip save for now and continue editor to inverse field with inferred changes
                            Ext.defer(() => me.startRelationshipLabelEdit(relationship, true, null, patch), 10);
                            return;
                        }
                    }
                }

                me.setLoading('Updating relationship…');
                relationship.save({
                    callback: function (savedRecord, operation, success) {
                        if (success) {
                            me.setLoading(false);
                        }
                    }
                });
            } else if (!editor.getIsInverse()) {
                // auto-populate inverse if template selected
                if (relationship.phantom) {
                    const matchedTemplate = templatesStore.findRecord('label', value.Label, 0, false, false, true);

                    if (matchedTemplate) {
                        relationship.set(matchedTemplate.get('Relationship'));

                        const inverseRelationship = relationship.get('InverseRelationship');
                        if (!inverseRelationship.Label) {
                            const inverseRelationshipTemplate = matchedTemplate.get('InverseRelationship');

                            inverseRelationship.Label = matchedTemplate.getInverseLabel(currentPersonGender);

                            if (inverseRelationshipTemplate.Class) {
                                inverseRelationship.Class = inverseRelationshipTemplate.Class;
                            }
                        }

                        const personTemplate = matchedTemplate.get('Person');
                        const relatedPerson = relationship.get('RelatedPerson');
                        if (personTemplate && relatedPerson.phantom) {
                            relatedPerson.set(personTemplate);
                        }
                    }
                }

                // automatically activate editor on inverse field if a value has been input
                // defer momentarily to allow current event sequence to finish
                if (value.Label) {
                    Ext.defer(() => me.startRelationshipLabelEdit(relationship, true), 10);
                }
            }
        },

        onRelationshipEditorCancel: function(editor, value, startValue) {
            var relationship = editor.activeRelationship;

            // if any unsaved edits are cancelled on a non-phantom record, reject all unsaved edits
            if (!relationship.phantom) {
                editor.activeRelationship.reject();
            }
        },

        onPersonSelect: function(personField) {
            personField.up('editor').completeEdit();
        },

        onBeforePersonEditorComplete: function(editor, value) {
            // test creating a model from provided string
            if (
                Ext.isString(value)
                && !editor.field.getStore().getModel().createFromName(value).isValid()
            ) {
                editor.field.setActiveError('A complete first and last name must be provided');
                return false;
            }
        },

        onPersonEditorComplete: function(editor, value, startValue) {
            var me = this,
                person = me.getPerson(),
                editorField = editor.field,
                editorStore = editorField.getStore(),
                editorModel = editorStore.getModel(),
                selectedRecord = editorField.getSelection(),
                relationship;

            if (selectedRecord) {
                value = selectedRecord;
            } else if (Ext.isString(value)) {
                value = editorModel.createFromName(value);

                if (!value.isValid()) {
                    return false;
                }

                editorStore.add(value);
            }

            // create new relationship record
            relationship = me.getStore().add({
                Class: CLASS_RELATIONSHIP,
                Label: '',
                Person: person,
                PersonID: person.getId(),
                RelatedPerson: value,
                RelatedPersonID: value.phantom ? null : value.getId(),
                InverseRelationship: {
                    Class: CLASS_RELATIONSHIP,
                    Label: ''
                }
            })[0];

            // reset editor for next use
            editorField.clearValue();

            // activate relationship editor
            me.startRelationshipLabelEdit(relationship, false);
        },
    };
});
<commit_msg>fix(SlateAdmin): restore delete button for phantom relationships<commit_after>/**
 * Renders a list of demonstration skills
 */
 Ext.define('SlateAdmin.view.people.details.contacts.List', function() {
    const CLASS_RELATIONSHIP = 'Emergence\\People\\Relationship';
    const CLASS_GUARDIAN = 'Emergence\\People\\GuardianRelationship';

    return {
        extend: 'Ext.view.View',
        xtype: 'people-details-contacts-list',
        requires: [
            'SlateAdmin.view.people.details.contacts.RelationshipEditor',
            'SlateAdmin.widget.field.Person',
            'Slate.store.people.Relationships',
            'Ext.Editor',
        ],

        config: {
            person: null,
            relationshipEditor: true,
            personEditor: true,

            store: {
                xclass: 'Slate.store.people.Relationships',
                remoteSort: true,
                autoSync: false
            },
        },

        // component properties
        componentCls: 'contacts-list',

        // dataview properties
        loadMask: false,
        emptyText: 'No contacts have been linked or created yet.',
        disableSelection: true,
        itemSelector: '.x-dataview-item',
        tpl: [`
            <tpl for=".">
                <div class="x-dataview-item <tpl if="ID < 0">relationship-phantom</tpl>" role="option">

                        <tpl if="ID && !RelatedPerson.phantom">

                        </tpl>
                        <tpl for="RelatedPerson.getData()">{FirstName} {MiddleName} {LastName}</tpl>
                        <tpl if="ID && !RelatedPerson.phantom">

                        </tpl>

                            <tpl if="Class == this.CLASS_GUARDIAN">

                            </tpl>
                            <span class="label-text <tpl if="!Label">label-empty</tpl>">{Label:defaultValue('Click to set')}

                    <tpl for="InverseRelationship">

                            <tpl for="parent.Person.getData()">

                                    {FirstName} {MiddleName} {LastName}

                            </tpl>

                                <tpl if="Class == this.CLASS_GUARDIAN">

                                </tpl>
                                <span class="label-text <tpl if="!Label">label-empty</tpl>">{Label:defaultValue('Click to set')}

                    </tpl>

                        <button type="button" class="relationship-delete-btn" data-action="delete-relationship">

                        </button>

            </tpl>

                 Add a related person…

            `,{
                CLASS_RELATIONSHIP,
                CLASS_GUARDIAN,
                getSearchRoute: function (relationship) {
                    var path = ['people', 'search', 'related-to-id:' + relationship.PersonID],
                        relatedPerson = relationship.RelatedPerson,
                        relatedUsername = relatedPerson.get('Username');

                    if (relatedUsername) {
                        path.push(relatedUsername);
                    } else {
                        path.push('?id=' + relatedPerson.getId());
                    }

                    path.push('contacts');

                    return Ext.util.History.encodeRouteArray(path);
                }
            }
        ],

        // config handlers
        updatePerson: function(person) {
            this.getStore().setPerson(person.getId());
        },

        applyRelationshipEditor: function(relationshipEditor, oldRelationshipEditor) {
            if (!relationshipEditor || typeof relationshipEditor == 'boolean') {
                relationshipEditor = {
                    disabled: !relationshipEditor
                };
            }

            return Ext.factory(relationshipEditor, 'SlateAdmin.view.people.details.contacts.RelationshipEditor', oldRelationshipEditor);
        },

        updateRelationshipEditor: function (relationshipEditor) {
            relationshipEditor.on({
                scope: this,
                beforecomplete: 'onBeforeRelationshipEditorComplete',
                complete: 'onRelationshipEditorComplete',
                canceledit: 'onRelationshipEditorCancel'
            });

            relationshipEditor.ownerCmp = this;
        },

        applyPersonEditor: function(personEditor, oldPersonEditor) {
            if (!personEditor || typeof personEditor == 'boolean') {
                personEditor = {
                    disabled: !personEditor
                };
            }

            if (Ext.isSimpleObject(personEditor)) {
                Ext.applyIf(personEditor, {
                    updateEl: false,
                    allowBlur: true,
                    alignment: 'l-l?',
                    autoSize: {
                        width: 'boundEl',
                        height: 'field'
                    },
                    field: {
                        xtype: 'slate-personfield',
                        forceSelection: false,
                        displayField: 'FullName',
                        emptyText: 'Select existing contact, or name a new contact',
                    },
                });
            }

            return Ext.factory(personEditor, 'Ext.Editor', oldPersonEditor);
        },

        updatePersonEditor: function(personEditor) {
            personEditor.field.on('select', 'onPersonSelect', this);

            personEditor.on({
                scope: this,
                beforecomplete: 'onBeforePersonEditorComplete',
                complete: 'onPersonEditorComplete'
            });

            personEditor.ownerCmp = this;
        },

        // dataview lifecycle
        afterRender: function() {
            var me = this;

            me.callParent(arguments);
            me.mon(me.el, 'click', 'onRelationshipCreatorClick', me, { delegate: '.relationship-creator' });
        },

        // disable built-in focus manipulation that can interfere with editing
        onFocusEnter: Ext.emptyFn,
        onFocusLeave: Ext.emptyFn,

        prepareData: function(data) {
            var data = this.callParent(arguments);
            // chain data object before adding properties so we don't modify the actual record
            data = Ext.Object.chain(data);
            data.Person = this.getPerson();
            return data;
        },

        onBeforeItemClick: function(relationship, item, index, ev) {
            var targetEl;

            // clicks on people link should go through
            if (ev.getTarget('a[href^="#people/"')) {
                return false;
            }

            if (ev.getTarget('.relationship-delete')) {
                this.onRelationshipDeleteClick(relationship, ev);
                return false;
            }

            // delegate clicks on editable bits
            var isInverse = Boolean(ev.getTarget('.inverse-relationship'));

            if (targetEl = ev.getTarget('.relationship-label')) {
                this.onRelationshipLabelClick(relationship, isInverse, targetEl, ev);
                return false;
            }

            // default: don't block selection
            return true;
        },

        // contacts-list methods
        onRelationshipDeleteClick: function(relationship, ev) {
            var me = this,
                relatedPerson = relationship.get('RelatedPerson');

            // drop phantom with no confirmation
            if (relationship.phantom) {
                relationship.drop();
                return;
            }

            Ext.Msg.confirm('Delete relationship', `Are you sure you want to delete the "${relationship.get('Label')}" relationship with ${relatedPerson.get('FullName')}?`, function (btn) {
                if (btn != 'yes') {
                    return;
                }

                me.setLoading('Deleting relationship…');

                var deleteSession = new Ext.data.Session(),
                    inverseData = relationship.get('InverseRelationship'),
                    inverseRelationship,
                    deleteBatch;

                deleteSession.adopt(relationship);
                relationship.drop();

                if (inverseData) {
                    inverseRelationship = deleteSession.createRecord(relationship.self, inverseData);
                    inverseRelationship.drop();
                }

                deleteBatch = deleteSession.getSaveBatch();
                deleteBatch.on('complete', function() {
                    me.setLoading(false);
                });
                deleteBatch.start();
            });
        },

        onRelationshipLabelClick: function(relationship, isInverse, targetEl, ev) {
            this.startRelationshipLabelEdit(relationship, isInverse, targetEl);
        },

        startRelationshipLabelEdit: function(relationship, isInverse, targetEl, values) {
            var editor = this.getRelationshipEditor();

            if (!targetEl) {
                targetEl = this.getNodeByRecord(relationship);
                targetEl = Ext.fly(targetEl).down(`.${isInverse ? 'inverse' : 'forward'}-relationship .relationship-label`);
            }

            editor.activeRelationship = relationship;
            editor.setIsInverse(isInverse);
            editor.alignment = isInverse ? 'tl-tl?' : 'tr-tr?';
            editor.offsets = isInverse ? [-6, -6] : [5, -6];
            editor.startEdit(targetEl, Ext.apply({
                Class: isInverse
                    ? relationship.get('InverseRelationship').Class
                    : relationship.get('Class'),
                Label: isInverse
                    ? relationship.get('InverseRelationship').Label
                    : relationship.get('Label')
            }, values || {}));
        },

        onRelationshipCreatorClick: function(ev, target) {
            var editor = this.getPersonEditor(),
                editorStore = editor.field.getStore(),
                excludedPeopleIds = this.getStore().collect('RelatedPersonID');

            // add currently loaded person to excluded IDs
            excludedPeopleIds.push(this.getPerson().getId());

            // filter existing related people and currently loaded person from list
            editorStore.clearFilter(true);
            editorStore.addFilter(function (comboRecord) {
                return excludedPeopleIds.indexOf(comboRecord.getId()) == -1;
            });

            // open and position editor
            editor.startEdit(target, editor.field.getSelection());
        },

        onBeforeRelationshipEditorComplete: function(editor, value, startValue) {
            console.info('onBeforeRelationshipEditorComplete(%o, %o, %o)', editor, value, startValue);
        },

        onRelationshipEditorComplete: function(editor, value, startValue) {
            var me = this,
                relationship = editor.activeRelationship,
                isInverse = editor.getIsInverse(),
                currentPersonGender = me.getPerson().get('Gender'),
                templatesStore = editor.field.getLabelField().getStore();

            if (isInverse) {
                const inverseRelationship = relationship.get('InverseRelationship');

                relationship.set('InverseRelationship', Ext.applyIf(value, inverseRelationship));
            } else {
                relationship.set(value);
            }

            if (relationship.dirty && relationship.isValid()) {
                // when switching between two stock values for forward+inverse, suggest matching change to inverse
                if (!editor.getIsInverse()) {
                    const originalLabel = relationship.getModified('Label');
                    const originalTemplate = templatesStore.findRecord('label', originalLabel, 0, false, false, true);

                    if (originalTemplate) {
                        const originInverseLabel = originalTemplate.getInverseLabel(currentPersonGender);
                        const matchedTemplate = templatesStore.findRecord('label', value.Label, 0, false, false, true);

                        // if inverse matches original
                        if (relationship.get('InverseRelationship').Label == originInverseLabel
                            && matchedTemplate
                        ) {
                            const patch = {
                                Label: matchedTemplate.getInverseLabel(currentPersonGender)
                            };

                            const templateClass = matchedTemplate.get('InverseRelationship').Class;
                            if (templateClass) {
                                patch.Class = templateClass;
                            }

                            // skip save for now and continue editor to inverse field with inferred changes
                            Ext.defer(() => me.startRelationshipLabelEdit(relationship, true, null, patch), 10);
                            return;
                        }
                    }
                }

                me.setLoading('Updating relationship…');
                relationship.save({
                    callback: function (savedRecord, operation, success) {
                        if (success) {
                            me.setLoading(false);
                        }
                    }
                });
            } else if (!editor.getIsInverse()) {
                // auto-populate inverse if template selected
                if (relationship.phantom) {
                    const matchedTemplate = templatesStore.findRecord('label', value.Label, 0, false, false, true);

                    if (matchedTemplate) {
                        relationship.set(matchedTemplate.get('Relationship'));

                        const inverseRelationship = relationship.get('InverseRelationship');
                        if (!inverseRelationship.Label) {
                            const inverseRelationshipTemplate = matchedTemplate.get('InverseRelationship');

                            inverseRelationship.Label = matchedTemplate.getInverseLabel(currentPersonGender);

                            if (inverseRelationshipTemplate.Class) {
                                inverseRelationship.Class = inverseRelationshipTemplate.Class;
                            }
                        }

                        const personTemplate = matchedTemplate.get('Person');
                        const relatedPerson = relationship.get('RelatedPerson');
                        if (personTemplate && relatedPerson.phantom) {
                            relatedPerson.set(personTemplate);
                        }
                    }
                }

                // automatically activate editor on inverse field if a value has been input
                // defer momentarily to allow current event sequence to finish
                if (value.Label) {
                    Ext.defer(() => me.startRelationshipLabelEdit(relationship, true), 10);
                }
            }
        },

        onRelationshipEditorCancel: function(editor, value, startValue) {
            var relationship = editor.activeRelationship;

            // if any unsaved edits are cancelled on a non-phantom record, reject all unsaved edits
            if (!relationship.phantom) {
                editor.activeRelationship.reject();
            }
        },

        onPersonSelect: function(personField) {
            personField.up('editor').completeEdit();
        },

        onBeforePersonEditorComplete: function(editor, value) {
            // test creating a model from provided string
            if (
                Ext.isString(value)
                && !editor.field.getStore().getModel().createFromName(value).isValid()
            ) {
                editor.field.setActiveError('A complete first and last name must be provided');
                return false;
            }
        },

        onPersonEditorComplete: function(editor, value, startValue) {
            var me = this,
                person = me.getPerson(),
                editorField = editor.field,
                editorStore = editorField.getStore(),
                editorModel = editorStore.getModel(),
                selectedRecord = editorField.getSelection(),
                relationship;

            if (selectedRecord) {
                value = selectedRecord;
            } else if (Ext.isString(value)) {
                value = editorModel.createFromName(value);

                if (!value.isValid()) {
                    return false;
                }

                editorStore.add(value);
            }

            // create new relationship record
            relationship = me.getStore().add({
                Class: CLASS_RELATIONSHIP,
                Label: '',
                Person: person,
                PersonID: person.getId(),
                RelatedPerson: value,
                RelatedPersonID: value.phantom ? null : value.getId(),
                InverseRelationship: {
                    Class: CLASS_RELATIONSHIP,
                    Label: ''
                }
            })[0];

            // reset editor for next use
            editorField.clearValue();

            // activate relationship editor
            me.startRelationshipLabelEdit(relationship, false);
        },
    };
});
<|endoftext|>


<commit_before>'use strict';

module.exports = {
    parseElement: function (element) {
        var expr = element.match(/([а-я]*[\s\S]*)\[(\d+)\]/i);

        if (!expr) {
            return {
                name: element,
                index: 0
            };
        }

        return {
            name: expr[1].trim(),
            index: parseInt(expr[2])
        }
    },

    parseDate: function (date) {
        var expr = date.match(/^\d{2}.\d{2}.\d{4}$/);

        if (expr) {
            return date;
        }

        expr = date.match(/Сегодня/);

        if (expr) {
            var currentDate = Date.now();
            expr = date.match(/([\+,\-])(\d+)/);

            if (expr) {
                var sign = expr[1];
                var iterator = expr[2];
                iterator = parseInt(iterator) * 24 * 60 * 60 * 1000;
                currentDate = currentDate + (sign == '+' ? iterator : iterator * -1);
            }

            date = new Date(currentDate)
                .toLocaleDateString()
                .split('-');

            if(date.length == 3) {
                if(date[0].length == 4) {
                    date.reverse();
                }
                date = date.join('.');
            } else {
                date = date[0];
            }

            return date;
        }

        throw new Error('Некорректная дата');
    },

    fixQuotes: function (text) {
        text = text.replace(/''/g, '"');

        if(text.indexOf('"') != -1){
            return 'concat("' + text.replace(/"/g, '",\'"\',"') + '")';
        } else {
            return '"' + text + '"';
        }
    },

    ignoreNumbers: function (text, textWithIgnore) {
        var ignore = textWithIgnore.match(/{\d+}/g);
        var numbers = text.match(/\d+/g);

        if (!ignore) {
            return text;
        }

        ignore.forEach(function (item) {
            var i = parseInt(item.replace(/[{,}]/g, ''));
            text = text.replace(numbers[i], item);
        });

        return text;
    }
};<commit_msg>Release 1.0.20<commit_after>'use strict';

module.exports = {
    parseElement: function (element) {
        var expr = element.match(/([а-я]*[\s\S]*)\[(\d+)\]/i);

        if (!expr) {
            return {
                name: element,
                index: 0
            };
        }

        return {
            name: expr[1].trim(),
            index: parseInt(expr[2])
        }
    },

    parseDate: function (date) {
        var expr = date.match(/^\d{2}.\d{2}.\d{4}$/);

        if (expr) {
            return date;
        }

        expr = date.match(/Сегодня/);

        if (expr) {
            var currentDate = Date.now();
            expr = date.match(/([\+,\-])(\d+)/);

            if (expr) {
                var sign = expr[1];
                var iterator = expr[2];
                iterator = parseInt(iterator) * 24 * 60 * 60 * 1000;
                currentDate = currentDate + (sign == '+' ? iterator : iterator * -1);
            }

            date = new Date(currentDate)
                .toLocaleDateString()
                .split('-');

            if(date.length == 3) {
                if(date[0].length == 4) {
                    date.reverse();
                }
                date = date.join('.');
            } else {
                date = date[0];
            }

            return date;
        }

        throw new Error('Некорректная дата');
    },

    fixQuotes: function (text) {
        text = text.replace(/''/g, '"');

        if(text.indexOf('"') != -1){
            return 'concat("' + text.replace(/"/g, '",\'"\',"') + '")';
        } else {
            return '"' + text + '"';
        }
    },

    ignoreNumbers: function (text, textWithIgnore) {
        var numbers = text.match(/\d+/g);

        return this.format(textWithIgnore, numbers);
    },

    format: function (text, args) {
        return text.replace(/\{\{|\}\}|\{(\d+)\}/g, function (m, n) {
            if (m == "{{") {
                return "{";
            }

            if (m == "}}") {
                return "}";
            }

            return args[n];
        });
    }
};<|endoftext|>


<commit_before>// https://github.com/gruntjs/grunt-contrib-imagemin
module.exports = {
	images: {
		files: [
			{
				expand: true,
				cwd: '<%= paths.imagesSrc %>',
				src: [ '**/*.{png,jpg,jpeg,gif,svg}' ],
				dest: '<%= paths.images %>'
			}
		]
	}
};
<commit_msg>Adjusted imagemin config<commit_after>// https://github.com/gruntjs/grunt-contrib-imagemin
module.exports = {
	images: {
		files: [
			{
				expand: true,
				cwd: '<%= paths.imagesSrc %>',
				src: [
					'**/*.{png,jpg,jpeg,gif,svg}',
					'!screenshot.png'
				],
				dest: '<%= paths.images %>'
			},
			{
				expand: true,
				cwd: '<%= paths.imagesSrc %>',
				src: [
					'screenshot.png'
				],
				dest: ''
			}
		]
	}
};
<|endoftext|>


<commit_before>'use babel'
/* global process, emit */

import Bacon from 'baconjs'
import chokidar from 'chokidar'
import fs from 'fs'
import R from 'ramda'
import PathFilter from 'scandal/lib/path-filter'
import Sifter from 'sifter'
import DisposableValues from './disposable-values'
import receiveMessagesFrom from './task/receive-messages-from'

export default function (projectPaths, ignoredNames, excludeVcsIgnores) {
  const terminate = this.async()
  const messageReceiver = receiveMessagesFrom(process)
  const closeProjectPathStream = messageReceiver.createStream('closeProjectPath')

  const filterOpts = {
    excludeVcsIgnores: true,
    exclusions: ignoredNames,
    inclusions: ['*.md', '*.txt']
  }
  const pathFiltersProp = Bacon.update(
    projectPaths.map(path => new PathFilter(path, filterOpts)),
    [closeProjectPathStream], (pathFilters, path) => pathFilters.filter(pf => pf.rootPath !== path)
  )

  const watcher = chokidar.watch(projectPaths, {
    persistent: true
  })
  const readyStream = Bacon.fromEvent(watcher, 'ready')
  const addItemsStream = Bacon.fromEvent(watcher, 'add', (path, stat) => {
    return {
      path: path,
      stat: stat
    }
  })
  const removedItemsStream = Bacon.fromEvent(watcher, 'unlink')
  const itemsProp = Bacon.update(
      [],
      [addItemsStream, pathFiltersProp], (items, item, pathFilters) => {
        const path = item.path
        const pf = pathFilters.find(pf => path.startsWith(pf.rootPath))
        if (pf && pf.isFileAccepted(path)) {
          return items.concat({
            path: path,
            stat: item.stat || fs.statSync(path),
            content: fs.readFileSync(path, 'utf8')
          })
        } else {
          return items
        }
      },
      [removedItemsStream], (items, path) => items.filter(item => item.path !== path),
      [closeProjectPathStream], (items, path) => items.filter(item => !item.path.startsWith(path)),
      [readyStream], (items) => items
    )
    .debounce(100) // make sure to not spam prop changes until changes calms down (e.g. initial scan)

  const sifterProp = itemsProp.map(items => new Sifter(items)).startWith(new Sifter([]))
  const queryProp = messageReceiver.createStream('query').toProperty({ searchStr: '' })
  const queryResultsProp = Bacon.combineWith(sifterProp, queryProp, (sifter, {searchStr}) => {
    // see https://github.com/brianreavis/sifter.js/#searchquery-options
    return sifter.search(searchStr, {
      fields: ['path', 'content'],
      sort: [{field: 'path', direction: 'asc'}],
      conjunction: 'and'
    })
  })

  const resultsStream = Bacon.when(
    [queryResultsProp.changes(), queryProp, itemsProp], (r, q, items) => {
      let regex = R.path(['regex'], r.tokens[0])
      return {
        offset: 0,
        total: r.total,
        regexpStr: regex && regex.toString(),
        items: r.items
          .slice(0, q.paginationSize)
          .map(({id}) => {
            const item = items[id]
            item.hrtime = process.hrtime().toString()
            return item
          })
      }
    },
    [messageReceiver.createStream('paginateLastQuery'), queryResultsProp, itemsProp], (q, r, items) => {
      return {
        offset: q.paginationOffset,
        total: r.total,
        items: r.items
          .slice(q.paginationOffset, q.paginationOffset + q.paginationSize)
          .map(({id}) => {
            const item = items[id]
            item.hrtime = process.hrtime().toString()
            return item
          })
      }
    }
  )

  const disposableValues = new DisposableValues(
    resultsStream.onValue(r => emit('results', r)),
    closeProjectPathStream.onValue(path => watcher.unwatch(path))
  )

  messageReceiver.createStream('dispose').onValue(() => {
    watcher.close()
    disposableValues.dispose()
    terminate()
  })
}
<commit_msg>:art: Create results separately, DRY mapping code<commit_after>'use babel'
/* global process, emit */

import Bacon from 'baconjs'
import chokidar from 'chokidar'
import fs from 'fs'
import R from 'ramda'
import PathFilter from 'scandal/lib/path-filter'
import Sifter from 'sifter'
import DisposableValues from './disposable-values'
import receiveMessagesFrom from './task/receive-messages-from'

export default function (projectPaths, ignoredNames, excludeVcsIgnores) {
  const terminate = this.async()
  const messageReceiver = receiveMessagesFrom(process)
  const closeProjectPathStream = messageReceiver.createStream('closeProjectPath')

  const filterOpts = {
    excludeVcsIgnores: true,
    exclusions: ignoredNames,
    inclusions: ['*.md', '*.txt']
  }
  const pathFiltersProp = Bacon.update(
    projectPaths.map(path => new PathFilter(path, filterOpts)),
    [closeProjectPathStream], (pathFilters, path) => pathFilters.filter(pf => pf.rootPath !== path)
  )

  const watcher = chokidar.watch(projectPaths, {
    persistent: true
  })
  const readyStream = Bacon.fromEvent(watcher, 'ready')
  const addItemsStream = Bacon.fromEvent(watcher, 'add', (path, stat) => {
    return {
      path: path,
      stat: stat
    }
  })
  const removedItemsStream = Bacon.fromEvent(watcher, 'unlink')
  const itemsProp = Bacon.update(
      [],
      [addItemsStream, pathFiltersProp], (items, item, pathFilters) => {
        const path = item.path
        const pf = pathFilters.find(pf => path.startsWith(pf.rootPath))
        if (pf && pf.isFileAccepted(path)) {
          return items.concat({
            path: path,
            stat: item.stat || fs.statSync(path),
            content: fs.readFileSync(path, 'utf8')
          })
        } else {
          return items
        }
      },
      [removedItemsStream], (items, path) => items.filter(item => item.path !== path),
      [closeProjectPathStream], (items, path) => items.filter(item => !item.path.startsWith(path)),
      [readyStream], (items) => items
    )
    .debounce(100) // make sure to not spam prop changes until changes calms down (e.g. initial scan)

  const sifterProp = itemsProp.map(items => new Sifter(items)).startWith(new Sifter([]))
  const queryProp = messageReceiver.createStream('query').toProperty({ searchStr: '' })
  const queryResultsProp = Bacon.combineWith(sifterProp, queryProp, (sifter, {searchStr}) => {
    // see https://github.com/brianreavis/sifter.js/#searchquery-options
    return sifter.search(searchStr, {
      fields: ['path', 'content'],
      sort: [{field: 'path', direction: 'asc'}],
      conjunction: 'and'
    })
  })
  const queryResultsStream = Bacon.when(
      [queryResultsProp.changes(), queryProp, itemsProp], (r, q) => {
        let regex = R.path(['regex'], r.tokens[0])
        return {
          offset: 0,
          total: r.total,
          regexpStr: regex && regex.toString(),
          items: r.items.slice(0, q.paginationSize)
        }
      },
      [messageReceiver.createStream('paginateLastQuery'), queryResultsProp], (q, r) => {
        return {
          offset: q.paginationOffset,
          total: r.total,
          items: r.items.slice(q.paginationOffset, q.paginationOffset + q.paginationSize)
        }
      }
    )
  const resultsStream = Bacon.combineWith(queryResultsStream, itemsProp, (r, items) => {
    r.items = r.items.map(({id}) => {
      const item = items[id]
      item.hrtime = process.hrtime().toString()
      return item
    })
    return r
  })

  const disposableValues = new DisposableValues(
    resultsStream.onValue(r => emit('results', r)),
    closeProjectPathStream.onValue(path => watcher.unwatch(path))
  )

  messageReceiver.createStream('dispose').onValue(() => {
    watcher.close()
    disposableValues.dispose()
    terminate()
  })
}
<|endoftext|>


<commit_before>var os = require('os-utils');
var RounderDB = require('rounderdb');
var http = require('http');
var util = require('util');
var fs = require('fs');

var selfMon = require('./lib/monitorSelf.js');
var router = require('./lib/router.js');

var config = require('./conf/default.conf');
var DBconf = {
    "persistenceConf": {
        "dbFile": "./db/rounderDB.json",
        "syncInterval": 5
    },
    "cpuUsage": [
        {
            "capacity": 60,
            "aggregationStrategy": "average"
        },
        {
            "capacity": 600,
            "aggregationStrategy": "average"
        }
    ],
    "loadAvg": [
        {
            "capacity": 60,
            "aggregationStrategy": "average"
        },
        {
            "capacity": 600,
            "aggregationStrategy": "average"
        }
    ],
    "selfMem": [
        {
            "capacity": 60,
            "aggregationStrategy": "average"
        },
        {
            "capacity": 600,
            "aggregationStrategy": "average"
        }
    ],
    "freemem": [
        {
            "capacity": 60,
            "aggregationStrategy": "average"
        },
        {
            "capacity": 600
        }
    ]
};

var port = 8000;
var db = null;

if (fs.existsSync(DBconf.persistenceConf.dbFile)) {
    console.log("CREATING FROM FILE: " + DBconf.persistenceConf.dbFile);
    var data = fs.readFileSync(DBconf.persistenceConf.dbFile);
    var obj = JSON.parse(data);
    db = RounderDB.loadInstance(obj);
} else
    db = RounderDB.createInstance(DBconf);

// Enable logging
var doLogging = false;
var dryrun = false; // if true, don't add anything to db (just print, if doLogging is enabled)

var intervalCPU = 1000;
var intervalLoadAvg = 1 * 60 * 1000;
var intervalMem = 1500;

var addToDB = function (key, value) {
    if (!dryrun)
        db.add(key, value);
}

var cpuMon = function () {
    os.cpuUsage(function (v) {
        log('CPU Usage (%): ' + v);
        addToDB('cpuUsage', v);
    });
};

var memMon = function () {
    var freeMem = os.freememPercentage();
    log('Free mem (%): ' + freeMem);
    addToDB('freemem', freeMem);
};

var selfMem = function () {
    var freeMem = selfMon.RAMUsed();
    log('Process mem (MB): ' + freeMem);
    addToDB('selfMem', freeMem);
};

var loadAvg = function () {
    var load = os.loadavg(1);
    log('Load Avg. : ' + load);
    addToDB('loadAvg', load);
};

// Register data collectors
//
var cpuHandle = setInterval(cpuMon, intervalCPU);
var cpuLoad = setInterval(loadAvg, intervalLoadAvg);
var memHandle = setInterval(memMon, intervalMem);
var selfMemHandle = setInterval(selfMem, intervalMem);

// Configure our HTTP server to respond with Hello World to all requests.
var server = http.createServer(function (request, response) {
//    console.log("----- NEW REQEST!\n"+util.inspect(request));
    console.log("----- NEW REQEST! method: " + request.method + " URL: " + request.url);
    var result = router.dispatch(db, request.method, request.url);
    response.writeHead(result.status, {"Content-Type": "text/plain"});
    response.end(result.data);
});

// Listen on port 8000, IP defaults to 127.0.0.1
server.listen(port);

console.log("Server running at http://127.0.0.1:8000/");

var log = function (msg) {
    if (doLogging)
        console.log(msg);

}<commit_msg>Added handlers for SIGINT and SIGTERM. Saving DB before shutting down.<commit_after>var os = require('os-utils');
var RounderDB = require('rounderdb');
var http = require('http');
var util = require('util');
var fs = require('fs');

var selfMon = require('./lib/monitorSelf.js');
var router = require('./lib/router.js');

var config = require('./conf/default.conf');
var DBconf = {
    "persistenceConf": {
        "dbFile": "./db/rounderDB.json",
        "syncInterval": 5
    },
    "cpuUsage": [
        {
            "capacity": 60,
            "aggregationStrategy": "average"
        },
        {
            "capacity": 600,
            "aggregationStrategy": "average"
        }
    ],
    "loadAvg": [
        {
            "capacity": 60,
            "aggregationStrategy": "average"
        },
        {
            "capacity": 600,
            "aggregationStrategy": "average"
        }
    ],
    "selfMem": [
        {
            "capacity": 60,
            "aggregationStrategy": "average"
        },
        {
            "capacity": 600,
            "aggregationStrategy": "average"
        }
    ],
    "freemem": [
        {
            "capacity": 60,
            "aggregationStrategy": "average"
        },
        {
            "capacity": 600
        }
    ]
};

var port = 8000;
var db = null;

if (fs.existsSync(DBconf.persistenceConf.dbFile)) {
    console.log("CREATING FROM FILE: " + DBconf.persistenceConf.dbFile);
    var data = fs.readFileSync(DBconf.persistenceConf.dbFile);
    var obj = JSON.parse(data);
    db = RounderDB.loadInstance(obj);
} else
    db = RounderDB.createInstance(DBconf);

// Enable logging
var doLogging = false;
var dryrun = false; // if true, don't add anything to db (just print, if doLogging is enabled)

var intervalCPU = 1000;
var intervalLoadAvg = 1 * 60 * 1000;
var intervalMem = 1500;

var addToDB = function (key, value) {
    if (!dryrun)
        db.add(key, value);
}

var cpuMon = function () {
    os.cpuUsage(function (v) {
        log('CPU Usage (%): ' + v);
        addToDB('cpuUsage', v);
    });
};

var memMon = function () {
    var freeMem = os.freememPercentage();
    log('Free mem (%): ' + freeMem);
    addToDB('freemem', freeMem);
};

var selfMem = function () {
    var freeMem = selfMon.RAMUsed();
    log('Process mem (MB): ' + freeMem);
    addToDB('selfMem', freeMem);
};

var loadAvg = function () {
    var load = os.loadavg(1);
    log('Load Avg. : ' + load);
    addToDB('loadAvg', load);
};

// Register data collectors
//
var cpuHandle = setInterval(cpuMon, intervalCPU);
var cpuLoad = setInterval(loadAvg, intervalLoadAvg);
var memHandle = setInterval(memMon, intervalMem);
var selfMemHandle = setInterval(selfMem, intervalMem);

var shutdownHook = function(err) {
    var exitCode = 0;
    if(err) {
        console.err("ERROR. Couldn't save DB before shutting down. Cause: "+err);
        exitCode = 1;
    }
    process.exit(exitCode);
  };

// Configure graceful exits on SIGINT and SIGTERM
//
process.on('SIGINT', function() {
  console.log('Got SIGINT.  Saving DB and shutting down');
  RounderDB.saveInstance(db, shutdownHook);
});

process.on('SIGTERM', function() {
  console.log('Got SIGTERM.  Saving DB and shutting down');
  RounderDB.saveInstance(db, shutdownHook);
});

// Configure our HTTP server to respond with Hello World to all requests.
var server = http.createServer(function (request, response) {
//    console.log("----- NEW REQEST!\n"+util.inspect(request));
    console.log("----- NEW REQEST! method: " + request.method + " URL: " + request.url);
    var result = router.dispatch(db, request.method, request.url);
    response.writeHead(result.status, {"Content-Type": "text/plain"});
    response.end(result.data);
});

// Listen on port 8000, IP defaults to 127.0.0.1
server.listen(port);

console.log("Server running at http://127.0.0.1:8000/");

var log = function (msg) {
    if (doLogging)
        console.log(msg);

}<|endoftext|>


<commit_before>;((rc) => {
  'use strict';
  var tagContent = 'router3';

  var div = document.createElement('div');
  div.innerHTML = `
    <${tagContent} hash="case10-1" hidden>
      case 10-1
      <${tagContent} hash="case10-11">
        case 10-11

        <!-- Default -->
        <${tagContent} id="defA" hash="">
          Default 10-default A
        </${tagContent}>

        <${tagContent} hash="case10-111">
          case 10-111
        </${tagContent}>

        <!-- Default -->
        <${tagContent} id="defB" hash="">
          Default 10-default B
        </${tagContent}>
      </${tagContent}>

      <!-- Default -->
      <${tagContent} id="defC" hash="">
        case 10-default C
      </${tagContent}>
    </${tagContent}>

    <!-- Default -->
    <${tagContent} id="def" hash="">
      case 10-default
    </${tagContent}>
  `;

  var async1 = async_test('Case 10: hash changed to content[hash="case10-1"]');

  async1.next = async1.step_func(_ => {
    var hash = "case10-1";
    var content1 = document.querySelector(`${tagContent}[hash="${hash}"]`);
    var content2 = document.querySelector(`${tagContent}#def`);
    var content3 = document.querySelector(`${tagContent}#defC`);

    assert_true(content1.hidden);

    var check_hash_1 = async1.step_func((e) => {
      assert_false(content1.hidden);
      assert_true(content2.hidden);
      assert_false(content3.hidden);

      content1.removeEventListener('show', check_hash_1);
      content1.addEventListener('hide', check_hash_def);
      window.location.hash = '';
    });

    var check_hash_def = async1.step_func(e => {
      setTimeout(_ => {
        assert_false(content2.hidden);
        async1.done();

        document.body.removeChild(div);
        rc.next();
      }, 10);
      content1.removeEventListener('hide', check_hash_def);
    });

    content1.addEventListener('show', check_hash_1);
    window.location.hash = hash;
  });

  rc.push(_ => {
    async1.step(_ => {
      document.body.appendChild(div);
      async1.next();
    });
  })

})(window.routeCases);
<commit_msg>Add another test to cover a nested default view<commit_after>;((rc) => {
  'use strict';
  var tagContent = 'router3';

  var div = document.createElement('div');
  div.innerHTML = `
    <${tagContent} hash="case10-1" hidden>
      case 10-1
      <${tagContent} hash="case10-11">
        case 10-11

        <!-- Default -->
        <${tagContent} id="defA" hash="">
          Default 10-default A
        </${tagContent}>

        <${tagContent} hash="case10-111">
          case 10-111
        </${tagContent}>

        <!-- Default -->
        <${tagContent} id="defB" hash="">
          Default 10-default B
        </${tagContent}>
      </${tagContent}>

      <!-- Default -->
      <${tagContent} id="defC" hash="">
        case 10-default C
      </${tagContent}>
    </${tagContent}>

    <!-- Default -->
    <${tagContent} id="def" hash="">
      case 10-default
    </${tagContent}>
  `;

  var async1 = async_test('Case 10: hash changed to content[hash="case10-1"]');
  var async2 = async_test('Case 10: hash changed to content[hash="case10-1/case10-11"]');
  //var async3 = async_test('Case 10: hash changed to content[hash="case10-1/case10-11/case10-111"]');

  async1.next = async1.step_func(_ => {
    var hash = "case10-1";
    var content1 = document.querySelector(`${tagContent}[hash="${hash}"]`);
    var content2 = document.querySelector(`${tagContent}#def`);
    var content3 = document.querySelector(`${tagContent}#defC`);

    assert_true(content1.hidden);

    var check_hash_1 = async1.step_func((e) => {
      assert_false(content1.hidden);
      assert_true(content2.hidden);
      assert_false(content3.hidden);

      content1.removeEventListener('show', check_hash_1);
      content1.addEventListener('hide', check_hash_def);
      window.location.hash = '';
    });

    var check_hash_def = async1.step_func(e => {
      setTimeout(_ => {
        assert_false(content2.hidden);
        async1.done();
        async2.next();
      }, 10);
      content1.removeEventListener('hide', check_hash_def);
    });

    content1.addEventListener('show', check_hash_1);
    window.location.hash = hash;
  });

  async2.next = async2.step_func(_ => {
    var hash = "case10-11";
    var content1 = document.querySelector(`${tagContent}[hash="${hash}"]`);
    var content2 = document.querySelector(`${tagContent}#def`);
    var content3 = document.querySelector(`${tagContent}#defC`);
    var content4 = document.querySelector(`${tagContent}#defB`);
    var content5 = document.querySelector(`${tagContent}#defA`);

    var check_hash_1 = async2.step_func((e) => {
      assert_false(content1.hidden);
      assert_true(content2.hidden);
      assert_true(content3.hidden);
      assert_false(content4.hidden);
      assert_false(content5.hidden);

      content1.removeEventListener('show', check_hash_1);
      content1.addEventListener('hide', check_hash_def);
      window.location.hash = '';
    });

    var check_hash_def = async2.step_func(e => {
      setTimeout(_ => {
        assert_true(content4.hidden);
        assert_true(content5.hidden);
        async2.done();

        document.body.removeChild(div);
        rc.next();
      }, 10);
      content1.removeEventListener('hide', check_hash_def);
    });

    content1.addEventListener('show', check_hash_1);
    window.location.hash = "case10-1/" + hash;
  });

  rc.push(_ => {
    async1.step(_ => {
      document.body.appendChild(div);
      async1.next();
    });
  })

})(window.routeCases);
<|endoftext|>


<commit_before>'use strict';

function notEmpty(src) {
    return !!src;
}

module.exports = [{
    type:    'input',
    name:    'name',
    message: 'Project Name',
    validate: notEmpty
}, {
    type:     'input',
    name:     'description',
    message:  'Description',
    validate: notEmpty
}, {
    type: 'list',
    name: 'engine',
    message: 'Which templating engine?',
    choices: [{
        name:  'Handlebars',
        value: 'handlebars'
    }, {
        name:  'DustJS',
        value: 'dust'
    }],
    default: 'handlebars'
}, {
    type:    'list',
    name:    'grid',
    message: 'Which grid system?',
    choices: [{
        name:  'Bootstrap',
        value: 'bootstrap'
    }, {
        name:  'Susy',
        value: 'susy'
    }]
}];
<commit_msg>Add default grid system prompt<commit_after>'use strict';

function notEmpty(src) {
    return !!src;
}

module.exports = [{
    type:    'input',
    name:    'name',
    message: 'Project Name',
    validate: notEmpty
}, {
    type:     'input',
    name:     'description',
    message:  'Description',
    validate: notEmpty
}, {
    type: 'list',
    name: 'engine',
    message: 'Which templating engine?',
    choices: [{
        name:  'Handlebars',
        value: 'handlebars'
    }, {
        name:  'DustJS',
        value: 'dust'
    }],
    default: 'handlebars'
}, {
    type:    'list',
    name:    'grid',
    message: 'Which grid system?',
    choices: [{
        name:  'Bootstrap',
        value: 'bootstrap'
    }, {
        name:  'Susy',
        value: 'susy'
    }],
    default: 'bootstrap'
}];
<|endoftext|>


<commit_before>/*
 * files.js: Instance methods for working with files from Rackspace Cloudfiles
 *
 * (C) 2011 Nodejitsu Inc.
 *
 */

var fs = require('fs'),
    filed = require('filed'),
    mime = require('mime'),
    request = require('request'),
    utile = require('utile'),
    base = require('../../../core/storage'),
    pkgcloud = require('../../../../../lib/pkgcloud'),
    storage = pkgcloud.providers.rackspace.storage;

//
// ### function removeFile (container, file, callback)
// #### @container {string} Name of the container to destroy the file in
// #### @file {string} Name of the file to destroy.
// #### @callback {function} Continuation to respond to when complete.
// Destroys the `file` in the specified `container`.
//
exports.removeFile = function (container, file, callback) {
  this.request('DELETE', [container, file], callback, function (body, res) {
    callback(null, true);
  });
};

exports.upload = function (options, callback) {
  if (typeof options === 'function' && !callback) {
    callback = options;
    options = {};
  }

  var container = options.container,
      rstream,
      lstream;

  if (container instanceof storage.Container) {
    container = container.name;
  }

  if (options.local) {
    lstream = filed(options.local);
    options.headers = options.headers || {};
    options.headers['content-length'] = fs.statSync(options.local).size;
  }
  else if (options.stream) {
    lstream = options.stream;
  }

  if (options.headers && !options.headers['content-type']) {
    options.headers['content-type'] = mime.lookup(options.remote);
  }

  rstream = this.request({
    method: 'PUT',
    upload: true,
    path: [container, options.remote],
    headers: options.headers || {}
  }, callback, function (body, res) {
    callback(null, true);
  });

  if (lstream) {
    lstream.pipe(rstream);
  }

  return rstream;
};

exports.download = function (options, callback) {
  var self = this,
      container = options.container,
      lstream,
      rstream;

  if (container instanceof storage.Container) {
    container = container.name;
  }

  if (options.local) {
    lstream = filed(options.local);
  }
  else if (options.stream) {
    lstream = options.stream;
  }

  rstream = this.request({
    path: [container, options.remote],
    download: true
  }, callback, function (body, res) {
    callback(null, new (storage.File)(self, utile.mixin(res.headers, {
      container: container,
      name: options.remote
    })));
  });

  if (lstream) {
    rstream.pipe(lstream);
  }

  return rstream;
};

exports.getFile = function (container, file, callback) {
  var containerName = container instanceof base.Container ? container.name : container,
      self = this;

  this.request({
    method: 'HEAD',
    path: [containerName, file, true ]
  }, callback, function (body, res) {
    callback(null, new storage.File(self, utile.mixin(res.headers, {
      container: container,
      name: file
    })));
  });
}

exports.getFiles = function (container, callback) {
  var containerName = container instanceof base.Container ? container.name : container,
      self = this;

  this.request({ path: [containerName, true] }, callback, function (body, res) {
    callback(null, body.map(function (file) {
      file.container = container;
      return new storage.File(self, file);
    }));
  });
};

<commit_msg>changed getFiles() to adhere to current signature (accepts download param) made removeFile() more flexible<commit_after>/*
 * files.js: Instance methods for working with files from Rackspace Cloudfiles
 *
 * (C) 2011 Nodejitsu Inc.
 *
 */

var fs = require('fs'),
    filed = require('filed'),
    mime = require('mime'),
    request = require('request'),
    utile = require('utile'),
    base = require('../../../core/storage'),
    pkgcloud = require('../../../../../lib/pkgcloud'),
    storage = pkgcloud.providers.rackspace.storage;

//
// ### function removeFile (container, file, callback)
// #### @container {string} Name of the container to destroy the file in
// #### @file {string} Name of the file to destroy.
// #### @callback {function} Continuation to respond to when complete.
// Destroys the `file` in the specified `container`.
//
exports.removeFile = function (container, file, callback) {
  this.request('DELETE'
    ,[container instanceof base.Container ? container.name : container, file]
    ,callback
    ,callback.bind(null, null, true)
  );
};

exports.upload = function (options, callback) {
  if (typeof options === 'function' && !callback) {
    callback = options;
    options = {};
  }

  var container = options.container,
      rstream,
      lstream;

  if (container instanceof storage.Container) {
    container = container.name;
  }

  if (options.local) {
    lstream = filed(options.local);
    options.headers = options.headers || {};
    options.headers['content-length'] = fs.statSync(options.local).size;
  }
  else if (options.stream) {
    lstream = options.stream;
  }

  if (options.headers && !options.headers['content-type']) {
    options.headers['content-type'] = mime.lookup(options.remote);
  }

  rstream = this.request({
    method: 'PUT',
    upload: true,
    path: [container, options.remote],
    headers: options.headers || {}
  }, callback, function (body, res) {
    callback(null, true);
  });

  if (lstream) {
    lstream.pipe(rstream);
  }

  return rstream;
};

exports.download = function (options, callback) {
  var self = this,
      container = options.container,
      lstream,
      rstream;

  if (container instanceof storage.Container) {
    container = container.name;
  }

  if (options.local) {
    lstream = filed(options.local);
  }
  else if (options.stream) {
    lstream = options.stream;
  }

  rstream = this.request({
    path: [container, options.remote],
    download: true
  }, callback, function (body, res) {
    callback(null, new (storage.File)(self, utile.mixin(res.headers, {
      container: container,
      name: options.remote
    })));
  });

  if (lstream) {
    rstream.pipe(lstream);
  }

  return rstream;
};

exports.getFile = function (container, file, callback) {
  var containerName = container instanceof base.Container ? container.name : container,
      self = this;

  this.request({
    method: 'HEAD',
    path: [containerName, file, true ]
  }, callback, function (body, res) {
    callback(null, new storage.File(self, utile.mixin(res.headers, {
      container: container,
      name: file
    })));
  });
};

exports.getFiles = function (container, download, callback) {
  var containerName = container instanceof base.Container ? container.name : container,
      self = this;

  if (typeof download == 'function' && !(download instanceof RegExp)) {
    callback = download;
    download = false;
  }

  this.request({ path: [containerName, true] }, callback, function (body, res) {
    callback(null, body.map(function (file) {
      file.container = container;
      return new storage.File(self, file);
    }));
  });
};

<|endoftext|>


<commit_before>(function() {
  "use strict";

  var makeModule = function(require, exports) {

    var base = require('./base');
    var curry = base.curry;
    var curryWithArity = base.curryWithArity;
    var flip = base.flip;
    var permuteLeft = base.permuteLeft;

    /*
     * callPropWithArity: takes a property name, and an arity. Returns a curried function
     *                    of (arity + 1) arguments, that calls the given property on an
     *                    object with the first arity arguments (the object is the last parameter),
     *                    returning the result. The function is called on the object with the parameters
     *                    in the order given.
     *
     * For example:
     *   var myMap = callPropWithArity('map', 1);
     *   myMap(f, arr);  // returns arr.map(f);
     *   var myFoldr = callPropWithArity('reduceRight', 2);
     *   myFoldr(f, initialValue, arr); // returns arr.reduceRight(f, initialValue);
     *
     *   var myConstructor = function() {};
     *   myConstructor.prototype.return42 = function() {return 42;};
     *   var myObj = new myConstructor();
     *   var call42 = callPropWithArity('return42', 0);
     *   call42(myObj);  // calls myObj.return42();
     *
     */

    var callPropWithArity = curry(function(prop, arity) {
      return curryWithArity(arity + 1, function() {
        // curryWithArity guarantees we will be called with arity + 1 args
        var propArgs = [].slice.call(arguments, 0, arity);
        var obj = arguments[arity];

        return obj[prop].apply(obj, propArgs);
      });
    });

    /*
     * callProp: A shorthand for callPropWithArity(prop, 0). Returns a function that takes an object
     *           and calls the given property on the object.
     *
     * For example:
     *   var myConstructor = function() {};
     *   myConstructor.prototype.return42 = function() {return 42;};
     *   var myObj = new myConstructor();
     *   var call42 = callProp('return42');
     *   call42(myObj);  // calls myObj.return42();
     *
     */

    var callProp = flip(callPropWithArity)(0);

    /*
     * hasOwnProperty: a curried wrapper around Object.prototype.hasOwnProperty. Takes a
     *                 string containing a property name and an object.
     *
     * hasOwnProperty('funkier', {funkier: 1}); // true
     * hasOwnProperty('toString', {funkier: 1}); // false
     *
     */

    var hasOwnProperty = callPropWithArity('hasOwnProperty', 1);

    /*
     * hasProperty: a curried wrapper around the 'in' operator. Takes a
     *              string containing a property name and an object.
     *
     * hasProperty('funkier', {funkier: 1}); // true
     * hasProperty('toString', {funkier: 1}); // true
     *
     */

    var hasProperty = curry(function(prop, object) {
      return prop in object;
    });

    /*
     * instanceOf: a curried wrapper around the 'instanceof' operator. Takes a
     *             constructor function and an object.
     *
     * var Constructor = function() {};
     * instanceOf(Constructor, new Constructor()); // true
     * instanceOf(Function, {}); // false
     *
     */

    var instanceOf = curry(function(constructor, object) {
      return object instanceof constructor;
    });

    /*
     * isPrototypeOf: a curried wrapper around Object.prototype.isPrototypeOf.
     *                Takes two objects: the prototype object, and the object whose
     *                prototype chain you wish to check.
     *
     * var Constructor = function() {};
     * isPrototypeOf(Constructor.prototype, new Constructor()); // true
     * isPrototypeOf(Function.prototype, {}); // false
     *
     */

    var isPrototypeOf = flip(callPropWithArity('isPrototypeOf', 1));

    /*
     * createObject: creates an object whose internal prototype property is the given object.
     *
     * var obj = {};
     * var newObj = createObject(obj);
     * isPrototypeOf(obj, newObj); // true
     *
     * Note: this is an unary function that discards excess arguments. If you need to add new
     * properties, use createObjectWithProps.
     */

    var createObject = curry(function(obj) {
      return Object.create(obj);
    });

    /*
     * createObjectWithProps: creates an object whose internal prototype property is the given object,
     *                        and which has the additional properties described in the given property
     *                        descriptor object. The property descriptor object is of the form accepted
     *                        by Object.create, Object.defineProperties etc.
     *
     * var obj = {};
     * var newObj = createObjectWithProps(obj, {prop: configurable: false, enumerable: false, writeable: true, value: 1}});
     * isPrototypeOf(obj, newObj); // true
     * hasOwnProperty('prop', newObj); // true
     *
     */

    var createObjectWithProps = curry(function(obj, descriptor) {
      return Object.create(obj, descriptor);
    });

    /*
     * defineProperty: a curried wrapper around Object.defineProperty. Takes a property name,
     *                 a property descriptor, and an object. Returns the object.
     *
     */

    var defineProperty = permuteLeft(Object.defineProperty);

    /*
     * defineProperties: a curried wrapper around Object.defineProperties. Takes an object whose
     *                   properties are property descriptors, and an object. Returns the object.
     *
     */

    var defineProperties = permuteLeft(Object.defineProperties);

    /*
     * getOwnPropertyDescriptor: a curried wrapper around Object.getOwnPropertyDescriptor.
     *                           Takes the name of a property and an object. Returns the property
     *                           descriptor only if the object itself has the given property, and
     *                           undefined otherwise.
     *
     */

    var getOwnPropertyDescriptor = flip(Object.getOwnPropertyDescriptor);

    /*
     * extract: extract the given property from the given object. Equivalent to evaluating obj[prop].
     *
     */

    var extract = curry(function(prop, obj) {
      return obj[prop];
    });

    // A thousand curses!
    // Per the ECMAScript spec, when setting a property on an object, the property descriptor - if it exists -
    // should be checked. Setting should fail when writable=false or there is no setter in the descriptor.
    // If the property descriptor doesn't exist on the property, then walk up the prototype chain making the
    // same check.
    //
    // Versions of V8 up to 3.11.9 fail to walk up the prototype chain. Further, when it was fixed, the fix
    // was gated behind a flag, which defaulted to false until 3.13.6, when the flag was flipped and V8 became
    // spec-compliant. The flag was removed in 3.25.4.
    //
    // Nevertheless, despite the flag being flipped in 3.13.6, node continues by default to set the flag to
    // spec-incompliant.
    var engineHandlesProtosCorrectly = (function() {
      var a = function(){};
      Object.defineProperty(a.prototype, 'foo', {writable: false});
      var compliant = false;
      var b = new a();

      try {
        b.foo = 1;
      } catch (e) {
        compliant = true;
      }

      return compliant;
    })();

    // Utility function for set: work backwards to Object.prototype, looking for a property descriptor
    var findPropertyDescriptor = function(prop, obj) {
      var descriptor = undefined;
      var toppedOut = false;

      while (descriptor === undefined && !toppedOut) {
        descriptor = getOwnPropertyDescriptor(prop, obj);
        if (descriptor === undefined) {
          if (obj === Object.prototype) {
            toppedOut = true;
          } else {
            obj = Object.getPrototypeOf(obj);
          }
        }
      }

      return descriptor;
    };

    var checkIfWritable = function(prop, obj) {
      var writable = true;
      var descriptor = findPropertyDescriptor(prop, obj);

      // Don't modify writable false properties
      if (descriptor && 'writable' in descriptor && descriptor.writable === false)
        writable = false;

      // Don't modify no setter properties
      if (descriptor && writable && 'set' in descriptor && descriptor.set === undefined)
        writable = false;

      return writable;
    };

    var set = curry(function(prop, val, obj) {
      // We manually emulate the operation of [[CanPut]], rather than just setting in a
      // try-catch. We don't want to suppress other errors: for example the set function
      // might throw
      var writable = checkIfWritable(prop, obj);

      if (writable && !hasOwnProperty(prop, obj) && (Object.isSealed(obj) || !Object.isExtensible(obj)))
        writable = false;

      if (writable)
        obj[prop] = val;
      return obj;
    });

    /*
     * setOrThrow: set the given property to the given value on the given object, returning the object.
     *             Equivalent to evaluating obj[prop] = val. The property will be created if it doesn't
     *             exist on the object. This function will throw when the property is not writable,
     *             when it has no setter function, or when the object is frozen. Likewise, if the property
     *             must be created, it will throw if the object is sealed, frozen, or not extensible.
     *
     * If you don't want to throw when setting, use set.
     *
     */

    var setOrThrow = curry(function(prop, val, obj) {
      // We *should* be able to just set the property, and trust the JS
      // engine to throw. Sadly, this is untrue; see the comments for
      // engineHandlesProtosCorrectly.

      // Take the fast path if we can
      if (engineHandlesProtosCorrectly) {
        obj[prop] = val;
      } else {
        if (checkIfWritable(prop, Object.getPrototypeOf(obj))) {
          obj[prop] = val;
        } else {
          // Spec says throw a TypeError
          throw new TypeError('Setting property disallowed by prototype');
        }
      }

      return obj;
    });

    /*
     * modify: set the given property to the given value on the given object, returning the object.
     *         Equivalent to evaluating obj[prop] = val. The property will *not* be created when the object
     *         does not have the property, and will instead silently fail. In particular, the property will
     *         not be created if it exists only in the prototype chain: hasOwnProperty(prop, obj) must be true.
     *
     *         This function will also not throw when the property is not writable, when it has no setter function,
     *         or when the object is frozen. Again, it will silently fail.
     *
     */

    var modify = curry(function(prop, val, obj) {
      // Return straight away if the property doesn't exist
      if (!hasOwnProperty(prop, obj))
        return obj;

      return set(prop, val, obj);
    });

    var exported = {
      callProp: callProp,
      callPropWithArity: callPropWithArity,
      createObject: createObject,
      createObjectWithProps: createObjectWithProps,
      defineProperty: defineProperty,
      defineProperties: defineProperties,
      extract: extract,
      getOwnPropertyDescriptor: getOwnPropertyDescriptor,
      hasOwnProperty: hasOwnProperty,
      hasProperty: hasProperty,
      instanceOf: instanceOf,
      isPrototypeOf: isPrototypeOf,
      modify: modify,
      set: set,
      setOrThrow: setOrThrow
    };

    module.exports = exported;
  };

  // AMD/CommonJS foo
  if (typeof(define) === "function") {
    define(function(require, exports, module) {
      makeModule(require, exports, module);
    });
  } else {
    makeModule(require, exports, module);
  }
})();
<commit_msg>Restore accidentally deleted comment<commit_after>(function() {
  "use strict";

  var makeModule = function(require, exports) {

    var base = require('./base');
    var curry = base.curry;
    var curryWithArity = base.curryWithArity;
    var flip = base.flip;
    var permuteLeft = base.permuteLeft;

    /*
     * callPropWithArity: takes a property name, and an arity. Returns a curried function
     *                    of (arity + 1) arguments, that calls the given property on an
     *                    object with the first arity arguments (the object is the last parameter),
     *                    returning the result. The function is called on the object with the parameters
     *                    in the order given.
     *
     * For example:
     *   var myMap = callPropWithArity('map', 1);
     *   myMap(f, arr);  // returns arr.map(f);
     *   var myFoldr = callPropWithArity('reduceRight', 2);
     *   myFoldr(f, initialValue, arr); // returns arr.reduceRight(f, initialValue);
     *
     *   var myConstructor = function() {};
     *   myConstructor.prototype.return42 = function() {return 42;};
     *   var myObj = new myConstructor();
     *   var call42 = callPropWithArity('return42', 0);
     *   call42(myObj);  // calls myObj.return42();
     *
     */

    var callPropWithArity = curry(function(prop, arity) {
      return curryWithArity(arity + 1, function() {
        // curryWithArity guarantees we will be called with arity + 1 args
        var propArgs = [].slice.call(arguments, 0, arity);
        var obj = arguments[arity];

        return obj[prop].apply(obj, propArgs);
      });
    });

    /*
     * callProp: A shorthand for callPropWithArity(prop, 0). Returns a function that takes an object
     *           and calls the given property on the object.
     *
     * For example:
     *   var myConstructor = function() {};
     *   myConstructor.prototype.return42 = function() {return 42;};
     *   var myObj = new myConstructor();
     *   var call42 = callProp('return42');
     *   call42(myObj);  // calls myObj.return42();
     *
     */

    var callProp = flip(callPropWithArity)(0);

    /*
     * hasOwnProperty: a curried wrapper around Object.prototype.hasOwnProperty. Takes a
     *                 string containing a property name and an object.
     *
     * hasOwnProperty('funkier', {funkier: 1}); // true
     * hasOwnProperty('toString', {funkier: 1}); // false
     *
     */

    var hasOwnProperty = callPropWithArity('hasOwnProperty', 1);

    /*
     * hasProperty: a curried wrapper around the 'in' operator. Takes a
     *              string containing a property name and an object.
     *
     * hasProperty('funkier', {funkier: 1}); // true
     * hasProperty('toString', {funkier: 1}); // true
     *
     */

    var hasProperty = curry(function(prop, object) {
      return prop in object;
    });

    /*
     * instanceOf: a curried wrapper around the 'instanceof' operator. Takes a
     *             constructor function and an object.
     *
     * var Constructor = function() {};
     * instanceOf(Constructor, new Constructor()); // true
     * instanceOf(Function, {}); // false
     *
     */

    var instanceOf = curry(function(constructor, object) {
      return object instanceof constructor;
    });

    /*
     * isPrototypeOf: a curried wrapper around Object.prototype.isPrototypeOf.
     *                Takes two objects: the prototype object, and the object whose
     *                prototype chain you wish to check.
     *
     * var Constructor = function() {};
     * isPrototypeOf(Constructor.prototype, new Constructor()); // true
     * isPrototypeOf(Function.prototype, {}); // false
     *
     */

    var isPrototypeOf = flip(callPropWithArity('isPrototypeOf', 1));

    /*
     * createObject: creates an object whose internal prototype property is the given object.
     *
     * var obj = {};
     * var newObj = createObject(obj);
     * isPrototypeOf(obj, newObj); // true
     *
     * Note: this is an unary function that discards excess arguments. If you need to add new
     * properties, use createObjectWithProps.
     */

    var createObject = curry(function(obj) {
      return Object.create(obj);
    });

    /*
     * createObjectWithProps: creates an object whose internal prototype property is the given object,
     *                        and which has the additional properties described in the given property
     *                        descriptor object. The property descriptor object is of the form accepted
     *                        by Object.create, Object.defineProperties etc.
     *
     * var obj = {};
     * var newObj = createObjectWithProps(obj, {prop: configurable: false, enumerable: false, writeable: true, value: 1}});
     * isPrototypeOf(obj, newObj); // true
     * hasOwnProperty('prop', newObj); // true
     *
     */

    var createObjectWithProps = curry(function(obj, descriptor) {
      return Object.create(obj, descriptor);
    });

    /*
     * defineProperty: a curried wrapper around Object.defineProperty. Takes a property name,
     *                 a property descriptor, and an object. Returns the object.
     *
     */

    var defineProperty = permuteLeft(Object.defineProperty);

    /*
     * defineProperties: a curried wrapper around Object.defineProperties. Takes an object whose
     *                   properties are property descriptors, and an object. Returns the object.
     *
     */

    var defineProperties = permuteLeft(Object.defineProperties);

    /*
     * getOwnPropertyDescriptor: a curried wrapper around Object.getOwnPropertyDescriptor.
     *                           Takes the name of a property and an object. Returns the property
     *                           descriptor only if the object itself has the given property, and
     *                           undefined otherwise.
     *
     */

    var getOwnPropertyDescriptor = flip(Object.getOwnPropertyDescriptor);

    /*
     * extract: extract the given property from the given object. Equivalent to evaluating obj[prop].
     *
     */

    var extract = curry(function(prop, obj) {
      return obj[prop];
    });

    // A thousand curses!
    // Per the ECMAScript spec, when setting a property on an object, the property descriptor - if it exists -
    // should be checked. Setting should fail when writable=false or there is no setter in the descriptor.
    // If the property descriptor doesn't exist on the property, then walk up the prototype chain making the
    // same check.
    //
    // Versions of V8 up to 3.11.9 fail to walk up the prototype chain. Further, when it was fixed, the fix
    // was gated behind a flag, which defaulted to false until 3.13.6, when the flag was flipped and V8 became
    // spec-compliant. The flag was removed in 3.25.4.
    //
    // Nevertheless, despite the flag being flipped in 3.13.6, node continues by default to set the flag to
    // spec-incompliant.
    var engineHandlesProtosCorrectly = (function() {
      var a = function(){};
      Object.defineProperty(a.prototype, 'foo', {writable: false});
      var compliant = false;
      var b = new a();

      try {
        b.foo = 1;
      } catch (e) {
        compliant = true;
      }

      return compliant;
    })();

    // Utility function for set: work backwards to Object.prototype, looking for a property descriptor
    var findPropertyDescriptor = function(prop, obj) {
      var descriptor = undefined;
      var toppedOut = false;

      while (descriptor === undefined && !toppedOut) {
        descriptor = getOwnPropertyDescriptor(prop, obj);
        if (descriptor === undefined) {
          if (obj === Object.prototype) {
            toppedOut = true;
          } else {
            obj = Object.getPrototypeOf(obj);
          }
        }
      }

      return descriptor;
    };

    var checkIfWritable = function(prop, obj) {
      var writable = true;
      var descriptor = findPropertyDescriptor(prop, obj);

      // Don't modify writable false properties
      if (descriptor && 'writable' in descriptor && descriptor.writable === false)
        writable = false;

      // Don't modify no setter properties
      if (descriptor && writable && 'set' in descriptor && descriptor.set === undefined)
        writable = false;

      return writable;
    };

    /*
     * set: set the given property to the given value on the given object, returning the object.
     *      Equivalent to evaluating obj[prop] = val. The property will be created if it doesn't
     *      exist on the object. This function will not throw when the property is not writable,
     *      when it has no setter function, or when the object is frozen. Likewise, if the property
     *      must be created, it will not throw if the object is sealed, frozen, or Object.preventExtensions
     *      has been called. It will fail silently in all these cases.
     *
     * Use setOrThrow if you require a function which throws in such circumstances.
     * Use modify if you require a function that will only modify existing properties.
     */

    var set = curry(function(prop, val, obj) {
      // We manually emulate the operation of [[CanPut]], rather than just setting in a
      // try-catch. We don't want to suppress other errors: for example the set function
      // might throw
      var writable = checkIfWritable(prop, obj);

      if (writable && !hasOwnProperty(prop, obj) && (Object.isSealed(obj) || !Object.isExtensible(obj)))
        writable = false;

      if (writable)
        obj[prop] = val;
      return obj;
    });

    /*
     * setOrThrow: set the given property to the given value on the given object, returning the object.
     *             Equivalent to evaluating obj[prop] = val. The property will be created if it doesn't
     *             exist on the object. This function will throw when the property is not writable,
     *             when it has no setter function, or when the object is frozen. Likewise, if the property
     *             must be created, it will throw if the object is sealed, frozen, or not extensible.
     *
     * Use set if you require a function which will not throw in such circumstances.
     *
     */

    var setOrThrow = curry(function(prop, val, obj) {
      // We *should* be able to just set the property, and trust the JS
      // engine to throw. Sadly, this is untrue; see the comments for
      // engineHandlesProtosCorrectly.

      // Take the fast path if we can
      if (engineHandlesProtosCorrectly) {
        obj[prop] = val;
      } else {
        if (checkIfWritable(prop, Object.getPrototypeOf(obj))) {
          obj[prop] = val;
        } else {
          // Spec says throw a TypeError
          throw new TypeError('Setting property disallowed by prototype');
        }
      }

      return obj;
    });

    /*
     * modify: set the given property to the given value on the given object, returning the object.
     *         Equivalent to evaluating obj[prop] = val. The property will *not* be created when the object
     *         does not have the property, and will instead silently fail. In particular, the property will
     *         not be created if it exists only in the prototype chain: hasOwnProperty(prop, obj) must be true.
     *
     *         This function will also not throw when the property is not writable, when it has no setter function,
     *         or when the object is frozen. Again, it will silently fail.
     *
     */

    var modify = curry(function(prop, val, obj) {
      // Return straight away if the property doesn't exist
      if (!hasOwnProperty(prop, obj))
        return obj;

      return set(prop, val, obj);
    });

    var exported = {
      callProp: callProp,
      callPropWithArity: callPropWithArity,
      createObject: createObject,
      createObjectWithProps: createObjectWithProps,
      defineProperty: defineProperty,
      defineProperties: defineProperties,
      extract: extract,
      getOwnPropertyDescriptor: getOwnPropertyDescriptor,
      hasOwnProperty: hasOwnProperty,
      hasProperty: hasProperty,
      instanceOf: instanceOf,
      isPrototypeOf: isPrototypeOf,
      modify: modify,
      set: set,
      setOrThrow: setOrThrow
    };

    module.exports = exported;
  };

  // AMD/CommonJS foo
  if (typeof(define) === "function") {
    define(function(require, exports, module) {
      makeModule(require, exports, module);
    });
  } else {
    makeModule(require, exports, module);
  }
})();
<|endoftext|>