<commit_before>Kwf.onContentReady(function(el, param) {
    if (!param.newRender) return false;
    Ext.select('.kwcForm', true, el).each(function(form) {
        if (!form.kwcForm) {
            form.kwcForm = new Kwc.Form.Component(form);
        }
    });
});
Ext.ns('Kwc.Form');
Kwc.Form.Component = function(form)
{
    this.addEvents('submitSuccess');
    this.el = form;
    var config = form.parent().down('.config', true);
    if (!config) return;
    config = Ext.decode(config.value);
    if (!config) return;
    this.config = config;

    this.fields = [];
    form.select('.kwfField', true).each(function(fieldEl) {
        var classes = fieldEl.dom.className.split(' ');
        var fieldConstructor = false;
        classes.each(function (c) {
            if (Kwf.FrontendForm.fields[c]) {
                fieldConstructor = Kwf.FrontendForm.fields[c];
            }
        }, this);
        if (fieldConstructor) {
            var field = new fieldConstructor(fieldEl);
            this.fields.push(field);
        }
    }, this);

    if (this.config.hideForValue) {
        this.config.hideForValue.each(function(hideForValue) {
            var field = this.findField(hideForValue.field);
            field.on('change', function(v, f) {
                if (v == hideForValue.value) {
                    this.findField(hideForValue.hide).hide();
                } else {
                    this.findField(hideForValue.hide).show();
                }
            }, this);

            //initialize on startup
            if (field.getValue() == hideForValue.value) {
                this.findField(hideForValue.hide).hide();
            }
        }, this);
    }

    var button = form.child('form button.submit');
    if (button) {
        button.on('click', this.onSubmit, this);
    }
};
Ext.extend(Kwc.Form.Component, Ext.util.Observable, {
    findField: function(fieldName) {
        var ret = null;
        this.fields.each(function(f) {
            if (f.getFieldName() == fieldName) {
                ret = f;
                return true;
            }
        }, this);
        return ret;
    },
    onSubmit: function(e) {
        if (this.dontUseAjaxRequest) return;

        var button = this.el.child('.button');
        button.down('.saving').show();
        button.down('.submit').hide();

        Ext.Ajax.request({
            url: this.config.controllerUrl + '/json-save',
            ignoreErrors: true,
            params: {
                componentId: this.config.componentId
            },
            form: this.el.down('form'),
            failure: function() {
                //on failure try a plain old post of the form
                this.dontUseAjaxRequest = true; //avoid endless recursion
                button.down('.submit').dom.click();
            },
            success: function(response, options, r) {

                var hasErrors = false;

                // remove and set error classes for fields
                this.fields.each(function(field) {
                    field.hideError();
                }, this);
                for(var fieldName in r.errorFields) {
                    var field = this.findField(fieldName);
                    field.showError(r.errorFields[fieldName]);
                    hasErrors= true;
                }

                // remove and add error messages
                var error = this.el.parent().down('.webFormError');
                if (error) error.remove();

                if (r.errorMessages && r.errorMessages.length) {
                    hasErrors= true;
                    var html = '
';
                    html += '
' + r.errorPlaceholder + ':
';
                    html += '
';
                    for (var i=0; i<r.errorMessages.length; i++) {
                        html += '
' + r.errorMessages[i] + '
';
                    }
                    html += '
';
                    html += '
';
                    this.el.parent().createChild(html, this.el.down('form'));
                    Kwf.callOnContentReady(this.el.dom, {newRender: true});
                }

                // show success content
                if (r.successContent) {
                    var el = this.el.parent().createChild(r.successContent);
                    this.el.remove();
                    Kwf.callOnContentReady(el.dom, {newRender: true});
                } else if (r.successUrl) {
                    document.location.href = r.successUrl;
                }

                if (!r.successUrl) {
                    button.down('.saving').hide();
                    button.down('.submit').show();
                }

                if (!hasErrors) {
                    this.fireEvent('submitSuccess', this, r);
                }
            },
            scope: this
        });

        e.stopEvent();
    }
});<commit_msg>bugfix errormessages in form<commit_after>Kwf.onContentReady(function(el, param) {
    if (!param.newRender) return false;
    Ext.select('.kwcForm', true, el).each(function(form) {
        if (!form.kwcForm) {
            form.kwcForm = new Kwc.Form.Component(form);
        }
    });
});
Ext.ns('Kwc.Form');
Kwc.Form.Component = function(form)
{
    this.addEvents('submitSuccess');
    this.el = form;
    var config = form.parent().down('.config', true);
    if (!config) return;
    config = Ext.decode(config.value);
    if (!config) return;
    this.config = config;

    this.fields = [];
    form.select('.kwfField', true).each(function(fieldEl) {
        var classes = fieldEl.dom.className.split(' ');
        var fieldConstructor = false;
        classes.each(function (c) {
            if (Kwf.FrontendForm.fields[c]) {
                fieldConstructor = Kwf.FrontendForm.fields[c];
            }
        }, this);
        if (fieldConstructor) {
            var field = new fieldConstructor(fieldEl);
            this.fields.push(field);
        }
    }, this);

    if (this.config.hideForValue) {
        this.config.hideForValue.each(function(hideForValue) {
            var field = this.findField(hideForValue.field);
            field.on('change', function(v, f) {
                if (v == hideForValue.value) {
                    this.findField(hideForValue.hide).hide();
                } else {
                    this.findField(hideForValue.hide).show();
                }
            }, this);

            //initialize on startup
            if (field.getValue() == hideForValue.value) {
                this.findField(hideForValue.hide).hide();
            }
        }, this);
    }

    var button = form.child('form button.submit');
    if (button) {
        button.on('click', this.onSubmit, this);
    }
};
Ext.extend(Kwc.Form.Component, Ext.util.Observable, {
    findField: function(fieldName) {
        var ret = null;
        this.fields.each(function(f) {
            if (f.getFieldName() == fieldName) {
                ret = f;
                return true;
            }
        }, this);
        return ret;
    },
    onSubmit: function(e) {
        if (this.dontUseAjaxRequest) return;

        var button = this.el.child('.button');
        button.down('.saving').show();
        button.down('.submit').hide();

        Ext.Ajax.request({
            url: this.config.controllerUrl + '/json-save',
            ignoreErrors: true,
            params: {
                componentId: this.config.componentId
            },
            form: this.el.down('form'),
            failure: function() {
                //on failure try a plain old post of the form
                this.dontUseAjaxRequest = true; //avoid endless recursion
                button.down('.submit').dom.click();
            },
            success: function(response, options, r) {

                var hasErrors = false;

                // remove and set error classes for fields
                this.fields.each(function(field) {
                    field.hideError();
                }, this);
                for(var fieldName in r.errorFields) {
                    var field = this.findField(fieldName);
                    field.showError(r.errorFields[fieldName]);
                    hasErrors= true;
                }

                // remove and add error messages
                var error = this.el.parent().child('.webFormError');
                if (error) error.remove();

                if (r.errorMessages && r.errorMessages.length) {
                    hasErrors= true;
                    var html = '
';
                    html += '
' + r.errorPlaceholder + ':
';
                    html += '
';
                    for (var i=0; i<r.errorMessages.length; i++) {
                        html += '
' + r.errorMessages[i] + '
';
                    }
                    html += '
';
                    html += '
';
                    this.el.parent().createChild(html, this.el.down('form'));
                    Kwf.callOnContentReady(this.el.dom, {newRender: true});
                }

                // show success content
                if (r.successContent) {
                    var el = this.el.parent().createChild(r.successContent);
                    this.el.remove();
                    Kwf.callOnContentReady(el.dom, {newRender: true});
                } else if (r.successUrl) {
                    document.location.href = r.successUrl;
                }

                if (!r.successUrl) {
                    button.down('.saving').hide();
                    button.down('.submit').show();
                }

                if (!hasErrors) {
                    this.fireEvent('submitSuccess', this, r);
                }
            },
            scope: this
        });

        e.stopEvent();
    }
});<|endoftext|>


<commit_before>function getSelectedText(divID)
{
	if (typeof divID == 'undefined' || divID == false)
		return false;

	var text = '',
	selection,
	found = 0;

	if (window.getSelection)
	{
		selection = window.getSelection();
		text = selection.toString();
	}
	else if (document.selection && document.selection.type != 'Control')
	{
		selection = document.selection.createRange();
		text = selection.text;
	}

	// Need to be sure the selected text does belong to the right div.
	for (var i = 0; i < selection.rangeCount; i++) {
			s = selection.getRangeAt(i).startContainer.parentNode.id;
			e = selection.getRangeAt(i).endContainer.parentNode.id;

			if (s == divID || (s != divID && e == 'child'))
			{
				found = 1;
				break;
			}
		}

	return found === 1 ? text : false;
}

function quotedTextClick(oOptions)
{
		text = '';

		// The process has been started, hide the button.
		$('#quoteSelected_' + oOptions.msgID).hide();

		// Do a call to make sure this is a valid message.
		$.ajax({
			url: smf_prepareScriptUrl(smf_scripturl) + 'action=quotefast;quote=' + oOptions.msgID + ';xml;pb='+ oEditorID + ';mode=' + (oEditorObject.bRichTextEnabled ? 1 : 0),
			type: 'GET',
			dataType: 'xml',
			beforeSend: function () {
				ajax_indicator(true);
			},
			success: function (data, textStatus, xhr) {
				// Search the xml data to get the quote tag.
				text = $(data).find('quote').text();

				// Insert the selected text between the quotes BBC tags.
				text = text.match(/^\[quote(.*)]/ig) + oOptions.text + '[/quote]' + '\n\n';

				// Add the whole text to the editor's instance.
				$('#' + oEditorID).data('sceditor').InsertText(text);

				// Move the view to the quick reply box. If available.
				if (typeof oJumpAnchor != 'undefined'){
					if (navigator.appName == 'Microsoft Internet Explorer')
						window.location.hash = oJumpAnchor;
					else
						window.location.hash = '#' + oJumpAnchor;
				}

				ajax_indicator(false);
			},
			error: function (xhr, textStatus, errorThrown) {
				ajax_indicator(false);
			}
		});
}

$(function() {

	// Event for handling selected quotes.
	$(document).on('mouseup', '.inner, .list_posts', function() {

		// Get everything we need.
		var oSelected = {
			divID : $(this).attr('id'),
			msgID : $(this).data('msgid'),
		};

		// If the button is already visible, hide it!
		$('#quoteSelected_' + oSelected.divID).hide();

		// Get any selected text.
		oSelected.text = getSelectedText(oSelected.divID);

		// Do we have some selected text?
		if (typeof oSelected.text == 'undefined' || oSelected.text == false)
			return false;

		// Show the "quote this" button.
		$('#quoteSelected_' + oSelected.msgID).show();

			$(document).one('click', '#quoteSelected_' + oSelected.msgID + ' a', function(e){
				e.preventDefault();
				quotedTextClick(oSelected);
			});

		return false;
	});
});<commit_msg>Use msgID to hide the button<commit_after>function getSelectedText(divID)
{
	if (typeof divID == 'undefined' || divID == false)
		return false;

	var text = '',
	selection,
	found = 0;

	if (window.getSelection)
	{
		selection = window.getSelection();
		text = selection.toString();
	}
	else if (document.selection && document.selection.type != 'Control')
	{
		selection = document.selection.createRange();
		text = selection.text;
	}

	// Need to be sure the selected text does belong to the right div.
	for (var i = 0; i < selection.rangeCount; i++) {
			s = selection.getRangeAt(i).startContainer.parentNode.id;
			e = selection.getRangeAt(i).endContainer.parentNode.id;

			if (s == divID || (s != divID && e == 'child'))
			{
				found = 1;
				break;
			}
		}

	return found === 1 ? text : false;
}

function quotedTextClick(oOptions)
{
		text = '';

		// The process has been started, hide the button.
		$('#quoteSelected_' + oOptions.msgID).hide();

		// Do a call to make sure this is a valid message.
		$.ajax({
			url: smf_prepareScriptUrl(smf_scripturl) + 'action=quotefast;quote=' + oOptions.msgID + ';xml;pb='+ oEditorID + ';mode=' + (oEditorObject.bRichTextEnabled ? 1 : 0),
			type: 'GET',
			dataType: 'xml',
			beforeSend: function () {
				ajax_indicator(true);
			},
			success: function (data, textStatus, xhr) {
				// Search the xml data to get the quote tag.
				text = $(data).find('quote').text();

				// Insert the selected text between the quotes BBC tags.
				text = text.match(/^\[quote(.*)]/ig) + oOptions.text + '[/quote]' + '\n\n';

				// Add the whole text to the editor's instance.
				$('#' + oEditorID).data('sceditor').InsertText(text);

				// Move the view to the quick reply box. If available.
				if (typeof oJumpAnchor != 'undefined'){
					if (navigator.appName == 'Microsoft Internet Explorer')
						window.location.hash = oJumpAnchor;
					else
						window.location.hash = '#' + oJumpAnchor;
				}

				ajax_indicator(false);
			},
			error: function (xhr, textStatus, errorThrown) {
				ajax_indicator(false);
			}
		});
}

$(function() {

	// Event for handling selected quotes.
	$(document).on('mouseup', '.inner, .list_posts', function() {

		// Get everything we need.
		var oSelected = {
			divID : $(this).attr('id'),
			msgID : $(this).data('msgid'),
		};

		// If the button is already visible, hide it!
		$('#quoteSelected_' + oSelected.msgID).hide();

		// Get any selected text.
		oSelected.text = getSelectedText(oSelected.divID);

		// Do we have some selected text?
		if (typeof oSelected.text == 'undefined' || oSelected.text == false)
			return false;

		// Show the "quote this" button.
		$('#quoteSelected_' + oSelected.msgID).show();

			$(document).one('click', '#quoteSelected_' + oSelected.msgID + ' a', function(e){
				e.preventDefault();
				quotedTextClick(oSelected);
			});

		return false;
	});
});<|endoftext|>


<commit_before>Vps.onContentReady(function()
{
    var components = Ext.query('a.vpsLightbox');
    Ext.each(components, function(c) {
        var link = Ext.get(c);
        var settings = Ext.decode(link.child('.settings').getValue());
        if (!link.lightboxProcessed) {
            link.lightboxProcessed = true;
            link.on('click', function (ev) {
                ev.preventDefault();
                Vps.Lightbox.Lightbox.open(link.dom, '.' + settings.sel, settings);
            });

        }
    });
});

Ext.namespace("Vps.Lightbox");
Vps.Lightbox.Lightbox = (function(link, config) {
    var els = {},
        items = [],
        activeItem,
        extboxBorders = [],
        interfaceWidth,
        interfaceHeight,
        currentWidth = 250,
        currentHeight = 250,
        currentX,
        currentY,
        initialized = false,
        selectors = [],
        wrapper = false;

    return {
        version: '1.0',
        opts: {},
        defaults: {
            current: trlVps('Entry {current} of {total}'),
            previous: '←',
            next: '→',
            close: 'close',
            width: false,
            height: false,
            innerWidth: false,
            innerHeight: false,
            maxWidth: '90%',
            maxHeight: '90%',
            group: true,
            resizeDuration: 0.3,
            overlayOpacity: 0.1,
            overlayDuration: 0.2,
            hideInfo: false,
            easing: 'easeNone',
            title: false,
            navigate: true
        },

        // ************* open *****************
        open: function(item, sel, options) {
            Ext.apply(this.opts, options, this.defaults);
            this.setViewSize();
            els.overlay.fadeIn({
                duration: this.opts.overlayDuration,
                endOpacity: this.opts.overlayOpacity,
                callback: function() {
                    items = [];
                    var index = 0;
                    if(!this.opts.group) {
                        items.push(item);
                    } else {
                        items = Ext.query(sel);
                    }

                    // calculate top and left offset for the extbox
                    var pageScroll = Ext.fly(document).getScroll();
                    var extboxTop = (Ext.lib.Dom.getViewportHeight() - currentHeight) / 2 + pageScroll.top;
                    var extboxLeft = (Ext.lib.Dom.getViewportWidth() - currentWidth) / 2 + pageScroll.left;

                    els.extbox.setStyle({
                        top: extboxTop + 'px',
                        left: extboxLeft + 'px'
                    }).show();

                    this.loadItem(index);

                    // update controls
                    if (this.opts.navigate) {
                        els.navPrev.update(this.opts.previous);
                        els.navNext.update(this.opts.next);
                    }
                    els.navClose.update(this.opts.close);

                    // check info visibility
                    if (this.opts.hideInfo == 'auto') {
                        els.extbox.on('mouseenter', this.showInfo, this);
                        els.extbox.on('mouseleave', this.hideInfo, this);
                        els.info.hide();
                    } else if (this.opts.hideInfo === false) {
                        els.extbox.un('mouseenter', this.showInfo, this);
                        els.extbox.un('mouseleave', this.hideInfo, this);
                        els.info.show();
                    } else if (this.opts.hideInfo === true) {
                        els.extbox.un('mouseenter', this.showInfo, this);
                        els.extbox.un('mouseleave', this.hideInfo, this);
                        els.info.hide();
                    }

                    Ext.fly(window).on('resize', this.resizeWindow, this);
                    this.fireEvent('open', items[index]);
                },
                scope: this
            });
        },

        loadItem: function(index) {
            var timeout, loadContent = {};
            activeItem = index;
            this.disableKeyNav();
            els.loadingOverlay.show();
            els.loading.show();

            if (this.opts.inline) {
                isImg = false;
                currentX = false;
                currentY = false;
                var cnt = Ext.query(this.opts.href);
                loadContent = {
                    tag: 'div',
                    id: 'ux-extbox-loadedContent',
                    html: cnt[0].innerHTML,
                    style: {display: 'none'}
                };

                Ext.DomHelper.overwrite(els.content, loadContent);
                this.resize();
            } else {
                var url = items[activeItem].href;
                Ext.Ajax.request({
                    params: {url: url},
                    url: '/vps/util/render/render',
                    success: function(response, options, r) {
                        currentX = false;
                        currentY = false;
                        loadContent = {
                            tag: 'div',
                            id: 'ux-extbox-loadedContent',
                            html: response.responseText,
                            style: {display: 'none'}
                        };
                        Ext.DomHelper.overwrite(els.content, loadContent);
                        Vps.callOnContentReady();
                        this.resize();
                    },
                    scope: this
                });
            }

            // update Nav
            (function () {
                this.enableKeyNav();

                // if not first image in set, display prev image button
                if (activeItem < 1) {
                    els.navPrev.hide();
                } else {
                    els.navPrev.show();
                }

                // if not last image in set, display next image button
                if (activeItem >= (items.length - 1)) {
                    els.navNext.hide();
                } else {
                    els.navNext.show();
                }
                els.loadingOverlay.hide();
                els.loading.hide();
            }).defer(this.opts.resizeDuration * 1000, this);
        },

        // ************* init *****************
        init: function() {
            if(!initialized) {
                Ext.apply(this, Ext.util.Observable.prototype);
                Ext.util.Observable.constructor.call(this);
                this.addEvents('open', 'close');
                this.initMarkup();
                this.initEvents();
                initialized = true;
            }
        },

        initMarkup: function() {
            els.overlay = Ext.DomHelper.insertFirst(document.body, {
                id: 'ux-extbox-overlay'
            }, true);
            els.overlay.setVisibilityMode(Ext.Element.DISPLAY).hide();

            if (Ext.isIE6) {
                els.shim = Ext.DomHelper.insertFirst(document.body, {
                    tag: 'iframe',
                    id: 'ux-extbox-shim',
                    frameborder: 0
                }, true);
                els.shim.setVisibilityMode(Ext.Element.DISPLAY);
                els.shim.hide();
            }

            var extboxTpl = new Ext.Template(this.getTemplate());
            els.extbox = extboxTpl.insertAfter(els.overlay, {}, true);
            els.extbox.setVisibilityMode(Ext.Element.DISPLAY).hide();

            var ids = ['container', 'content', 'loadingOverlay', 'loading', 'navPrev', 'navNext', 'navClose', 'info', 'title', 'current'];
            Ext.each(ids, function(id){
                els[id] = Ext.get('ux-extbox-' + id);
            });
            extboxBorders = [(
                els.extbox.getPadding('t') +
                els.extbox.getBorderWidth('t')
            ), (
                els.extbox.getPadding('r') +
                els.extbox.getBorderWidth('r')
            ), (
                els.extbox.getPadding('b') +
                els.extbox.getBorderWidth('b')
            ), (
                els.extbox.getPadding('l') +
                els.extbox.getBorderWidth('l')
            )];
            interfaceWidth =
                els.container.getPadding('rl') +
                els.container.getBorderWidth('rl') +
                els.content.getPadding('rl') +
                els.content.getBorderWidth('rl') +
                parseInt(els.container.getStyle('margin-left'), 10) +
                parseInt(els.container.getStyle('margin-right'), 10);
            interfaceHeight =
                els.container.getPadding('tb') +
                els.container.getBorderWidth('tb') +
                els.content.getPadding('tb') +
                els.content.getBorderWidth('tb') +
                parseInt(els.container.getStyle('margin-top'), 10) +
                parseInt(els.container.getStyle('margin-bottom'), 10);

            els.extbox.setStyle({
                width: currentWidth + 'px',
                height: currentHeight + 'px'
            });
            if (wrapper) {
                els.wrapper = els.container.wrap({tag: 'div', id: 'ux-extbox-trc'})
                .wrap({tag: 'div', id: 'ux-extbox-tlc'})
                .wrap({tag: 'div', id: 'ux-extbox-tb'})
                .wrap({tag: 'div', id: 'ux-extbox-brc'})
                .wrap({tag: 'div', id: 'ux-extbox-blc'})
                .wrap({tag: 'div', id: 'ux-extbox-bb'})
                .wrap({tag: 'div', id: 'ux-extbox-rb'})
                .wrap({tag: 'div', id: 'ux-extbox-lb'});
            }
        },

        getTemplate : function() {
            return [
                '
',
                    '
',
                        '
',

                        '
',
                        '
',
                            '

',
                        '
',
                        '

',
                        '

',
                        '

',
                        '
',
                            '

',
                            '

',
                        '
',
                    '
',
                '
'
            ];
        },

        initEvents: function() {
            var close = function(ev) {
                ev.preventDefault();
                this.close();
            };

            els.overlay.on('click', close, this);
            els.navClose.on('click', close, this);

            els.extbox.on('click', function(ev) {
                if(ev.getTarget().id == 'ux-extbox') {
                    this.close();
                }
            }, this);

            els.navPrev.on('click', function(ev) {
                ev.preventDefault();
                this.loadItem(activeItem - 1);
            }, this);

            els.navNext.on('click', function(ev) {
                ev.preventDefault();
                this.loadItem(activeItem + 1);
            }, this);
        },

        // ************* helping methods *****************
        setViewSize: function() {
            var viewSize = [
                Math.max(Ext.lib.Dom.getViewWidth(), Ext.lib.Dom.getDocumentWidth()),
                Math.max(Ext.lib.Dom.getViewHeight(), Ext.lib.Dom.getDocumentHeight())
            ];
            if (Ext.isIE6) {
                els.shim.setStyle({
                    width: viewSize[0] + 'px',
                    height: viewSize[1] + 'px'
                }).setOpacity(0).show();
                els.overlay.setStyle({
                    width: viewSize[0] + 'px',
                    height: viewSize[1] + 'px',
                    position: 'absolute'
                });
            } else {
                els.overlay.setStyle({
                    width: viewSize[0] + 'px',
                    height: viewSize[1] + 'px'
                });
            }
        },

        resize: function (w, h) {
            var c, x, y, cx, cy, cl, ct, loadedContent;
            var viewSize = [Ext.lib.Dom.getViewWidth(), Ext.lib.Dom.getViewHeight()];
            var pageScroll = Ext.fly(document).getScroll();
            var maxW = this.setSize(this.opts.maxWidth, 'x') - extboxBorders[3] - extboxBorders[1] - interfaceWidth;
            var maxH = this.setSize(this.opts.maxWidth, 'y') - extboxBorders[0] - extboxBorders[2] - interfaceHeight;
            cx = w || this.opts.innerWidth;
            cy = h || this.opts.innerHeight;
            x = (cx) ?
                cx : (this.opts.width) ?
                    this.opts.width - extboxBorders[3] - extboxBorders[1] : maxW;
            y = (cy) ?
                cy : (this.opts.height) ?
                    this.opts.height - extboxBorders[0] - extboxBorders[2] : maxH;
            x = parseInt(x, 10);
            y = parseInt(y, 10);
            currentWidth = x + interfaceWidth + extboxBorders[1] + extboxBorders[3];
            currentHeight = y + interfaceHeight + extboxBorders[0] + extboxBorders[2];
            cl = ((viewSize[0] - x - extboxBorders[1] - extboxBorders[3] - interfaceWidth) / 2) + pageScroll.left;
            ct = ((viewSize[1] - y - extboxBorders[0] - extboxBorders[2] - interfaceHeight) / 2)  + pageScroll.top;
            cl = (cl > 0) ? cl : 0;
            ct = (ct > 0) ? ct : 0;
            Ext.Fx.syncFx();

            els.extbox.shift({
                width: currentWidth,
                height: currentHeight,
                left: cl,
                top: ct,
                easing: this.opts.easing,
                duration: this.opts.resizeDuration,
                scope: this
            });
            els.content.shift({
                width: x,
                height: y,
                easing: this.opts.easing,
                duration: this.opts.resizeDuration,
                scope: this,
                callback: function() {
                    // update details
                    var title = this.opts.title || items[activeItem].title
                    els.title.update(title);
                    //els.title.show();
                    if (items.length > 1) {
                        els.current.update(this.opts.current.replace(/\{current\}/, activeItem+1).replace(/\{total\}/, items.length));
                        //els.current.show();
                    } else {
                        els.current.update('');
                    }
                }
            });
            loadedContent = Ext.get('ux-extbox-loadedContent');
            if (loadedContent !== null && loadedContent.isVisible()) {
                loadedContent.shift({
                    width: x - 20,
                    height: y,
                    easing: this.opts.easing,
                    duration: this.opts.resizeDuration
                });
            } else {
                loadedContent.shift({
                    width: x - 20,
                    height: y,
                    easing: this.opts.easing,
                    duration: this.opts.resizeDuration
                }).fadeIn({duration: this.opts.resizeDuration/2});
            }
            Ext.Fx.sequenceFx();
        },

        resizeWindow: function() {
                    this.setViewSize();
                    this.resize(currentX, currentY);
        },

        showInfo: function() {
            els.info.stopFx().fadeIn({duration: this.opts.resizeDuration});
        },

        hideInfo: function() {
            els.info.stopFx().fadeOut({duration: this.opts.resizeDuration});
        },

        enableKeyNav: function() {
            Ext.fly(document).on('keydown', this.keyNavAction, this);
        },

        disableKeyNav: function() {
            Ext.fly(document).un('keydown', this.keyNavAction, this);
        },

        keyNavAction: function(ev) {
            var keyCode = ev.getKey();
            if (
                keyCode == 88 || // x
                keyCode == 67 || // c
                keyCode == 27
            ) {
                this.close();
            } else if (keyCode == 80 || keyCode == 37) { // display previous item
                if (activeItem != 0){
                    this.loadItem(activeItem - 1);
                }
            } else if (keyCode == 78 || keyCode == 39) { // display next item
                if (activeItem != (items.length - 1)) {
                    this.loadItem(activeItem + 1);
                }
            }
        },

        close: function() {
            this.disableKeyNav();
            els.extbox.hide();
            els.overlay.fadeOut({
                duration: this.opts.overlayDuration
            });
            if (Ext.isIE6) els.shim.hide();
            Ext.DomHelper.overwrite(els.content, '');
            Ext.DomHelper.overwrite(els.title, '');
            Ext.DomHelper.overwrite(els.current, '');
            Ext.fly(window).un('resize', this.resizeWindow, this);
            this.fireEvent('close', activeItem);
        },

        setSize: function (size, dimension) {
            dimension = dimension === 'x' ? Ext.lib.Dom.getViewWidth() : Ext.lib.Dom.getViewHeight();
            return (typeof size === 'string') ? Math.round((size.match(/%/) ? (dimension / 100) * parseInt(size, 10) : parseInt(size, 10))) : size;
        }
    }
})();
Ext.onReady(Vps.Lightbox.Lightbox.init, Vps.Lightbox.Lightbox);<commit_msg>Lightbox with ajax-rendering: bugfix, didn't find correct index<commit_after>Vps.onContentReady(function()
{
    var components = Ext.query('a.vpsLightbox');
    Ext.each(components, function(c) {
        var link = Ext.get(c);
        var settings = Ext.decode(link.child('.settings').getValue());
        if (!link.lightboxProcessed) {
            link.lightboxProcessed = true;
            link.on('click', function (ev) {
                ev.preventDefault();
                Vps.Lightbox.Lightbox.open(link.dom, '.' + settings.sel, settings);
            });

        }
    });
});

Ext.namespace("Vps.Lightbox");
Vps.Lightbox.Lightbox = (function(link, config) {
    var els = {},
        items = [],
        activeItem,
        extboxBorders = [],
        interfaceWidth,
        interfaceHeight,
        currentWidth = 250,
        currentHeight = 250,
        currentX,
        currentY,
        initialized = false,
        selectors = [],
        wrapper = false;

    return {
        version: '1.0',
        opts: {},
        defaults: {
            current: trlVps('Entry {current} of {total}'),
            previous: '←',
            next: '→',
            close: 'close',
            width: false,
            height: false,
            innerWidth: false,
            innerHeight: false,
            maxWidth: '90%',
            maxHeight: '90%',
            group: true,
            resizeDuration: 0.3,
            overlayOpacity: 0.1,
            overlayDuration: 0.2,
            hideInfo: false,
            easing: 'easeNone',
            title: false,
            navigate: true
        },

        // ************* open *****************
        open: function(item, sel, options) {
            Ext.apply(this.opts, options, this.defaults);
            this.setViewSize();
            els.overlay.fadeIn({
                duration: this.opts.overlayDuration,
                endOpacity: this.opts.overlayOpacity,
                callback: function() {
                    items = [];
                    var index = 0;
                    if(!this.opts.group) {
                        items.push(item);
                    } else {
                        items = Ext.query(sel);
                        while (items[index] && items[index].href != item.href) {
                            index++;
                        }
                    }

                    // calculate top and left offset for the extbox
                    var pageScroll = Ext.fly(document).getScroll();
                    var extboxTop = (Ext.lib.Dom.getViewportHeight() - currentHeight) / 2 + pageScroll.top;
                    var extboxLeft = (Ext.lib.Dom.getViewportWidth() - currentWidth) / 2 + pageScroll.left;

                    els.extbox.setStyle({
                        top: extboxTop + 'px',
                        left: extboxLeft + 'px'
                    }).show();

                    this.loadItem(index);

                    // update controls
                    if (this.opts.navigate) {
                        els.navPrev.update(this.opts.previous);
                        els.navNext.update(this.opts.next);
                    }
                    els.navClose.update(this.opts.close);

                    // check info visibility
                    if (this.opts.hideInfo == 'auto') {
                        els.extbox.on('mouseenter', this.showInfo, this);
                        els.extbox.on('mouseleave', this.hideInfo, this);
                        els.info.hide();
                    } else if (this.opts.hideInfo === false) {
                        els.extbox.un('mouseenter', this.showInfo, this);
                        els.extbox.un('mouseleave', this.hideInfo, this);
                        els.info.show();
                    } else if (this.opts.hideInfo === true) {
                        els.extbox.un('mouseenter', this.showInfo, this);
                        els.extbox.un('mouseleave', this.hideInfo, this);
                        els.info.hide();
                    }

                    Ext.fly(window).on('resize', this.resizeWindow, this);
                    this.fireEvent('open', items[index]);
                },
                scope: this
            });
        },

        loadItem: function(index) {
            var timeout, loadContent = {};
            activeItem = index;
            this.disableKeyNav();
            els.loadingOverlay.show();
            els.loading.show();

            if (this.opts.inline) {
                isImg = false;
                currentX = false;
                currentY = false;
                var cnt = Ext.query(this.opts.href);
                loadContent = {
                    tag: 'div',
                    id: 'ux-extbox-loadedContent',
                    html: cnt[0].innerHTML,
                    style: {display: 'none'}
                };

                Ext.DomHelper.overwrite(els.content, loadContent);
                this.resize();
            } else {
                var url = items[activeItem].href;
                Ext.Ajax.request({
                    params: {url: url},
                    url: '/vps/util/render/render',
                    success: function(response, options, r) {
                        currentX = false;
                        currentY = false;
                        loadContent = {
                            tag: 'div',
                            id: 'ux-extbox-loadedContent',
                            html: response.responseText,
                            style: {display: 'none'}
                        };
                        Ext.DomHelper.overwrite(els.content, loadContent);
                        Vps.callOnContentReady();
                        this.resize();
                    },
                    scope: this
                });
            }

            // update Nav
            (function () {
                this.enableKeyNav();

                // if not first image in set, display prev image button
                if (activeItem < 1) {
                    els.navPrev.hide();
                } else {
                    els.navPrev.show();
                }

                // if not last image in set, display next image button
                if (activeItem >= (items.length - 1)) {
                    els.navNext.hide();
                } else {
                    els.navNext.show();
                }
                els.loadingOverlay.hide();
                els.loading.hide();
            }).defer(this.opts.resizeDuration * 1000, this);
        },

        // ************* init *****************
        init: function() {
            if(!initialized) {
                Ext.apply(this, Ext.util.Observable.prototype);
                Ext.util.Observable.constructor.call(this);
                this.addEvents('open', 'close');
                this.initMarkup();
                this.initEvents();
                initialized = true;
            }
        },

        initMarkup: function() {
            els.overlay = Ext.DomHelper.insertFirst(document.body, {
                id: 'ux-extbox-overlay'
            }, true);
            els.overlay.setVisibilityMode(Ext.Element.DISPLAY).hide();

            if (Ext.isIE6) {
                els.shim = Ext.DomHelper.insertFirst(document.body, {
                    tag: 'iframe',
                    id: 'ux-extbox-shim',
                    frameborder: 0
                }, true);
                els.shim.setVisibilityMode(Ext.Element.DISPLAY);
                els.shim.hide();
            }

            var extboxTpl = new Ext.Template(this.getTemplate());
            els.extbox = extboxTpl.insertAfter(els.overlay, {}, true);
            els.extbox.setVisibilityMode(Ext.Element.DISPLAY).hide();

            var ids = ['container', 'content', 'loadingOverlay', 'loading', 'navPrev', 'navNext', 'navClose', 'info', 'title', 'current'];
            Ext.each(ids, function(id){
                els[id] = Ext.get('ux-extbox-' + id);
            });
            extboxBorders = [(
                els.extbox.getPadding('t') +
                els.extbox.getBorderWidth('t')
            ), (
                els.extbox.getPadding('r') +
                els.extbox.getBorderWidth('r')
            ), (
                els.extbox.getPadding('b') +
                els.extbox.getBorderWidth('b')
            ), (
                els.extbox.getPadding('l') +
                els.extbox.getBorderWidth('l')
            )];
            interfaceWidth =
                els.container.getPadding('rl') +
                els.container.getBorderWidth('rl') +
                els.content.getPadding('rl') +
                els.content.getBorderWidth('rl') +
                parseInt(els.container.getStyle('margin-left'), 10) +
                parseInt(els.container.getStyle('margin-right'), 10);
            interfaceHeight =
                els.container.getPadding('tb') +
                els.container.getBorderWidth('tb') +
                els.content.getPadding('tb') +
                els.content.getBorderWidth('tb') +
                parseInt(els.container.getStyle('margin-top'), 10) +
                parseInt(els.container.getStyle('margin-bottom'), 10);

            els.extbox.setStyle({
                width: currentWidth + 'px',
                height: currentHeight + 'px'
            });
            if (wrapper) {
                els.wrapper = els.container.wrap({tag: 'div', id: 'ux-extbox-trc'})
                .wrap({tag: 'div', id: 'ux-extbox-tlc'})
                .wrap({tag: 'div', id: 'ux-extbox-tb'})
                .wrap({tag: 'div', id: 'ux-extbox-brc'})
                .wrap({tag: 'div', id: 'ux-extbox-blc'})
                .wrap({tag: 'div', id: 'ux-extbox-bb'})
                .wrap({tag: 'div', id: 'ux-extbox-rb'})
                .wrap({tag: 'div', id: 'ux-extbox-lb'});
            }
        },

        getTemplate : function() {
            return [
                '
',
                    '
',
                        '
',

                        '
',
                        '
',
                            '

',
                        '
',
                        '

',
                        '

',
                        '

',
                        '
',
                            '

',
                            '

',
                        '
',
                    '
',
                '
'
            ];
        },

        initEvents: function() {
            var close = function(ev) {
                ev.preventDefault();
                this.close();
            };

            els.overlay.on('click', close, this);
            els.navClose.on('click', close, this);

            els.extbox.on('click', function(ev) {
                if(ev.getTarget().id == 'ux-extbox') {
                    this.close();
                }
            }, this);

            els.navPrev.on('click', function(ev) {
                ev.preventDefault();
                this.loadItem(activeItem - 1);
            }, this);

            els.navNext.on('click', function(ev) {
                ev.preventDefault();
                this.loadItem(activeItem + 1);
            }, this);
        },

        // ************* helping methods *****************
        setViewSize: function() {
            var viewSize = [
                Math.max(Ext.lib.Dom.getViewWidth(), Ext.lib.Dom.getDocumentWidth()),
                Math.max(Ext.lib.Dom.getViewHeight(), Ext.lib.Dom.getDocumentHeight())
            ];
            if (Ext.isIE6) {
                els.shim.setStyle({
                    width: viewSize[0] + 'px',
                    height: viewSize[1] + 'px'
                }).setOpacity(0).show();
                els.overlay.setStyle({
                    width: viewSize[0] + 'px',
                    height: viewSize[1] + 'px',
                    position: 'absolute'
                });
            } else {
                els.overlay.setStyle({
                    width: viewSize[0] + 'px',
                    height: viewSize[1] + 'px'
                });
            }
        },

        resize: function (w, h) {
            var c, x, y, cx, cy, cl, ct, loadedContent;
            var viewSize = [Ext.lib.Dom.getViewWidth(), Ext.lib.Dom.getViewHeight()];
            var pageScroll = Ext.fly(document).getScroll();
            var maxW = this.setSize(this.opts.maxWidth, 'x') - extboxBorders[3] - extboxBorders[1] - interfaceWidth;
            var maxH = this.setSize(this.opts.maxWidth, 'y') - extboxBorders[0] - extboxBorders[2] - interfaceHeight;
            cx = w || this.opts.innerWidth;
            cy = h || this.opts.innerHeight;
            x = (cx) ?
                cx : (this.opts.width) ?
                    this.opts.width - extboxBorders[3] - extboxBorders[1] : maxW;
            y = (cy) ?
                cy : (this.opts.height) ?
                    this.opts.height - extboxBorders[0] - extboxBorders[2] : maxH;
            x = parseInt(x, 10);
            y = parseInt(y, 10);
            currentWidth = x + interfaceWidth + extboxBorders[1] + extboxBorders[3];
            currentHeight = y + interfaceHeight + extboxBorders[0] + extboxBorders[2];
            cl = ((viewSize[0] - x - extboxBorders[1] - extboxBorders[3] - interfaceWidth) / 2) + pageScroll.left;
            ct = ((viewSize[1] - y - extboxBorders[0] - extboxBorders[2] - interfaceHeight) / 2)  + pageScroll.top;
            cl = (cl > 0) ? cl : 0;
            ct = (ct > 0) ? ct : 0;
            Ext.Fx.syncFx();

            els.extbox.shift({
                width: currentWidth,
                height: currentHeight,
                left: cl,
                top: ct,
                easing: this.opts.easing,
                duration: this.opts.resizeDuration,
                scope: this
            });
            els.content.shift({
                width: x,
                height: y,
                easing: this.opts.easing,
                duration: this.opts.resizeDuration,
                scope: this,
                callback: function() {
                    // update details
                    var title = this.opts.title || items[activeItem].title
                    els.title.update(title);
                    //els.title.show();
                    if (items.length > 1) {
                        els.current.update(this.opts.current.replace(/\{current\}/, activeItem+1).replace(/\{total\}/, items.length));
                        //els.current.show();
                    } else {
                        els.current.update('');
                    }
                }
            });
            loadedContent = Ext.get('ux-extbox-loadedContent');
            if (loadedContent !== null && loadedContent.isVisible()) {
                loadedContent.shift({
                    width: x - 20,
                    height: y,
                    easing: this.opts.easing,
                    duration: this.opts.resizeDuration
                });
            } else {
                loadedContent.shift({
                    width: x - 20,
                    height: y,
                    easing: this.opts.easing,
                    duration: this.opts.resizeDuration
                }).fadeIn({duration: this.opts.resizeDuration/2});
            }
            Ext.Fx.sequenceFx();
        },

        resizeWindow: function() {
                    this.setViewSize();
                    this.resize(currentX, currentY);
        },

        showInfo: function() {
            els.info.stopFx().fadeIn({duration: this.opts.resizeDuration});
        },

        hideInfo: function() {
            els.info.stopFx().fadeOut({duration: this.opts.resizeDuration});
        },

        enableKeyNav: function() {
            Ext.fly(document).on('keydown', this.keyNavAction, this);
        },

        disableKeyNav: function() {
            Ext.fly(document).un('keydown', this.keyNavAction, this);
        },

        keyNavAction: function(ev) {
            var keyCode = ev.getKey();
            if (
                keyCode == 88 || // x
                keyCode == 67 || // c
                keyCode == 27
            ) {
                this.close();
            } else if (keyCode == 80 || keyCode == 37) { // display previous item
                if (activeItem != 0){
                    this.loadItem(activeItem - 1);
                }
            } else if (keyCode == 78 || keyCode == 39) { // display next item
                if (activeItem != (items.length - 1)) {
                    this.loadItem(activeItem + 1);
                }
            }
        },

        close: function() {
            this.disableKeyNav();
            els.extbox.hide();
            els.overlay.fadeOut({
                duration: this.opts.overlayDuration
            });
            if (Ext.isIE6) els.shim.hide();
            Ext.DomHelper.overwrite(els.content, '');
            Ext.DomHelper.overwrite(els.title, '');
            Ext.DomHelper.overwrite(els.current, '');
            Ext.fly(window).un('resize', this.resizeWindow, this);
            this.fireEvent('close', activeItem);
        },

        setSize: function (size, dimension) {
            dimension = dimension === 'x' ? Ext.lib.Dom.getViewWidth() : Ext.lib.Dom.getViewHeight();
            return (typeof size === 'string') ? Math.round((size.match(/%/) ? (dimension / 100) * parseInt(size, 10) : parseInt(size, 10))) : size;
        }
    }
})();
Ext.onReady(Vps.Lightbox.Lightbox.init, Vps.Lightbox.Lightbox);<|endoftext|>


<commit_before>import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';

import { Link } from 'react-router-dom';

import Header from '../Header';
import { months } from '../../constants';

import './styles.css';

const DEFAULT_ENTRY_HEIGHT = 400;
export class EntryForm extends Component {
  state = {
    text: this.props.entry,
    height: DEFAULT_ENTRY_HEIGHT,
  };

  notebook = null;

  updateHeight = () => {
    if (this.notebook !== null) {
      const content = this.notebook.querySelector('.content');

      if (content.scrollHeight !== this.state.height) {
        this.setState({ height: content.scrollHeight });
      }
    }
  };

  onChangeText = ({ currentTarget: { value } }) =>
    this.setState({ text: value });

  onSubmit = e => {
    if (e !== undefined) {
      e.preventDefault();
    }
    this.props.saveEntry(this.state.text);
  };

  onReset = e => {
    e.preventDefault();
    this.setState({ text: this.props.entry });
  };

  render() {
    const { nameOfUser } = this.props;

    return (
      <form onSubmit={this.onSubmit} onReset={this.onReset}>

{nameOfUser}

        <div
          className="notebook"
          style={{ height: `${this.state.height}px` }}
          ref={notebook => {
            this.notebook = notebook;
            this.updateHeight();
          }}
        >
          <div
            aria-hidden
            className="back"
            style={{ height: `${this.state.height}px` }}
          />
          <textarea
            className="content"
            title="Diary entry"
            value={this.state.text}
            onChange={this.onChangeText}
            style={{ height: `${this.state.height}px` }}
            onKeyDown={({ key, ctrlKey }) =>
              key === 'Enter' && ctrlKey && this.onSubmit()
            }
          />

          {this.props.entry === this.state.text ? (

Saved

          ) : (
            <Fragment>
              <input type="reset" value="Discard changes" />
              <input type="submit" value="Save" />
            </Fragment>
          )}

      </form>
    );
  }
}

EntryForm.propTypes = {
  nameOfUser: PropTypes.string.isRequired,
  saveEntry: PropTypes.func.isRequired,
  entry: PropTypes.string.isRequired,
};

export class EntryBody extends Component {
  state = {
    height: DEFAULT_ENTRY_HEIGHT,
  };

  notebook = null;

  updateHeight = () => {
    if (this.notebook !== null) {
      const content = this.notebook.querySelector('.content');

      const heightOfContentExcludingPadding = content.scrollHeight;

      if (heightOfContentExcludingPadding !== this.state.height) {
        const heightOfContentWithPadding = heightOfContentExcludingPadding + 15;
        this.setState({ height: heightOfContentWithPadding });
      }
    }
  };

  render() {
    const { nameOfUser } = this.props;

    return (
      <Fragment>

{nameOfUser}

        <div
          className="notebook"
          style={{ height: `${this.state.height}px` }}
          ref={notebook => {
            this.notebook = notebook;
            this.updateHeight();
          }}
        >
          <div
            aria-hidden
            className="back"
            style={{ height: `${this.state.height}px` }}
          />
          <p className="content" style={{ height: `${this.state.height}px` }}>
            {this.props.entry}

      </Fragment>
    );
  }
}

EntryBody.propTypes = {
  nameOfUser: PropTypes.string.isRequired,
  entry: PropTypes.string.isRequired,
};

const Entry = ({ year, month, date, body, nameOfPartner }) => (
  <Fragment>

        <Link to={`/calendar/${year}/${month}`} title="Back to calendar">
          <svg
            aria-hidden
            role="img"
            xmlns="http://www.w3.org/2000/svg"
            viewBox="0 0 448 512"
            width="25"
            height="25"
          >
            <path
              fill="currentColor"
              d="M148 288h-40c-6.6 0-12-5.4-12-12v-40c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v40c0 6.6-5.4 12-12 12zm108-12v-40c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12zm96 0v-40c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12zm-96 96v-40c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12zm-96 0v-40c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12zm192 0v-40c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12zm96-260v352c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V112c0-26.5 21.5-48 48-48h48V12c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v52h128V12c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v52h48c26.5 0 48 21.5 48 48zm-48 346V160H48v298c0 3.3 2.7 6 6 6h340c3.3 0 6-2.7 6-6z"
            />
          </svg>
        </Link>

{year}

{`${months[month - 1]} ${date}`}

        <Link
          to={`/entry/${year}/${month}/${date}/${nameOfPartner}`}
          title="Read partner's entry"
        >
          Read other
        </Link>

      {body}

  </Fragment>
);

Entry.propTypes = {
  year: PropTypes.number.isRequired,
  month: PropTypes.number.isRequired,
  date: PropTypes.number.isRequired,
  body: PropTypes.node.isRequired,
  nameOfPartner: PropTypes.string.isRequired,
};

export default Entry;
<commit_msg>Auto focus textarea in Entry<commit_after>import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';

import { Link } from 'react-router-dom';

import Header from '../Header';
import { months } from '../../constants';

import './styles.css';

const DEFAULT_ENTRY_HEIGHT = 400;
export class EntryForm extends Component {
  state = {
    text: this.props.entry,
    height: DEFAULT_ENTRY_HEIGHT,
  };

  notebook = null;

  componentDidMount() {
    const textarea = this.getTextarea();
    if (textarea !== null) {
      textarea.focus();
    }
  }

  getTextarea() {
    if (this.notebook == null) {
      return null;
    }

    return this.notebook.querySelector('.content');
  }

  updateHeight = () => {
    if (this.notebook !== null) {
      const textarea = this.getTextarea();

      if (textarea.scrollHeight !== this.state.height) {
        this.setState({ height: textarea.scrollHeight });
      }
    }
  };

  onChangeText = ({ currentTarget: { value } }) =>
    this.setState({ text: value });

  onSubmit = e => {
    if (e !== undefined) {
      e.preventDefault();
    }
    this.props.saveEntry(this.state.text);
  };

  onReset = e => {
    e.preventDefault();
    this.setState({ text: this.props.entry });
  };

  render() {
    const { nameOfUser } = this.props;

    return (
      <form onSubmit={this.onSubmit} onReset={this.onReset}>

{nameOfUser}

        <div
          className="notebook"
          style={{ height: `${this.state.height}px` }}
          ref={notebook => {
            this.notebook = notebook;
            this.updateHeight();
          }}
        >
          <div
            aria-hidden
            className="back"
            style={{ height: `${this.state.height}px` }}
          />
          <textarea
            className="content"
            title="Diary entry"
            value={this.state.text}
            onChange={this.onChangeText}
            style={{ height: `${this.state.height}px` }}
            onKeyDown={({ key, ctrlKey }) =>
              key === 'Enter' && ctrlKey && this.onSubmit()
            }
          />

          {this.props.entry === this.state.text ? (

Saved

          ) : (
            <Fragment>
              <input type="reset" value="Discard changes" />
              <input type="submit" value="Save" />
            </Fragment>
          )}

      </form>
    );
  }
}

EntryForm.propTypes = {
  nameOfUser: PropTypes.string.isRequired,
  saveEntry: PropTypes.func.isRequired,
  entry: PropTypes.string.isRequired,
};

export class EntryBody extends Component {
  state = {
    height: DEFAULT_ENTRY_HEIGHT,
  };

  notebook = null;

  updateHeight = () => {
    if (this.notebook !== null) {
      const content = this.notebook.querySelector('.content');

      const heightOfContentExcludingPadding = content.scrollHeight;

      if (heightOfContentExcludingPadding !== this.state.height) {
        const heightOfContentWithPadding = heightOfContentExcludingPadding + 15;
        this.setState({ height: heightOfContentWithPadding });
      }
    }
  };

  render() {
    const { nameOfUser } = this.props;

    return (
      <Fragment>

{nameOfUser}

        <div
          className="notebook"
          style={{ height: `${this.state.height}px` }}
          ref={notebook => {
            this.notebook = notebook;
            this.updateHeight();
          }}
        >
          <div
            aria-hidden
            className="back"
            style={{ height: `${this.state.height}px` }}
          />
          <p className="content" style={{ height: `${this.state.height}px` }}>
            {this.props.entry}

      </Fragment>
    );
  }
}

EntryBody.propTypes = {
  nameOfUser: PropTypes.string.isRequired,
  entry: PropTypes.string.isRequired,
};

const Entry = ({ year, month, date, body, nameOfPartner }) => (
  <Fragment>

        <Link to={`/calendar/${year}/${month}`} title="Back to calendar">
          <svg
            aria-hidden
            role="img"
            xmlns="http://www.w3.org/2000/svg"
            viewBox="0 0 448 512"
            width="25"
            height="25"
          >
            <path
              fill="currentColor"
              d="M148 288h-40c-6.6 0-12-5.4-12-12v-40c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v40c0 6.6-5.4 12-12 12zm108-12v-40c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12zm96 0v-40c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12zm-96 96v-40c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12zm-96 0v-40c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12zm192 0v-40c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12zm96-260v352c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V112c0-26.5 21.5-48 48-48h48V12c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v52h128V12c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v52h48c26.5 0 48 21.5 48 48zm-48 346V160H48v298c0 3.3 2.7 6 6 6h340c3.3 0 6-2.7 6-6z"
            />
          </svg>
        </Link>

{year}

{`${months[month - 1]} ${date}`}

        <Link
          to={`/entry/${year}/${month}/${date}/${nameOfPartner}`}
          title="Read partner's entry"
        >
          Read other
        </Link>

      {body}

  </Fragment>
);

Entry.propTypes = {
  year: PropTypes.number.isRequired,
  month: PropTypes.number.isRequired,
  date: PropTypes.number.isRequired,
  body: PropTypes.node.isRequired,
  nameOfPartner: PropTypes.string.isRequired,
};

export default Entry;
<|endoftext|>


<commit_before>'use strict';

const list = {};
const single = {};

module.exports = traverseNode => {
	const descendProperties = properties => (node, parent, ast, visitor) => {
		const propertiesNames = Object.keys(properties);

		if (propertiesNames.every(name => !node[name])) {
			return node;
		}

		const traverse = traverseNode(node, ast, visitor);
		const traversedProperties = propertiesNames.map(name => {
			try {
				const kind = properties[name];
				const value = kind === list ?
					((node[name] && node[name].map(traverse)) || []) :
					traverse(node[name]);
				return {[name]: value};
			} catch (err) {
				throw new Error(
					`While traversing property ${name}:${err.message}`
				);
			}
		});

		return Object.assign({}, node, ...traversedProperties);
	};

	return {
		defaultMethod(node) {
			return node;
		},

		AssignmentWord: descendProperties({expansion: list}),

		Script: descendProperties({commands: list}),

		CompoundList: descendProperties({commands: list}),

		Word: descendProperties({expansion: list}),

		Function: descendProperties({
			body: single,
			redirections: list
		}),

		Redirect: descendProperties({file: single}),

		LogicalExpression: descendProperties({
			left: single,
			right: single
		}),

		Case: descendProperties({
			cases: list,
			clause: single
		}),

		CaseItem: descendProperties({
			pattern: list,
			body: single
		}),

		If: descendProperties({
			clause: single,
			else: single,
			then: single
		}),

		While: descendProperties({
			clause: single,
			do: single
		}),

		Until: descendProperties({
			clause: single,
			do: single
		}),

		Command: descendProperties({
			name: single,
			prefix: list,
			suffix: list
		}),

		For: descendProperties({
			wordlist: list,
			do: single
		}),

		Pipeline: descendProperties({
			commands: list
		}),

		Subshell: descendProperties({
			list: single
		})
	};
};
<commit_msg>Use node v4 syntax<commit_after>'use strict';

const list = {};
const single = {};

module.exports = traverseNode => {
	const descendProperties = properties => (node, parent, ast, visitor) => {
		const propertiesNames = Object.keys(properties);

		if (propertiesNames.every(name => !node[name])) {
			return node;
		}

		const traverse = traverseNode(node, ast, visitor);
		const traversedProperties = propertiesNames.map(name => {
			try {
				const kind = properties[name];
				const value = kind === list ?
					((node[name] && node[name].map(traverse)) || []) :
					traverse(node[name]);
				return {[name]: value};
			} catch (err) {
				throw new Error(
					`While traversing property ${name}:${err.message}`
				);
			}
		});

		return Object.assign.apply(null, [{}, node].concat(traversedProperties));
	};

	return {
		defaultMethod(node) {
			return node;
		},

		AssignmentWord: descendProperties({expansion: list}),

		Script: descendProperties({commands: list}),

		CompoundList: descendProperties({commands: list}),

		Word: descendProperties({expansion: list}),

		Function: descendProperties({
			body: single,
			redirections: list
		}),

		Redirect: descendProperties({file: single}),

		LogicalExpression: descendProperties({
			left: single,
			right: single
		}),

		Case: descendProperties({
			cases: list,
			clause: single
		}),

		CaseItem: descendProperties({
			pattern: list,
			body: single
		}),

		If: descendProperties({
			clause: single,
			else: single,
			then: single
		}),

		While: descendProperties({
			clause: single,
			do: single
		}),

		Until: descendProperties({
			clause: single,
			do: single
		}),

		Command: descendProperties({
			name: single,
			prefix: list,
			suffix: list
		}),

		For: descendProperties({
			wordlist: list,
			do: single
		}),

		Pipeline: descendProperties({
			commands: list
		}),

		Subshell: descendProperties({
			list: single
		})
	};
};
<|endoftext|>


<commit_before>'use strict';
var isSymbol = require('../internals/is-symbol');
var getOwnPropertyNames = require('../internals/object-get-own-property-names');
var classof = require('../internals/classof');
var getBuiltin = require('../internals/get-built-in');
var isObject = require('../internals/is-object');
var has = require('../internals/has');
var isArrayBufferDetached = require('../internals/array-buffer-is-deatched');

var Set = getBuiltin('Set');
var Map = getBuiltin('Map');
var DataView = getBuiltin('DataView');

function createDataCloneError(message) {
  if (typeof DOMException === 'function') {
    return new DOMException(message, 'DataCloneError');
  }
  return new Error(message);
}

/**
 * Tries best to replicate structuredClone behaviour.
 *
 * @param {Map<object, object>} map cache map
 * @param {any} value object to clone
 */
module.exports = function structuredCloneInternal(map, value) {
  if (isSymbol(value)) throw createDataCloneError('Symbols are not cloneable');
  if (!isObject(value)) return value;
  if (map.has(value)) return map.get(value); // effectively preserves circular references

  var cloned, i, deep;
  var type = classof(value);

  switch (type) {
    case 'Boolean':
    case 'BigInt':
    case 'Number':
    case 'String':
      cloned = Object(value.valueOf());
      break;
    case 'Date':
      cloned = new Date(value.valueOf());
      break;
    case 'RegExp':
      cloned = new RegExp(value);
      break;
    case 'ArrayBuffer':
      if (!isArrayBufferDetached()) throw createDataCloneError('ArrayBuffer is deatched');
      // falls through
    case 'SharedArrayBuffer':
    case 'Blob':
      cloned = value.slice(0);
      break;
    case 'DataView':
      // this is safe, since arraybuffer cannot have circular references
      cloned = new DataView(structuredCloneInternal(value.buffer), value.byteOffset, value.byteLength);
      break;
    case 'Int8Array':
    case 'Uint8Array':
    case 'Uint8ClampedArray':
    case 'Int16Array':
    case 'Uint16Array':
    case 'Int32Array':
    case 'Uint32Array':
    case 'Float32Array':
    case 'Float64Array':
    case 'BigInt64Array':
    case 'BigUint64Array':
      // TypedArray
      cloned = new value.constructor(value);
      break;
    case 'Map':
      cloned = new Map();
      deep = true;
      break;
    case 'Set':
      cloned = new Set();
      deep = true;
      break;
    case 'Error':
      cloned = value.constructor(value.message.toString());
      deep = true; // clone stack after storing in the weakmap
      break;
    case 'Array':
      cloned = [];
      deep = true;
      break;
    case 'Object':
      cloned = {};
      deep = true;
      break;
    default:
      throw createDataCloneError('Uncloneable type: ' + type);
  }

  map.set(value, cloned);

  if (deep) switch (type) {
    case 'Map':
      value.forEach(function (v, k) {
        cloned.set(structuredCloneInternal(map, k), structuredCloneInternal(map, v));
      });
      break;
    case 'Set':
      value.forEach(function (v) {
        cloned.add(structuredCloneInternal(map, v));
      });
      break;
    case 'Error':
      // Attempt to clone the stack.
      if (
        !has(value, 'stack') && // Chrome, Safari
        !has(Error.prototype, 'stack') // Firefox
      ) break;
      try {
        cloned.stack = structuredCloneInternal(map, value.stack);
      } catch (_) {
        return cloned; // Stack cloning not avaliable.
      }
      break;
    case 'Array':
    case 'Object':
      var properties = getOwnPropertyNames.f(value);
      for (i = 0; i < properties.length; i++) {
        cloned[properties[i]] = structuredCloneInternal(map, value[properties[i]]);
      }
      break;
  }

  return cloned;
};
<commit_msg>rename has<commit_after>'use strict';
var isSymbol = require('../internals/is-symbol');
var getOwnPropertyNames = require('../internals/object-get-own-property-names');
var classof = require('../internals/classof');
var getBuiltin = require('../internals/get-built-in');
var isObject = require('../internals/is-object');
var hasOwn = require('../internals/has-own-property');
var isArrayBufferDetached = require('../internals/array-buffer-is-deatched');

var Set = getBuiltin('Set');
var Map = getBuiltin('Map');
var DataView = getBuiltin('DataView');

function createDataCloneError(message) {
  if (typeof DOMException === 'function') {
    return new DOMException(message, 'DataCloneError');
  }
  return new Error(message);
}

/**
 * Tries best to replicate structuredClone behaviour.
 *
 * @param {Map<object, object>} map cache map
 * @param {any} value object to clone
 */
module.exports = function structuredCloneInternal(map, value) {
  if (isSymbol(value)) throw createDataCloneError('Symbols are not cloneable');
  if (!isObject(value)) return value;
  if (map.has(value)) return map.get(value); // effectively preserves circular references

  var cloned, i, deep;
  var type = classof(value);

  switch (type) {
    case 'Boolean':
    case 'BigInt':
    case 'Number':
    case 'String':
      cloned = Object(value.valueOf());
      break;
    case 'Date':
      cloned = new Date(value.valueOf());
      break;
    case 'RegExp':
      cloned = new RegExp(value);
      break;
    case 'ArrayBuffer':
      if (!isArrayBufferDetached()) throw createDataCloneError('ArrayBuffer is deatched');
      // falls through
    case 'SharedArrayBuffer':
    case 'Blob':
      cloned = value.slice(0);
      break;
    case 'DataView':
      // this is safe, since arraybuffer cannot have circular references
      cloned = new DataView(structuredCloneInternal(value.buffer), value.byteOffset, value.byteLength);
      break;
    case 'Int8Array':
    case 'Uint8Array':
    case 'Uint8ClampedArray':
    case 'Int16Array':
    case 'Uint16Array':
    case 'Int32Array':
    case 'Uint32Array':
    case 'Float32Array':
    case 'Float64Array':
    case 'BigInt64Array':
    case 'BigUint64Array':
      // TypedArray
      cloned = new value.constructor(value);
      break;
    case 'Map':
      cloned = new Map();
      deep = true;
      break;
    case 'Set':
      cloned = new Set();
      deep = true;
      break;
    case 'Error':
      cloned = value.constructor(value.message.toString());
      deep = true; // clone stack after storing in the weakmap
      break;
    case 'Array':
      cloned = [];
      deep = true;
      break;
    case 'Object':
      cloned = {};
      deep = true;
      break;
    default:
      throw createDataCloneError('Uncloneable type: ' + type);
  }

  map.set(value, cloned);

  if (deep) switch (type) {
    case 'Map':
      value.forEach(function (v, k) {
        cloned.set(structuredCloneInternal(map, k), structuredCloneInternal(map, v));
      });
      break;
    case 'Set':
      value.forEach(function (v) {
        cloned.add(structuredCloneInternal(map, v));
      });
      break;
    case 'Error':
      // Attempt to clone the stack.
      if (
        !hasOwn(value, 'stack') && // Chrome, Safari
        !hasOwn(Error.prototype, 'stack') // Firefox
      ) break;
      try {
        cloned.stack = structuredCloneInternal(map, value.stack);
      } catch (_) {
        return cloned; // Stack cloning not avaliable.
      }
      break;
    case 'Array':
    case 'Object':
      var properties = getOwnPropertyNames.f(value);
      for (i = 0; i < properties.length; i++) {
        cloned[properties[i]] = structuredCloneInternal(map, value[properties[i]]);
      }
      break;
  }

  return cloned;
};
<|endoftext|>


<commit_before>import {SlateInput} from '@sanity/form-builder'

export default [
  {
    name: 'lowLevelBlocksTest',
    title: 'Blocks test',
    type: 'object',
    fields: [
      {
        name: 'title',
        title: 'Title',
        type: 'string',
      },
      {
        name: 'content',
        title: 'Content',
        type: 'array',
        inputComponent: SlateInput,
        of: [
          {
            type: 'block',
            title: 'Block'
          },
          {
            type: 'address',
            title: 'Address'
          },
          {
            title: 'Image',
            type: 'image',
            preview: {
              select: {
                imageUrl: 'asset.url',
                title: 'caption'
              }
            },
            fields: [
              {name: 'caption', type: 'string', title: 'Caption', options: {isHighlighted: true}}
            ]
          }
        ],
      }
    ]
  },
  {
    name: 'address',
    type: 'object',
    preview: {
      select: {
        title: 'street',
        subtitle: 'zip'
      }
    },
    fields: [
      {
        name: 'street',
        type: 'string',
        title: 'Street'
      },
      {
        name: 'zip',
        type: 'string',
        title: 'Zip'
      }
    ]
  },
  {
    name: 'block',
    type: 'object',
    preview: {
      select: {
        style: 'style',
        spans: 'spans'
      },
      prepare({style, spans}) {
        return {
          title: `${style ? `${style}: ` : ''} ${(spans || []).map(span => span.text).join(' ')}`
        }
      }
    },
    fields: [
      {
        name: 'style',
        title: 'Style',
        type: 'string',
        options: {
          list: [
            {title: 'Normal', value: 'normal'},
            {title: 'H1', value: 'h1'},
            {title: 'H2', value: 'h2'},
            {title: 'H3', value: 'h3'},
            {title: 'H4', value: 'h4'},
            {title: 'H5', value: 'h5'},
            {title: 'H6', value: 'h6'},
            {title: 'Quote', value: 'blockquote'}
          ]
        }
      },
      {
        name: 'list',
        title: 'List type',
        type: 'string',
        options: {
          list: [
            {title: 'None', value: ''},
            {title: 'Bullet', value: 'bullet'},
            {title: 'Numbered', value: 'number'}
          ]
        }
      },
      {
        name: 'indentation',
        title: 'Indentation',
        type: 'number'
      },
      {
        name: 'spans',
        type: 'array',
        title: 'Content',
        of: [
          {
            type: 'span',
            title: 'Span'
          },
          {
            type: 'image',
            title: 'Image'
          }
        ]
      }
    ]
  },
  {
    name: 'span',
    type: 'object',
    fields: [
      {
        type: 'text',
        name: 'text',
        title: 'Text'
      },
      {
        type: 'object',
        name: 'link',
        title: 'Link',
        fields: [
          {
            type: 'url',
            name: 'href',
          }
        ]
      },
      {
        name: 'author',
        title: 'Author',
        type: 'reference',
        to: [{type: 'author'}]
      },
      {
        type: 'array',
        name: 'marks',
        title: 'Marks',
        of: [{type: 'string'}],
        options: {
          direction: 'vertical',
          list: [
            {title: 'Strong', value: 'strong'},
            {title: 'Emphasis', value: 'em'},
            {title: 'Code', value: 'code'},
            {title: 'Strike', value: 'strike-through'}
          ]
        }
      }
    ]
  },
  {
    name: 'mark',
    type: 'object',
    title: 'Mark',
    fields: [
      {
        name: 'type',
        title: 'Type',
        type: 'string'
      }
    ]
  },
  {
    name: 'link',
    type: 'object',
    fields: [
      {
        type: 'url',
        name: 'href',
        title: 'Url'
      },
      {
        name: 'keys',
        title: 'Keys',
        type: 'array',
        of: [
          {
            type: 'string',
            title: 'Key'
          }
        ]
      }
    ]
  }
]
<commit_msg>[example-studio] Update schema<commit_after>import {SlateInput} from '@sanity/form-builder'

export default [
  {
    name: 'lowLevelBlocksTest',
    title: 'Blocks test',
    type: 'object',
    fields: [
      {
        name: 'title',
        title: 'Title',
        type: 'string',
      },
      {
        name: 'content',
        title: 'Content',
        type: 'array',
        inputComponent: SlateInput,
        of: [
          {
            type: 'block',
            title: 'Block'
          },
          {
            type: 'address',
            title: 'Address'
          },
          {
            title: 'Image',
            type: 'image',
            preview: {
              select: {
                imageUrl: 'asset.url',
                title: 'caption'
              }
            },
            fields: [
              {name: 'caption', type: 'string', title: 'Caption', options: {isHighlighted: true}}
            ]
          }
        ],
      }
    ]
  },
  {
    name: 'address',
    type: 'object',
    preview: {
      select: {
        title: 'street',
        subtitle: 'zip'
      }
    },
    fields: [
      {
        name: 'street',
        type: 'string',
        title: 'Street'
      },
      {
        name: 'zip',
        type: 'string',
        title: 'Zip'
      }
    ]
  },
  {
    name: 'block',
    type: 'object',
    preview: {
      select: {
        style: 'style',
        spans: 'spans'
      },
      prepare({style, spans}) {
        return {
          title: `${style ? `${style}: ` : ''} ${(spans || []).map(span => span.text).join(' ')}`
        }
      }
    },
    fields: [
      {
        name: 'style',
        title: 'Style',
        type: 'string',
        options: {
          list: [
            {title: 'Normal', value: 'normal'},
            {title: 'H1', value: 'h1'},
            {title: 'H2', value: 'h2'},
            {title: 'H3', value: 'h3'},
            {title: 'H4', value: 'h4'},
            {title: 'H5', value: 'h5'},
            {title: 'H6', value: 'h6'},
            {title: 'Quote', value: 'blockquote'}
          ]
        }
      },
      {
        name: 'list',
        title: 'List type',
        type: 'string',
        options: {
          list: [
            {title: 'None', value: ''},
            {title: 'Bullet', value: 'bullet'},
            {title: 'Numbered', value: 'number'}
          ]
        }
      },
      {
        name: 'indentation',
        title: 'Indentation',
        type: 'number'
      },
      {
        name: 'spans',
        type: 'array',
        title: 'Content',
        of: [
          {
            type: 'span',
            title: 'Span'
          },
          {
            type: 'image',
            title: 'Image'
          }
        ]
      }
    ]
  },
  {
    name: 'span',
    type: 'object',
    fields: [
      {
        type: 'text',
        name: 'text',
        title: 'Text'
      },
      {
        type: 'object',
        name: 'link',
        title: 'Link',
        fields: [
          {
            type: 'url',
            name: 'href',
          }
        ]
      },
      {
        name: 'author',
        title: 'Author',
        type: 'reference',
        to: [{type: 'author'}]
      },
      {
        type: 'array',
        name: 'marks',
        title: 'Marks',
        of: [{type: 'string'}],
        options: {
          direction: 'vertical',
          list: [
            {title: 'Strong', value: 'strong'},
            {title: 'Emphasis', value: 'em'},
            {title: 'Code', value: 'code'},
            {title: 'Underline', value: 'underline'},
            {title: 'Strike', value: 'strike-through'}
          ]
        }
      }
    ]
  },
  {
    name: 'mark',
    type: 'object',
    title: 'Mark',
    fields: [
      {
        name: 'type',
        title: 'Type',
        type: 'string'
      }
    ]
  },
  {
    name: 'link',
    type: 'object',
    fields: [
      {
        type: 'url',
        name: 'href',
        title: 'Url'
      },
      {
        name: 'keys',
        title: 'Keys',
        type: 'array',
        of: [
          {
            type: 'string',
            title: 'Key'
          }
        ]
      }
    ]
  }
]
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

var redis = require("redis");
var async = require("async");
var conf = require('../config').conf;

// Operations supported by the migration backend.
var SUPPORTED_OPERATIONS = [
  'keys', 'lrange', 'mget', 'sismember', 'smembers', 'get', 'pttl', 'ttl',
  'scard', 'set', 'setex', 'psetex', 'sadd', 'srem', 'pexpire',
  'expire', 'incr', 'decr', 'hmset', 'hgetall', 'hset', 'hget', 'hsetnx',
  'hdel'
];

var MULTI_OPERATIONS = [
  'pttl', 'ttl', 'set', 'setex', 'psetex', 'sadd', 'srem', 'pexpire',
  'expire', 'incr', 'decr', 'hmset', 'hset', 'hsetnx', 'hdel', 'del'
];

/**
 * Creates a redis proxy client, exposing the same APIs of the default client.
 *
 * This client takes parameters for a new and an old db, and copies data from
 * the old to the new db before asking the new db to answer the original
 * request.
 *
 * @param {Object}   options, which should have an `oldDB` and a `newDB`
 *                   key, which respects the semantics of a redis client (port,
 *                   host, options).
 *
 * @returns {Object} a client whith the same APIs as the one used in the redis
 *                   backend.
 **/
function createClient(options) {
  var old_db = getClient(options.oldDB);
  var new_db = getClient(options.newDB);

  var Proxy = function(){
    this.old_db = old_db;
    this.new_db = new_db;
  };

  /**
   * Copy a key from one database to the other.
   * Copies also the TTL information.
   *
   * @param {String} key the key to be copied.
   * @param {Function} callback that will be called once the copy is over.
   **/
  var copyKey = function(key, callback) {
    old_db.pttl(key, function(err, ttl) {
      if (err) throw err;
      if (ttl === -2) {
        // The key doesn't exist.
        callback(null);
        return;
      } else if (ttl === -1) {
        // Set the ttl to 0 if there is no TTL for the current key (it means
        // there is no expiration set for it)
        ttl = 0;
      }
      // Redis client will return buffers if it has buffers as arguments.
      // We want to have a buffer here to dump/restore keys using the right
      // encoding (otherwise a "DUMP payload version or checksum are wrong"
      // error is raised).
      old_db.dump(new Buffer(key), function(err, dump) {
        if (err) return callback(err);
        new_db.restore(key, ttl, dump, function(err) {
          if (err) return callback(err);
          old_db.del(key, function(err){
            if (err) return callback(err);
            callback(null);
          });
        });
      });
    });
  }

  /**
   * Decorator which, given an operation, returns a function that will
   * check if the key exists in the old db, and if so:
   *  - dump from the old db and restore in the new one
   *  - delete the key from the old db
   * And in any case, calls the initial operation on the new db.
   *
   * @param {String} operation — The redis operation name.
   * @return {Function} the function which will do the migration.
   **/
  var migrateAndExecute = function(operation) {
    return function() {
      var originalArguments = arguments;
      var key = arguments[0];
      var callback = arguments[arguments.length - 1];

      // Calls the original command with the arguments passed to this function.
      var callOriginalCommand = function(err){
        if (err) { return callback(err); }
        new_db[operation].apply(new_db, originalArguments);
      }

      // In case we have a keys or a mget command, since we have multiple keys
      // involved, copy all of them before running the original command on the
      // new database.
      if (operation === 'keys') {
        old_db.keys(key, function(err, keys) {
          if (err) return callback(err);
          async.each(keys, copyKey, callOriginalCommand);
        });
      } else if (operation === 'mget') {
        async.each(key, copyKey, callOriginalCommand);
      } else {
        copyKey(key, callOriginalCommand);
      }
    };
  }

  // For each of the supported operations, proxy the call the the migration
  // logic.
  SUPPORTED_OPERATIONS.forEach(function(operation) {
    Proxy.prototype[operation] = migrateAndExecute(operation);
  });

  // Do not relay flush operations if we aren't using the TEST environment.
  Proxy.prototype.flushdb = function(callback) {
    if (conf.get('env') !== 'test') {
      callback();
      return
    }
    old_db.flushdb(function(err) {
      if (err) return callback(err);
      new_db.flushdb(function(err) {
        callback(err);
      });
    });
  };

  // For deletion, we just remove from both databases and return the total
  // count of deleted values.
  Proxy.prototype.del = function(key, callback) {
    var deleted = 0;
    old_db.del(key, function(err, number) {
      deleted += number;
      if (err) return callback(err);
      new_db.del(key, function(err, number) {
        deleted += number;
        callback(err, deleted);
      });
    });
  };

  // For multi, we just chain them as "normal" operations, copying keys for all
  // the operations (so we actually lose the benefit offered by multi).
  Proxy.prototype.multi = function() {
    var self = this;
    var Multi = function(){};
    var operations = [];

    // Each time an operation is done on a multi, add it to a
    // list to execute.

    MULTI_OPERATIONS.forEach(function(operation) {
      Multi.prototype[operation] = function() {
        operations.push([
          operation, Array.prototype.slice.call(arguments)
        ]);
      }
    });

    Multi.prototype.exec = function(callback){
      async.eachSeries(operations, function(operation, done){
        var operationName = operation[0];
        var operationArguments = operation[1];
        operationArguments.push(done);

        var executor = migrateAndExecute(operationName);
        executor.apply(self, operationArguments);
      }, callback);
    }
    return new Multi();
  };

  return new Proxy();
}

/**
 * Returns a redis client from the options passed in arguments.
 *
 * @param {Object}   options with a (port, host, options) keys.
 **/
function getClient(options) {
  var client = redis.createClient(
    options.port,
    options.host,
    // This is to return buffers when buffers are sent as params.
    {detect_buffers: true}
  );
  if (options.db) {
    client.select(options.db);
  }
  return client;
}

module.exports = createClient;
<commit_msg>Make sure multi.exec doesn't use migrateAndExecute for multi.del()<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

var redis = require("redis");
var async = require("async");
var conf = require('../config').conf;

// Operations supported by the migration backend.
var SUPPORTED_OPERATIONS = [
  'keys', 'lrange', 'mget', 'sismember', 'smembers', 'get', 'pttl', 'ttl',
  'scard', 'set', 'setex', 'psetex', 'sadd', 'srem', 'pexpire',
  'expire', 'incr', 'decr', 'hmset', 'hgetall', 'hset', 'hget', 'hsetnx',
  'hdel'
];

var MULTI_OPERATIONS = [
  'pttl', 'ttl', 'set', 'setex', 'psetex', 'sadd', 'srem', 'pexpire',
  'expire', 'incr', 'decr', 'hmset', 'hset', 'hsetnx', 'hdel', 'del'
];

/**
 * Creates a redis proxy client, exposing the same APIs of the default client.
 *
 * This client takes parameters for a new and an old db, and copies data from
 * the old to the new db before asking the new db to answer the original
 * request.
 *
 * @param {Object}   options, which should have an `oldDB` and a `newDB`
 *                   key, which respects the semantics of a redis client (port,
 *                   host, options).
 *
 * @returns {Object} a client whith the same APIs as the one used in the redis
 *                   backend.
 **/
function createClient(options) {
  var old_db = getClient(options.oldDB);
  var new_db = getClient(options.newDB);

  var Proxy = function(){
    this.old_db = old_db;
    this.new_db = new_db;
  };

  /**
   * Copy a key from one database to the other.
   * Copies also the TTL information.
   *
   * @param {String} key the key to be copied.
   * @param {Function} callback that will be called once the copy is over.
   **/
  var copyKey = function(key, callback) {
    old_db.pttl(key, function(err, ttl) {
      if (err) throw err;
      if (ttl === -2) {
        // The key doesn't exist.
        callback(null);
        return;
      } else if (ttl === -1) {
        // Set the ttl to 0 if there is no TTL for the current key (it means
        // there is no expiration set for it)
        ttl = 0;
      }
      // Redis client will return buffers if it has buffers as arguments.
      // We want to have a buffer here to dump/restore keys using the right
      // encoding (otherwise a "DUMP payload version or checksum are wrong"
      // error is raised).
      old_db.dump(new Buffer(key), function(err, dump) {
        if (err) return callback(err);
        new_db.restore(key, ttl, dump, function(err) {
          if (err) return callback(err);
          old_db.del(key, function(err){
            if (err) return callback(err);
            callback(null);
          });
        });
      });
    });
  }

  /**
   * Decorator which, given an operation, returns a function that will
   * check if the key exists in the old db, and if so:
   *  - dump from the old db and restore in the new one
   *  - delete the key from the old db
   * And in any case, calls the initial operation on the new db.
   *
   * @param {String} operation — The redis operation name.
   * @return {Function} the function which will do the migration.
   **/
  var migrateAndExecute = function(operation) {
    return function() {
      var originalArguments = arguments;
      var key = arguments[0];
      var callback = arguments[arguments.length - 1];

      // Calls the original command with the arguments passed to this function.
      var callOriginalCommand = function(err){
        if (err) { return callback(err); }
        new_db[operation].apply(new_db, originalArguments);
      }

      // In case we have a keys or a mget command, since we have multiple keys
      // involved, copy all of them before running the original command on the
      // new database.
      if (operation === 'keys') {
        old_db.keys(key, function(err, keys) {
          if (err) return callback(err);
          async.each(keys, copyKey, callOriginalCommand);
        });
      } else if (operation === 'mget') {
        async.each(key, copyKey, callOriginalCommand);
      } else {
        copyKey(key, callOriginalCommand);
      }
    };
  }

  // For each of the supported operations, proxy the call the the migration
  // logic.
  SUPPORTED_OPERATIONS.forEach(function(operation) {
    Proxy.prototype[operation] = migrateAndExecute(operation);
  });

  // Do not relay flush operations if we aren't using the TEST environment.
  Proxy.prototype.flushdb = function(callback) {
    if (conf.get('env') !== 'test') {
      callback();
      return
    }
    old_db.flushdb(function(err) {
      if (err) return callback(err);
      new_db.flushdb(function(err) {
        callback(err);
      });
    });
  };

  // For deletion, we just remove from both databases and return the total
  // count of deleted values.
  Proxy.prototype.del = function(key, callback) {
    var deleted = 0;
    old_db.del(key, function(err, number) {
      deleted += number;
      if (err) return callback(err);
      new_db.del(key, function(err, number) {
        deleted += number;
        callback(err, deleted);
      });
    });
  };

  // For multi, we just chain them as "normal" operations, copying keys for all
  // the operations (so we actually lose the benefit offered by multi).
  Proxy.prototype.multi = function() {
    var self = this;
    var Multi = function(){};
    var operations = [];

    // Each time an operation is done on a multi, add it to a
    // list to execute.

    MULTI_OPERATIONS.forEach(function(operation) {
      Multi.prototype[operation] = function() {
        operations.push([
          operation, Array.prototype.slice.call(arguments)
        ]);
      }
    });

    Multi.prototype.exec = function(callback){
      async.eachSeries(operations, function(operation, done){
        var operationName = operation[0];
        var operationArguments = operation[1];
        operationArguments.push(done);

        if (operationName === "del") {
          self.del.apply(self, operationArguments);
        } else {
          var executor = migrateAndExecute(operationName);
          executor.apply(self, operationArguments);
        }
      }, callback);
    }
    return new Multi();
  };

  return new Proxy();
}

/**
 * Returns a redis client from the options passed in arguments.
 *
 * @param {Object}   options with a (port, host, options) keys.
 **/
function getClient(options) {
  var client = redis.createClient(
    options.port,
    options.host,
    // This is to return buffers when buffers are sent as params.
    {detect_buffers: true}
  );
  if (options.db) {
    client.select(options.db);
  }
  return client;
}

module.exports = createClient;
<|endoftext|>