<commit_before>(function ($, window, document) {
    'use strict';

    var cssTransitionSupport = function () {
        var s = document.body || document.documentElement;
        s = s.style;
        if (s.WebkitTransition === '') {
            return '-webkit-';
        }
        if (s.MozTransition === '') {
            return '-moz-';
        }
        if (s.OTransition === '') {
            return '-o-';
        }
        if (s.transition === '') {
            return '';
        }
        return false;
    };

    var isCssTransitionSupport = cssTransitionSupport() !== false;

    var cssTransitionTranslateX = function (element, positionX, speed) {
        var options = {}, prefix = cssTransitionSupport();
        options[prefix + 'transform']  = 'translateX(' + positionX + ')';
        options[prefix + 'transition'] = prefix + 'transform ' + speed + 's linear';
        element.css(options);
    };

    var CONST_HASTOUCH = ('ontouchstart' in window);
    var CONST_HASPOINTERS = window.navigator.pointerEnabled || window.navigator.msPointerEnabled;
    var wasTouched = function (event) {
        if (CONST_HASTOUCH) {
            return true;
        }

        if (!CONST_HASPOINTERS || typeof event === 'undefined' || typeof event.pointerType === 'undefined') {
            return false;
        }

        if (typeof event.MSPOINTER_TYPE_MOUSE !== 'undefined') {
            if (event.MSPOINTER_TYPE_MOUSE !== event.pointerType) {
                return true;
            }
        } else if (event.pointerType !== 'mouse') {
            return true;
        }

        return false;
    };

    $.fn.imageLightbox = function (opts) {
        var options = $.extend({
            selector: 'id="imagelightbox"',
            allowedTypes: 'png|jpg|jpeg|gif',
            animationSpeed: 250,
            preloadNext: true,
            enableKeyboard: true,
            quitOnEnd: false,
            quitOnImgClick: false,
            quitOnDocClick: true,
            quitOnEscKey: true, // quit when Esc key is pressed
            onStart: false,
            onEnd: false,
            onLoadStart: false,
            onLoadEnd: false,

            previousTarget: function () {
                var targetIndex = GlobalTargetsArray.index(GlobalTarget) - 1;
                if (targetIndex < 0) {
                    if (options.quitOnEnd === true) {
                        quitLightbox();
                        return false;
                    }
                    targetIndex = GlobalTargetsArray.length - 1;
                }
                GlobalTarget = GlobalTargetsArray.eq(targetIndex);
            },

            nextTarget: function () {
                var targetIndex = GlobalTargetsArray.index(GlobalTarget) + 1;
                if (targetIndex >= GlobalTargetsArray.length) {
                    if (options.quitOnEnd === true) {
                        quitLightbox();
                        return false;
                    }
                    targetIndex = 0;
                }
                GlobalTarget = GlobalTargetsArray.eq(targetIndex);
            },
        }, opts);

        var GlobalTargetsArray = $([]);
        var GlobalTarget = $();
        var GlobalImg = $();
        var GlobalImageWidth = 0;
        var GlobalImageHeight = 0;
        var GlobalSwipeDiff = 0;
        var GlobalInProgress = false;

        var isTargetValid = function (element) {
            var allowedFileExtentionsRegex = new RegExp('.(' + options.allowedTypes + ')$', 'i');
            var targetFileTypeIsSupported = allowedFileExtentionsRegex.test($(element).attr('href'));
            var elementIsAnchorTag = $(element).prop('tagName').toLowerCase() === 'a';
            var elementHasDataLightboxAttribute = $(element).attr('data-lightbox') !== undefined;
            return (elementIsAnchorTag && targetFileTypeIsSupported) || elementHasDataLightboxAttribute;
        };

        var setImage = function () {
            if (!GlobalImg.length) {
                return true;
            }

            var screenWidth = $(window).width() * 0.8;
            var screenHeight = $(window).height() * 0.9;
            var tmpImage = new Image();

            tmpImage.src = GlobalImg.attr('src');
            tmpImage.onload = function () {
                GlobalImageWidth = tmpImage.width;
                GlobalImageHeight = tmpImage.height;

                if (GlobalImageWidth > screenWidth || GlobalImageHeight > screenHeight) {
                    var ratio = GlobalImageWidth / GlobalImageHeight > screenWidth / screenHeight ? GlobalImageWidth / screenWidth: GlobalImageHeight / screenHeight;
                    GlobalImageWidth /= ratio;
                    GlobalImageHeight /= ratio;
                }

                GlobalImg.css({
                    'width': GlobalImageWidth + 'px',
                    'height': GlobalImageHeight + 'px',
                    'top': ($(window).height() - GlobalImageHeight) / 2 + 'px',
                    'left': ($(window).width() - GlobalImageWidth) / 2 + 'px',
                });
            };
        };

        var loadImage = function (direction) {
            if (GlobalInProgress) {
                return false;
            }

            direction = typeof direction === 'undefined' ? false: direction === 'left' ? 1: -1;

            if (GlobalImg.length) {
                var params = {
                    'opacity': 0,
                };
                if (isCssTransitionSupport) {
                    cssTransitionTranslateX(GlobalImg, (100 * direction) - GlobalSwipeDiff + 'px', options.animationSpeed / 1000);
                } else {
                    params.left = parseInt(GlobalImg.css('left')) + 100 * direction + 'px';
                }
                GlobalImg.animate(params, options.animationSpeed, function () {
                    removeImage();
                });
                GlobalSwipeDiff = 0;
            }

            GlobalInProgress = true;
            if (options.onLoadStart !== false) {
                options.onLoadStart();
            }

            setTimeout(function () {
                var imgPath = GlobalTarget.attr('href');
                if (imgPath === undefined) {
                    imgPath = GlobalTarget.attr('data-lightbox');
                }
                GlobalImg = $('<img ' + options.selector + ' />')
                    .attr('src', imgPath)
                    .load(function () {
                        GlobalImg.appendTo('body');
                        setImage();

                        var params = {
                            'opacity': 1,
                        };

                        GlobalImg.css('opacity', 0);
                        if (isCssTransitionSupport) {
                            cssTransitionTranslateX(GlobalImg, -100 * direction + 'px', 0);
                            setTimeout(function () {
                                cssTransitionTranslateX(GlobalImg, 0 + 'px', options.animationSpeed / 1000);
                            }, 50);
                        } else {
                            var imagePosLeft = parseInt(GlobalImg.css('left'));
                            params.left = imagePosLeft + 'px';
                            GlobalImg.css('left', imagePosLeft - 100 * direction + 'px');
                        }

                        GlobalImg.animate(params, options.animationSpeed, function () {
                            GlobalInProgress = false;
                            if (options.onLoadEnd !== false) {
                                options.onLoadEnd();
                            }
                        });
                        if (options.preloadNext) {
                            var nextTarget = GlobalTargetsArray.eq(GlobalTargetsArray.index(GlobalTarget) + 1);
                            if (!nextTarget.length) {
                                nextTarget = GlobalTargetsArray.eq(0);
                            }
                            $('<img />').attr('src', nextTarget.attr('href')).load();
                        }
                    })
                    .error(function () {
                        if (options.onLoadEnd !== false) {
                            options.onLoadEnd();
                        }
                    });

                var swipeStart = 0;
                var swipeEnd = 0;
                var imagePosLeft = 0;

                GlobalImg.on(CONST_HASPOINTERS ? 'pointerup MSPointerUp': 'click', function (e) {
                    e.preventDefault();
                    if (options.quitOnImgClick) {
                        quitLightbox();
                        return false;
                    }
                    if (wasTouched(e.originalEvent)) {
                        return true;
                    }
                    var posX = (e.pageX || e.originalEvent.pageX) - e.target.offsetLeft;
                    if (GlobalImageWidth / 2 > posX) {
                        loadPreviousImage();
                    } else {
                        loadNextImage();
                    }
                })
                    .on('touchstart pointerdown MSPointerDown', function (e) {
                        if (!wasTouched(e.originalEvent) || options.quitOnImgClick) {
                            return true;
                        }
                        if (isCssTransitionSupport) {
                            imagePosLeft = parseInt(GlobalImg.css('left'));
                        }
                        swipeStart = e.originalEvent.pageX || e.originalEvent.touches[0].pageX;
                    })
                    .on('touchmove pointermove MSPointerMove', function (e) {
                        if (!wasTouched(e.originalEvent) || options.quitOnImgClick) {
                            return true;
                        }
                        e.preventDefault();
                        swipeEnd = e.originalEvent.pageX || e.originalEvent.touches[0].pageX;
                        GlobalSwipeDiff = swipeStart - swipeEnd;
                        if (isCssTransitionSupport) {
                            cssTransitionTranslateX(GlobalImg, -GlobalSwipeDiff + 'px', 0);
                        } else {
                            GlobalImg.css('left', imagePosLeft - GlobalSwipeDiff + 'px');
                        }
                    })
                    .on('touchend touchcancel pointerup pointercancel MSPointerUp MSPointerCancel', function (e) {
                        if (!wasTouched(e.originalEvent) || options.quitOnImgClick) {
                            return true;
                        }
                        if (Math.abs(GlobalSwipeDiff) > 50) {
                            if (GlobalSwipeDiff < 0) {
                                loadPreviousImage();
                            } else {
                                loadNextImage();
                            }
                        } else {
                            if (isCssTransitionSupport) {
                                cssTransitionTranslateX(GlobalImg, 0 + 'px', options.animationSpeed / 1000);
                            } else {
                                GlobalImg.animate({ 'left': imagePosLeft + 'px' }, options.animationSpeed / 2);
                            }
                        }
                    });

            }, options.animationSpeed + 100);
        };

        var loadPreviousImage = function () {
            if (options.previousTarget() !== false) {
                loadImage('left');
            }
        };

        var loadNextImage = function () {
            if (options.nextTarget() !== false) {
                loadImage('right');
            }
        };

        var removeImage = function () {
            if (!GlobalImg.length) {
                return false;
            }
            GlobalImg.remove();
            GlobalImg = $();
        };

        var quitLightbox = function () {
            if (!GlobalImg.length) {
                return false;
            }
            GlobalImg.animate({ 'opacity': 0 }, options.animationSpeed, function () {
                removeImage();
                GlobalInProgress = false;
                if (options.onEnd !== false) {
                    options.onEnd();
                }
            });
        };

        $(window).on('resize', setImage);

        if (options.quitOnDocClick) {
            $(document).on(CONST_HASTOUCH ? 'touchend': 'click', function (e) {
                if (GlobalImg.length && !$(e.target).is(GlobalImg)) {
                    quitLightbox();
                }
            });
        }

        if (options.enableKeyboard) {
            $(document).on('keyup', function (e) {
                if (!GlobalImg.length) {
                    return true;
                }
                e.preventDefault();
                if (e.keyCode === 27 && options.quitOnEscKey === true) {
                    quitLightbox();
                }
                if (e.keyCode === 37) {
                    loadPreviousImage();
                } else if (e.keyCode === 39) {
                    loadNextImage();
                }
            });
        }

        this.startImageLightbox = function (e) {
            if (!isTargetValid(this)) {
                return true;
            }
            if (e !== undefined) {
                e.preventDefault();
            }
            if (GlobalInProgress) {
                return false;
            }
            GlobalInProgress = false;
            if (options.onStart !== false) {
                options.onStart();
            }
            GlobalTarget = $(this);
            loadImage();
        };

        $(document).off('click', this.selector);
        $(document).on('click', this.selector, this.startImageLightbox);

        this.each(function () {
            if (!isTargetValid(this)) {
                return true;
            }
            GlobalTargetsArray = GlobalTargetsArray.add($(this));
        });

        this.switchImageLightbox = function (index) {
            var tmpTarget = GlobalTargetsArray.eq(index);
            if (tmpTarget.length) {
                var currentIndex = GlobalTargetsArray.index(GlobalTarget);
                GlobalTarget = tmpTarget;
                loadImage(index < currentIndex ? 'left': 'right');
            }
            return this;
        };

        this.loadPreviousImage = function () {
            loadPreviousImage();
        };

        this.loadNextImage = function () {
            loadNextImage();
        };

        this.quitImageLightbox = function () {
            quitLightbox();
            return this;
        };

        return this;
    };
})(jQuery, window, document);
<commit_msg>Replace boolean-true comparisons with truthy values<commit_after>(function ($, window, document) {
    'use strict';

    var cssTransitionSupport = function () {
        var s = document.body || document.documentElement;
        s = s.style;
        if (s.WebkitTransition === '') {
            return '-webkit-';
        }
        if (s.MozTransition === '') {
            return '-moz-';
        }
        if (s.OTransition === '') {
            return '-o-';
        }
        if (s.transition === '') {
            return '';
        }
        return false;
    };

    var isCssTransitionSupport = cssTransitionSupport() !== false;

    var cssTransitionTranslateX = function (element, positionX, speed) {
        var options = {}, prefix = cssTransitionSupport();
        options[prefix + 'transform']  = 'translateX(' + positionX + ')';
        options[prefix + 'transition'] = prefix + 'transform ' + speed + 's linear';
        element.css(options);
    };

    var CONST_HASTOUCH = ('ontouchstart' in window);
    var CONST_HASPOINTERS = window.navigator.pointerEnabled || window.navigator.msPointerEnabled;
    var wasTouched = function (event) {
        if (CONST_HASTOUCH) {
            return true;
        }

        if (!CONST_HASPOINTERS || typeof event === 'undefined' || typeof event.pointerType === 'undefined') {
            return false;
        }

        if (typeof event.MSPOINTER_TYPE_MOUSE !== 'undefined') {
            if (event.MSPOINTER_TYPE_MOUSE !== event.pointerType) {
                return true;
            }
        } else if (event.pointerType !== 'mouse') {
            return true;
        }

        return false;
    };

    $.fn.imageLightbox = function (opts) {
        var options = $.extend({
            selector: 'id="imagelightbox"',
            allowedTypes: 'png|jpg|jpeg|gif',
            animationSpeed: 250,
            preloadNext: true,
            enableKeyboard: true,
            quitOnEnd: false,
            quitOnImgClick: false,
            quitOnDocClick: true,
            quitOnEscKey: true, // quit when Esc key is pressed
            onStart: false,
            onEnd: false,
            onLoadStart: false,
            onLoadEnd: false,

            previousTarget: function () {
                var targetIndex = GlobalTargetsArray.index(GlobalTarget) - 1;
                if (targetIndex < 0) {
                    if (options.quitOnEnd) {
                        quitLightbox();
                        return false;
                    }
                    targetIndex = GlobalTargetsArray.length - 1;
                }
                GlobalTarget = GlobalTargetsArray.eq(targetIndex);
            },

            nextTarget: function () {
                var targetIndex = GlobalTargetsArray.index(GlobalTarget) + 1;
                if (targetIndex >= GlobalTargetsArray.length) {
                    if (options.quitOnEnd) {
                        quitLightbox();
                        return false;
                    }
                    targetIndex = 0;
                }
                GlobalTarget = GlobalTargetsArray.eq(targetIndex);
            },
        }, opts);

        var GlobalTargetsArray = $([]);
        var GlobalTarget = $();
        var GlobalImg = $();
        var GlobalImageWidth = 0;
        var GlobalImageHeight = 0;
        var GlobalSwipeDiff = 0;
        var GlobalInProgress = false;

        var isTargetValid = function (element) {
            var allowedFileExtentionsRegex = new RegExp('.(' + options.allowedTypes + ')$', 'i');
            var targetFileTypeIsSupported = allowedFileExtentionsRegex.test($(element).attr('href'));
            var elementIsAnchorTag = $(element).prop('tagName').toLowerCase() === 'a';
            var elementHasDataLightboxAttribute = $(element).attr('data-lightbox') !== undefined;
            return (elementIsAnchorTag && targetFileTypeIsSupported) || elementHasDataLightboxAttribute;
        };

        var setImage = function () {
            if (!GlobalImg.length) {
                return true;
            }

            var screenWidth = $(window).width() * 0.8;
            var screenHeight = $(window).height() * 0.9;
            var tmpImage = new Image();

            tmpImage.src = GlobalImg.attr('src');
            tmpImage.onload = function () {
                GlobalImageWidth = tmpImage.width;
                GlobalImageHeight = tmpImage.height;

                if (GlobalImageWidth > screenWidth || GlobalImageHeight > screenHeight) {
                    var ratio = GlobalImageWidth / GlobalImageHeight > screenWidth / screenHeight ? GlobalImageWidth / screenWidth: GlobalImageHeight / screenHeight;
                    GlobalImageWidth /= ratio;
                    GlobalImageHeight /= ratio;
                }

                GlobalImg.css({
                    'width': GlobalImageWidth + 'px',
                    'height': GlobalImageHeight + 'px',
                    'top': ($(window).height() - GlobalImageHeight) / 2 + 'px',
                    'left': ($(window).width() - GlobalImageWidth) / 2 + 'px',
                });
            };
        };

        var loadImage = function (direction) {
            if (GlobalInProgress) {
                return false;
            }

            direction = typeof direction === 'undefined' ? false: direction === 'left' ? 1: -1;

            if (GlobalImg.length) {
                var params = {
                    'opacity': 0,
                };
                if (isCssTransitionSupport) {
                    cssTransitionTranslateX(GlobalImg, (100 * direction) - GlobalSwipeDiff + 'px', options.animationSpeed / 1000);
                } else {
                    params.left = parseInt(GlobalImg.css('left')) + 100 * direction + 'px';
                }
                GlobalImg.animate(params, options.animationSpeed, function () {
                    removeImage();
                });
                GlobalSwipeDiff = 0;
            }

            GlobalInProgress = true;
            if (options.onLoadStart !== false) {
                options.onLoadStart();
            }

            setTimeout(function () {
                var imgPath = GlobalTarget.attr('href');
                if (imgPath === undefined) {
                    imgPath = GlobalTarget.attr('data-lightbox');
                }
                GlobalImg = $('<img ' + options.selector + ' />')
                    .attr('src', imgPath)
                    .load(function () {
                        GlobalImg.appendTo('body');
                        setImage();

                        var params = {
                            'opacity': 1,
                        };

                        GlobalImg.css('opacity', 0);
                        if (isCssTransitionSupport) {
                            cssTransitionTranslateX(GlobalImg, -100 * direction + 'px', 0);
                            setTimeout(function () {
                                cssTransitionTranslateX(GlobalImg, 0 + 'px', options.animationSpeed / 1000);
                            }, 50);
                        } else {
                            var imagePosLeft = parseInt(GlobalImg.css('left'));
                            params.left = imagePosLeft + 'px';
                            GlobalImg.css('left', imagePosLeft - 100 * direction + 'px');
                        }

                        GlobalImg.animate(params, options.animationSpeed, function () {
                            GlobalInProgress = false;
                            if (options.onLoadEnd !== false) {
                                options.onLoadEnd();
                            }
                        });
                        if (options.preloadNext) {
                            var nextTarget = GlobalTargetsArray.eq(GlobalTargetsArray.index(GlobalTarget) + 1);
                            if (!nextTarget.length) {
                                nextTarget = GlobalTargetsArray.eq(0);
                            }
                            $('<img />').attr('src', nextTarget.attr('href')).load();
                        }
                    })
                    .error(function () {
                        if (options.onLoadEnd !== false) {
                            options.onLoadEnd();
                        }
                    });

                var swipeStart = 0;
                var swipeEnd = 0;
                var imagePosLeft = 0;

                GlobalImg.on(CONST_HASPOINTERS ? 'pointerup MSPointerUp': 'click', function (e) {
                    e.preventDefault();
                    if (options.quitOnImgClick) {
                        quitLightbox();
                        return false;
                    }
                    if (wasTouched(e.originalEvent)) {
                        return true;
                    }
                    var posX = (e.pageX || e.originalEvent.pageX) - e.target.offsetLeft;
                    if (GlobalImageWidth / 2 > posX) {
                        loadPreviousImage();
                    } else {
                        loadNextImage();
                    }
                })
                    .on('touchstart pointerdown MSPointerDown', function (e) {
                        if (!wasTouched(e.originalEvent) || options.quitOnImgClick) {
                            return true;
                        }
                        if (isCssTransitionSupport) {
                            imagePosLeft = parseInt(GlobalImg.css('left'));
                        }
                        swipeStart = e.originalEvent.pageX || e.originalEvent.touches[0].pageX;
                    })
                    .on('touchmove pointermove MSPointerMove', function (e) {
                        if (!wasTouched(e.originalEvent) || options.quitOnImgClick) {
                            return true;
                        }
                        e.preventDefault();
                        swipeEnd = e.originalEvent.pageX || e.originalEvent.touches[0].pageX;
                        GlobalSwipeDiff = swipeStart - swipeEnd;
                        if (isCssTransitionSupport) {
                            cssTransitionTranslateX(GlobalImg, -GlobalSwipeDiff + 'px', 0);
                        } else {
                            GlobalImg.css('left', imagePosLeft - GlobalSwipeDiff + 'px');
                        }
                    })
                    .on('touchend touchcancel pointerup pointercancel MSPointerUp MSPointerCancel', function (e) {
                        if (!wasTouched(e.originalEvent) || options.quitOnImgClick) {
                            return true;
                        }
                        if (Math.abs(GlobalSwipeDiff) > 50) {
                            if (GlobalSwipeDiff < 0) {
                                loadPreviousImage();
                            } else {
                                loadNextImage();
                            }
                        } else {
                            if (isCssTransitionSupport) {
                                cssTransitionTranslateX(GlobalImg, 0 + 'px', options.animationSpeed / 1000);
                            } else {
                                GlobalImg.animate({ 'left': imagePosLeft + 'px' }, options.animationSpeed / 2);
                            }
                        }
                    });

            }, options.animationSpeed + 100);
        };

        var loadPreviousImage = function () {
            if (options.previousTarget() !== false) {
                loadImage('left');
            }
        };

        var loadNextImage = function () {
            if (options.nextTarget() !== false) {
                loadImage('right');
            }
        };

        var removeImage = function () {
            if (!GlobalImg.length) {
                return false;
            }
            GlobalImg.remove();
            GlobalImg = $();
        };

        var quitLightbox = function () {
            if (!GlobalImg.length) {
                return false;
            }
            GlobalImg.animate({ 'opacity': 0 }, options.animationSpeed, function () {
                removeImage();
                GlobalInProgress = false;
                if (options.onEnd !== false) {
                    options.onEnd();
                }
            });
        };

        $(window).on('resize', setImage);

        if (options.quitOnDocClick) {
            $(document).on(CONST_HASTOUCH ? 'touchend': 'click', function (e) {
                if (GlobalImg.length && !$(e.target).is(GlobalImg)) {
                    quitLightbox();
                }
            });
        }

        if (options.enableKeyboard) {
            $(document).on('keyup', function (e) {
                if (!GlobalImg.length) {
                    return true;
                }
                e.preventDefault();
                if (e.keyCode === 27 && options.quitOnEscKey) {
                    quitLightbox();
                }
                if (e.keyCode === 37) {
                    loadPreviousImage();
                } else if (e.keyCode === 39) {
                    loadNextImage();
                }
            });
        }

        this.startImageLightbox = function (e) {
            if (!isTargetValid(this)) {
                return true;
            }
            if (e !== undefined) {
                e.preventDefault();
            }
            if (GlobalInProgress) {
                return false;
            }
            GlobalInProgress = false;
            if (options.onStart !== false) {
                options.onStart();
            }
            GlobalTarget = $(this);
            loadImage();
        };

        $(document).off('click', this.selector);
        $(document).on('click', this.selector, this.startImageLightbox);

        this.each(function () {
            if (!isTargetValid(this)) {
                return true;
            }
            GlobalTargetsArray = GlobalTargetsArray.add($(this));
        });

        this.switchImageLightbox = function (index) {
            var tmpTarget = GlobalTargetsArray.eq(index);
            if (tmpTarget.length) {
                var currentIndex = GlobalTargetsArray.index(GlobalTarget);
                GlobalTarget = tmpTarget;
                loadImage(index < currentIndex ? 'left': 'right');
            }
            return this;
        };

        this.loadPreviousImage = function () {
            loadPreviousImage();
        };

        this.loadNextImage = function () {
            loadNextImage();
        };

        this.quitImageLightbox = function () {
            quitLightbox();
            return this;
        };

        return this;
    };
})(jQuery, window, document);
<|endoftext|>


<commit_before>import { remote, shell } from 'electron';
import { autorun, computed, observable, toJS } from 'mobx';

import { isMac } from '../environment';

const { app, Menu, dialog } = remote;

const template = [
  {
    label: 'Edit',
    submenu: [
      {
        role: 'undo',
      },
      {
        role: 'redo',
      },
      {
        type: 'separator',
      },
      {
        role: 'cut',
      },
      {
        role: 'copy',
      },
      {
        role: 'paste',
      },
      {
        role: 'pasteandmatchstyle',
      },
      {
        role: 'delete',
      },
      {
        role: 'selectall',
      },
    ],
  },
  {
    label: 'View',
    submenu: [
      {
        type: 'separator',
      },
      {
        role: 'resetzoom',
      },
      {
        role: 'zoomin',
        accelerator: 'CommandOrControl+=',
      },
      {
        role: 'zoomout',
      },
      {
        type: 'separator',
      },
      {
        role: 'togglefullscreen',
      },
    ],
  },
  {
    label: 'Services',
    submenu: [],
  },
  {
    role: 'window',
    submenu: [
      {
        role: 'minimize',
      },
      {
        role: 'close',
      },
    ],
  },
  {
    role: 'help',
    submenu: [
      {
        label: 'Learn More',
        click() { shell.openExternal('http://meetfranz.com'); },
      },
      {
        label: 'Changelog',
        click() { shell.openExternal('https://github.com/meetfranz/franz/blob/master/CHANGELOG.md'); },
      },
      {
        type: 'separator',
      },
      {
        label: 'Support',
        click() { shell.openExternal('http://meetfranz.com/support'); },
      },
      {
        type: 'separator',
      },
      {
        label: 'Terms of Service',
        click() { shell.openExternal('https://meetfranz.com/terms'); },
      },
      {
        label: 'Privacy Statement',
        click() { shell.openExternal('https://meetfranz.com/privacy'); },
      },
    ],
  },
];

export default class FranzMenu {
  @observable tpl = template;

  constructor(stores, actions) {
    this.stores = stores;
    this.actions = actions;

    autorun(this._build.bind(this));
  }

  _build() {
    const tpl = toJS(this.tpl);

    tpl[1].submenu.push({
      role: 'toggledevtools',
    }, {
      label: 'Toggle Service Developer Tools',
      accelerator: 'CmdOrCtrl+Shift+Alt+i',
      click: () => {
        this.actions.service.openDevToolsForActiveService();
      },
    });

    tpl[1].submenu.unshift({
      label: 'Reload Service',
      id: 'reloadService',
      accelerator: 'CmdOrCtrl+R',
      click: () => {
        if (this.stores.user.isLoggedIn
          && this.stores.services.enabled.length > 0) {
          this.actions.service.reloadActive();
        } else {
          window.location.reload();
        }
      },
    }, {
      label: 'Reload Franz',
      accelerator: 'CmdOrCtrl+Shift+R',
      click: () => {
        window.location.reload();
      },
    });

    if (isMac) {
      tpl.unshift({
        label: app.getName(),
        submenu: [
          {
            role: 'about',
          },
          {
            type: 'separator',
          },
          {
            label: 'Settings',
            accelerator: 'CmdOrCtrl+,',
            click: () => {
              this.actions.ui.openSettings({ path: '' });
            },
          },
          {
            type: 'separator',
          },
          {
            role: 'services',
            submenu: [],
          },
          {
            type: 'separator',
          },
          {
            role: 'hide',
          },
          {
            role: 'hideothers',
          },
          {
            role: 'unhide',
          },
          {
            type: 'separator',
          },
          {
            role: 'quit',
          },
        ],
      });
      // Edit menu.
      tpl[1].submenu.push(
        {
          type: 'separator',
        },
        {
          label: 'Speech',
          submenu: [
            {
              role: 'startspeaking',
            },
            {
              role: 'stopspeaking',
            },
          ],
        },
      );
      // Window menu.
      tpl[3].submenu = [
        {
          // label: 'Close',
          accelerator: 'CmdOrCtrl+W',
          role: 'close',
        },
        {
          // label: 'Minimize',
          accelerator: 'CmdOrCtrl+M',
          role: 'minimize',
        },
        {
          // label: 'Zoom',
          role: 'zoom',
        },
        {
          type: 'separator',
        },
        {
          // label: 'Bring All to Front',
          role: 'front',
        },
      ];
    } else {
      tpl[4].submenu.unshift({
        role: 'about',
        click: () => {
          dialog.showMessageBox({
            type: 'info',
            title: 'Franz',
            message: 'Franz',
            detail: `Version: ${remote.app.getVersion()}\nRelease: ${process.versions.electron} / ${process.platform} / ${process.arch}`,
          });
        },
      });
    }

    const serviceTpl = this.serviceTpl;

    serviceTpl.unshift({
      label: 'Add new Service',
      accelerator: 'CmdOrCtrl+N',
      click: () => {
        this.actions.ui.openSettings({ path: 'recipes' });
      },
    }, {
      type: 'separator',
    });

    if (serviceTpl.length > 0) {
      tpl[isMac ? 3 : 2].submenu = toJS(this.serviceTpl);
    }

    const menu = Menu.buildFromTemplate(tpl);
    Menu.setApplicationMenu(menu);
  }

  @computed get serviceTpl() {
    const services = this.stores.services.enabled;

    if (this.stores.user.isLoggedIn) {
      return services.map((service, i) => ({
        label: service.name,
        accelerator: i <= 9 ? `CmdOrCtrl+${i + 1}` : null,
        type: 'radio',
        checked: service.isActive,
        click: () => {
          this.actions.service.setActive({ serviceId: service.id });
        },
      }));
    }

    return [];
  }
}
<commit_msg>fix(Menu): Add fallback to service menu when service name is empty<commit_after>import { remote, shell } from 'electron';
import { autorun, computed, observable, toJS } from 'mobx';

import { isMac } from '../environment';

const { app, Menu, dialog } = remote;

const template = [
  {
    label: 'Edit',
    submenu: [
      {
        role: 'undo',
      },
      {
        role: 'redo',
      },
      {
        type: 'separator',
      },
      {
        role: 'cut',
      },
      {
        role: 'copy',
      },
      {
        role: 'paste',
      },
      {
        role: 'pasteandmatchstyle',
      },
      {
        role: 'delete',
      },
      {
        role: 'selectall',
      },
    ],
  },
  {
    label: 'View',
    submenu: [
      {
        type: 'separator',
      },
      {
        role: 'resetzoom',
      },
      {
        role: 'zoomin',
        accelerator: 'CommandOrControl+=',
      },
      {
        role: 'zoomout',
      },
      {
        type: 'separator',
      },
      {
        role: 'togglefullscreen',
      },
    ],
  },
  {
    label: 'Services',
    submenu: [],
  },
  {
    role: 'window',
    submenu: [
      {
        role: 'minimize',
      },
      {
        role: 'close',
      },
    ],
  },
  {
    role: 'help',
    submenu: [
      {
        label: 'Learn More',
        click() { shell.openExternal('http://meetfranz.com'); },
      },
      {
        label: 'Changelog',
        click() { shell.openExternal('https://github.com/meetfranz/franz/blob/master/CHANGELOG.md'); },
      },
      {
        type: 'separator',
      },
      {
        label: 'Support',
        click() { shell.openExternal('http://meetfranz.com/support'); },
      },
      {
        type: 'separator',
      },
      {
        label: 'Terms of Service',
        click() { shell.openExternal('https://meetfranz.com/terms'); },
      },
      {
        label: 'Privacy Statement',
        click() { shell.openExternal('https://meetfranz.com/privacy'); },
      },
    ],
  },
];

export default class FranzMenu {
  @observable tpl = template;

  constructor(stores, actions) {
    this.stores = stores;
    this.actions = actions;

    autorun(this._build.bind(this));
  }

  _build() {
    const tpl = toJS(this.tpl);

    tpl[1].submenu.push({
      role: 'toggledevtools',
    }, {
      label: 'Toggle Service Developer Tools',
      accelerator: 'CmdOrCtrl+Shift+Alt+i',
      click: () => {
        this.actions.service.openDevToolsForActiveService();
      },
    });

    tpl[1].submenu.unshift({
      label: 'Reload Service',
      id: 'reloadService',
      accelerator: 'CmdOrCtrl+R',
      click: () => {
        if (this.stores.user.isLoggedIn
          && this.stores.services.enabled.length > 0) {
          this.actions.service.reloadActive();
        } else {
          window.location.reload();
        }
      },
    }, {
      label: 'Reload Franz',
      accelerator: 'CmdOrCtrl+Shift+R',
      click: () => {
        window.location.reload();
      },
    });

    if (isMac) {
      tpl.unshift({
        label: app.getName(),
        submenu: [
          {
            role: 'about',
          },
          {
            type: 'separator',
          },
          {
            label: 'Settings',
            accelerator: 'CmdOrCtrl+,',
            click: () => {
              this.actions.ui.openSettings({ path: '' });
            },
          },
          {
            type: 'separator',
          },
          {
            role: 'services',
            submenu: [],
          },
          {
            type: 'separator',
          },
          {
            role: 'hide',
          },
          {
            role: 'hideothers',
          },
          {
            role: 'unhide',
          },
          {
            type: 'separator',
          },
          {
            role: 'quit',
          },
        ],
      });
      // Edit menu.
      tpl[1].submenu.push(
        {
          type: 'separator',
        },
        {
          label: 'Speech',
          submenu: [
            {
              role: 'startspeaking',
            },
            {
              role: 'stopspeaking',
            },
          ],
        },
      );
      // Window menu.
      tpl[3].submenu = [
        {
          // label: 'Close',
          accelerator: 'CmdOrCtrl+W',
          role: 'close',
        },
        {
          // label: 'Minimize',
          accelerator: 'CmdOrCtrl+M',
          role: 'minimize',
        },
        {
          // label: 'Zoom',
          role: 'zoom',
        },
        {
          type: 'separator',
        },
        {
          // label: 'Bring All to Front',
          role: 'front',
        },
      ];
    } else {
      tpl[4].submenu.unshift({
        role: 'about',
        click: () => {
          dialog.showMessageBox({
            type: 'info',
            title: 'Franz',
            message: 'Franz',
            detail: `Version: ${remote.app.getVersion()}\nRelease: ${process.versions.electron} / ${process.platform} / ${process.arch}`,
          });
        },
      });
    }

    const serviceTpl = this.serviceTpl;

    serviceTpl.unshift({
      label: 'Add new Service',
      accelerator: 'CmdOrCtrl+N',
      click: () => {
        this.actions.ui.openSettings({ path: 'recipes' });
      },
    }, {
      type: 'separator',
    });

    if (serviceTpl.length > 0) {
      tpl[isMac ? 3 : 2].submenu = toJS(this.serviceTpl);
    }

    const menu = Menu.buildFromTemplate(tpl);
    Menu.setApplicationMenu(menu);
  }

  @computed get serviceTpl() {
    const services = this.stores.services.enabled;

    if (this.stores.user.isLoggedIn) {
      return services.map((service, i) => ({
        label: this._getServiceName(service),
        accelerator: i <= 9 ? `CmdOrCtrl+${i + 1}` : null,
        type: 'radio',
        checked: service.isActive,
        click: () => {
          this.actions.service.setActive({ serviceId: service.id });
        },
      }));
    }

    return [];
  }

  _getServiceName(service) {
    if (service.name) {
      return service.name;
    }

    let name = service.recipe.name;

    if (service.team) {
      name = `${name} (${service.team})`;
    } else if (service.customUrl) {
      name = `${name} (${service.customUrl})`;
    }

    return name;
  }
}
<|endoftext|>


<commit_before>/*  Author: Valerio Battaglia (vabatta)
 *  Description: Function to create dialog box. You can have a dialog box open at once.
 *  Version: 1.0a
 *
 *  Params:
 *      title - Title of the dialog box (HTML format)
 *      content - Content of the dialog box (HTML format)
 *      draggable - Set draggable to dialog box, values: true, false (default: false)
 *      overlay - Set the overlay of the page, values: true, false (default: true)
 *      closeButton - Enable or disable the close button, values: true, false (default: false)
 *      buttonsAlign - Align of the buttons, values: left, center, right (default: center)
 *      buttons - Set buttons in the action bar (JSON format)
 *          name - Text of the button (JSON format)
 *              action - Function to bind to the button
 *      position - Set the initial position of the dialog box (JSON format)
 *          zone - Zone of the dialog box, values: left, center, right (default: center)
 *          offsetY - Top offset pixels
 *          offsetX - Left offset pixels
 *
 *   Goal for next versions:
 *      Add style param to set custom css to the dialog box controls
 *      Add possibility to resize window
 */

(function($) {
    $.Dialog = function(params) {
        if(!$.DialogOpened) {
            $.DialogOpened = true;
        } else {
            return false;
        }

        params = $.extend({'position':{'zone':'center'}}, params);

        var buttonsHTML = '
';
        } else {
            buttonsHTML += '>';
        }

        $.each(params.buttons, function(name,obj) {
            // Generating the markup for the buttons

            buttonsHTML += '<button>' + name + '</button>';

            if(!obj.action)
            {
                obj.action = function() {};
            }
        });

        buttonsHTML += '
';

        var markup = [
            // If overlay is true, set it

            (!params.overlay)?('
'):(''),
            '
',
            '
',
            params.title,
            (params.closeButton)?('
<button></button>
'):(''),
            '
',
            '
', params.content, '
',
            '
',
            buttonsHTML,
            '

',
            (!params.overlay)?('
'):('')
        ].join('');

        $(markup).hide().appendTo('body').show();

        // Setting initial position
        if(params.position.zone == "left")
        {
            $('#dialogBox').css("top", Math.max(0, (($(window).height() - $('#dialogBox').outerHeight()) / 3) +
                                                $(window).scrollTop()) + "px");
            $('#dialogBox').css("left", 0);
        }
        else if(params.position.zone == "right")
        {
            $('#dialogBox').css("top", Math.max(0, (($(window).height() - $('#dialogBox').outerHeight()) / 3) +
                                                $(window).scrollTop()) + "px");
            $('#dialogBox').css("left", Math.max(0, (($(window).width() - $('#dialogBox').outerWidth())) +
                                                            $(window).scrollLeft()) + "px");
        }
        else
        {
            $('#dialogBox').css("top", (params.position.offsetY)?(params.position.offsetY):(Math.max(0, (($(window).height() - $('#dialogBox').outerHeight()) / 3) +
                                                                                $(window).scrollTop()) + "px"));
            $('#dialogBox').css("left", (params.position.offsetX)?(params.position.offsetX):(Math.max(0, (($(window).width() - $('#dialogBox').outerWidth()) / 2) +
                                                                                $(window).scrollLeft()) + "px"));
        }

        if(params.draggable) {
            // Make draggable the window

            $('#dialogBox div.header').css('cursor', 'move').on("mousedown", function(e) {
                var $drag = $(this).addClass('active-handle').parent().addClass('draggable');

                var z_idx = $drag.css('z-index'),
                    drg_h = $drag.outerHeight(),
                    drg_w = $drag.outerWidth(),
                    pos_y = $drag.offset().top + drg_h - e.pageY,
                    pos_x = $drag.offset().left + drg_w - e.pageX;
                $drag.css('z-index', 99999).parents().on("mousemove", function(e) {
                    $('.draggable').offset({
                        top:e.pageY + pos_y - drg_h,
                        left:e.pageX + pos_x - drg_w
                    }).on("mouseup", function() {
                        $(this).removeClass('draggable').css('z-index', z_idx);
                    });
                });
                e.preventDefault(); // disable selection
            }).on("mouseup", function() {
                $(this).removeClass('active-handle').parent().removeClass('draggable');
            });
        }

        $('#dialogBox .header button').click(function() {
            // Bind close button to hide dialog

            $.Dialog.hide();
            return false;
        });

        var buttons = $('#dialogBox .action button'),
            i = 0;

        $.each(params.buttons,function(name,obj){
            buttons.eq(i++).click(function(){
                // Calling function and hide the dialog

                obj.action();
                $.Dialog.hide();
                return false;
            });
        });
    }

    $.Dialog.hide = function(){
        $('#dialogOverlay').fadeOut(function(){
            $.DialogOpened = false;
            $(this).remove();
        });
    }
})(jQuery);
<commit_msg>upd dialog.js<commit_after>/*  Author: Valerio Battaglia (vabatta)
 *  Description: Function to create dialog box. You can have a dialog box open at once.
 *  Version: 1.0a
 *
 *  Params:
 *      title - Title of the dialog box (HTML format)
 *      content - Content of the dialog box (HTML format)
 *      draggable - Set draggable to dialog box, available: true, false (default: false)
 *      overlay - Set the overlay of the page, available: true, false (default: true)
 *      closeButton - Enable or disable the close button, available: true, false (default: false)
 *      buttonsAlign - Align of the buttons, available: left, center, right (default: center)
 *      buttons - Set buttons in the action bar (JSON format)
 *          name - Text of the button (JSON format)
 *              action - Function to bind to the button
 *      position - Set the initial position of the dialog box (JSON format)
 *          zone - Zone of the dialog box, available: left, center, right (default: center)
 *          offsetY - Top offset pixels
 *          offsetX - Left offset pixels
 *
 *   Goal for next versions:
 *      Add style param to set custom css to the dialog box controls
 *      Add possibility to resize window
 */

(function($) {
    $.Dialog = function(params) {
        if(!$.DialogOpened) {
            $.DialogOpened = true;
        } else {
            return false;
        }

        params = $.extend({'position':{'zone':'center'},'overlay':true}, params);

        var buttonsHTML = '
';
        } else {
            buttonsHTML += '>';
        }

        $.each(params.buttons, function(name,obj) {
            // Generating the markup for the buttons

            buttonsHTML += '<button>' + name + '</button>';

            if(!obj.action)
            {
                obj.action = function() {};
            }
        });

        buttonsHTML += '
';

        var markup = [
            // If overlay is true, set it

            '
',
            '
',
            '
',
            params.title,
            (params.closeButton)?('
<button></button>
'):(''),
            '
',
            '
', params.content, '
',
            '
',
            buttonsHTML,
            '

'
        ].join('');

        $(markup).hide().appendTo('body').fadeIn();

        if(!params.overlay) {
            $('#dialogOverlay').css('background-color', 'rgba(255, 255, 255, 0)');
        }

        // Setting initial position
        if(params.position.zone == "left")
        {
            $('#dialogBox').css("top", Math.max(0, (($(window).height() - $('#dialogBox').outerHeight()) / 3) +
                                                $(window).scrollTop()) + "px");
            $('#dialogBox').css("left", 0);
        }
        else if(params.position.zone == "right")
        {
            $('#dialogBox').css("top", Math.max(0, (($(window).height() - $('#dialogBox').outerHeight()) / 3) +
                                                $(window).scrollTop()) + "px");
            $('#dialogBox').css("left", Math.max(0, (($(window).width() - $('#dialogBox').outerWidth())) +
                                                            $(window).scrollLeft()) + "px");
        }
        else
        {
            $('#dialogBox').css("top", (params.position.offsetY)?(params.position.offsetY):(Math.max(0, (($(window).height() - $('#dialogBox').outerHeight()) / 3) +
                                                                                $(window).scrollTop()) + "px"));
            $('#dialogBox').css("left", (params.position.offsetX)?(params.position.offsetX):(Math.max(0, (($(window).width() - $('#dialogBox').outerWidth()) / 2) +
                                                                                $(window).scrollLeft()) + "px"));
        }

        if(params.draggable) {
            // Make draggable the window

            $('#dialogBox div.header').css('cursor', 'move').on("mousedown", function(e) {
                var $drag = $(this).addClass('active-handle').parent().addClass('draggable');

                var z_idx = $drag.css('z-index'),
                    drg_h = $drag.outerHeight(),
                    drg_w = $drag.outerWidth(),
                    pos_y = $drag.offset().top + drg_h - e.pageY,
                    pos_x = $drag.offset().left + drg_w - e.pageX;
                $drag.css('z-index', 99999).parents().on("mousemove", function(e) {
                    $('.draggable').offset({
                        top:e.pageY + pos_y - drg_h,
                        left:e.pageX + pos_x - drg_w
                    }).on("mouseup", function() {
                        $(this).removeClass('draggable').css('z-index', z_idx);
                    });
                });
                e.preventDefault(); // disable selection
            }).on("mouseup", function() {
                $(this).removeClass('active-handle').parent().removeClass('draggable');
            });
        }

        $('#dialogBox .header button').click(function() {
            // Bind close button to hide dialog

            $.Dialog.hide();
            return false;
        });

        var buttons = $('#dialogBox .action button'),
            i = 0;

        $.each(params.buttons,function(name,obj){
            buttons.eq(i++).click(function(){
                // Calling function and hide the dialog

                obj.action();
                $.Dialog.hide();
                return false;
            });
        });
    }

    $.Dialog.hide = function(){
        $('#dialogOverlay').fadeOut(function(){
            $.DialogOpened = false;
            $(this).remove();
        });
    }
})(jQuery);
<|endoftext|>


<commit_before>"use strict";

/* This simple http server is made by malpower
 * for testing web applications of mobile.
 * This porgram is based on Node.js which is
 * a platform from joyent, inc. It allows user
 * building backend service programs with JS.
 * Node.js uses Google V8 engine, it means that
 * it will be fast.
 * You can also share your files on http with
 * this simple http server.Conf.js is a configure
 * file for this simple server.Anthor named
 * malpower who is a web programmer in Chengdu.
 * This is the seconds edition of malache, you
 * can visit [https://github.com/malpower/Malache2]
 * to get the latest code.
 * Email: malpower@ymail.com
 *
 ========================================================================================================================
 *

                            #                         |-------------------------------------------------------------|
                           ###                        |                                                             |
                      ##  #####  ##                   |         #                #                                  |
                       ###########                    |        ###             #####                                |
                         # \ \ #                      |      #######            # #                                 |
                         #\ \ \#                      |  ###############                                            |
                         # \ \ #                      |    ###########                                              |
                         #\ \ \#                      |     #### ####       #                                       |
                         # \ \ #                      |     ##     ##     #####                                     |
                         #\ \ \#                      |    #         #     # #                                      |
                         # \ \ #                      |                                                             |
                         #\ \ \#                      |    #           #                                            |
           |-------|-----#--|\-#-----|------|---|     |  #####       #####                                          |
          ========================================    |   # #         # #                                           |
         ==========================================   |                                                             |
        ============================================  |                                                             |
      =============================================== |-------------------------------------------------------------|

                           *      *           *        *****      ******
                          * *    * *         * *       *    *     *
                         *   *  *   *       *   *      *     *    ******
                         *     *    *      *******     *    *     *
                         *     *    *     *       *    *****      ******

                                             *****     *    *
                                               *       * *  *
                                               *       *  * *
                                               *       *   **
                                             *****     *    *

                          ***      *    *     *****     *    *         *
                         *   *     *    *       *       * *  *        * *
                         *         ******       *       *  * *       *   *
                         *   *     *    *       *       *   **      *******
                          ***      *    *     *****     *    *     *       *

=======================================================================================================================

 */

var tls=require("tls");
var net=require("net");
var conf=require("./conf");
let http=require("http");

if (conf.https!=true)
{
    console.log("Please set [https] section in conf.js!");
    process.exit(0);
}

var cp=require("child_process");
cp.fork("./malache2");

var server=tls.createServer(conf.tls,function(socket)
{
    console.log("SDLFJSDLKFJ");
    var m=net.connect({host: "127.0.0.1",port: conf.port},function()
    {
        console.log("CONNECTED");
        socket.pipe(m);
        m.pipe(socket);
    });
    m.on("error",function()
    {
        socket.end();
    });
    socket.on("error",function()
    {
        try
        {
            m.end();
        }
        catch (e)
        {
            //
        }
    });
});
server.on("error",function(e)
{
    console.log(e);
});
server.listen(conf.httpsPort);

let redirectServer=http.createServer(function(req,res)
{
    let domain=req.headers["host"];
    if (domain.indexOf(":")!=-1)
    {
        domain=domain.split(":")[0];
    }
    if (conf.httpsPort!=443)
    {
        domain+=":"+conf.httpsPort;
    }
    let url=domain+req.url;
    res.setHeader("Location","https://"+url);
    res.statusCode=302;
    res.end();
});
redirectServer.listen(conf.redirPort);
<commit_msg>Debug info removed<commit_after>"use strict";

/* This simple http server is made by malpower
 * for testing web applications of mobile.
 * This porgram is based on Node.js which is
 * a platform from joyent, inc. It allows user
 * building backend service programs with JS.
 * Node.js uses Google V8 engine, it means that
 * it will be fast.
 * You can also share your files on http with
 * this simple http server.Conf.js is a configure
 * file for this simple server.Anthor named
 * malpower who is a web programmer in Chengdu.
 * This is the seconds edition of malache, you
 * can visit [https://github.com/malpower/Malache2]
 * to get the latest code.
 * Email: malpower@ymail.com
 *
 ========================================================================================================================
 *

                            #                         |-------------------------------------------------------------|
                           ###                        |                                                             |
                      ##  #####  ##                   |         #                #                                  |
                       ###########                    |        ###             #####                                |
                         # \ \ #                      |      #######            # #                                 |
                         #\ \ \#                      |  ###############                                            |
                         # \ \ #                      |    ###########                                              |
                         #\ \ \#                      |     #### ####       #                                       |
                         # \ \ #                      |     ##     ##     #####                                     |
                         #\ \ \#                      |    #         #     # #                                      |
                         # \ \ #                      |                                                             |
                         #\ \ \#                      |    #           #                                            |
           |-------|-----#--|\-#-----|------|---|     |  #####       #####                                          |
          ========================================    |   # #         # #                                           |
         ==========================================   |                                                             |
        ============================================  |                                                             |
      =============================================== |-------------------------------------------------------------|

                           *      *           *        *****      ******
                          * *    * *         * *       *    *     *
                         *   *  *   *       *   *      *     *    ******
                         *     *    *      *******     *    *     *
                         *     *    *     *       *    *****      ******

                                             *****     *    *
                                               *       * *  *
                                               *       *  * *
                                               *       *   **
                                             *****     *    *

                          ***      *    *     *****     *    *         *
                         *   *     *    *       *       * *  *        * *
                         *         ******       *       *  * *       *   *
                         *   *     *    *       *       *   **      *******
                          ***      *    *     *****     *    *     *       *

=======================================================================================================================

 */

var tls=require("tls");
var net=require("net");
var conf=require("./conf");
let http=require("http");

if (conf.https!=true)
{
    console.log("Please set [https] section in conf.js!");
    process.exit(0);
}

var cp=require("child_process");
cp.fork("./malache2");

var server=tls.createServer(conf.tls,function(socket)
{
    var m=net.connect({host: "127.0.0.1",port: conf.port},function()
    {
        socket.pipe(m);
        m.pipe(socket);
    });
    m.on("error",function()
    {
        socket.end();
    });
    socket.on("error",function()
    {
        try
        {
            m.end();
        }
        catch (e)
        {
            //
        }
    });
});
server.on("error",function(e)
{
    console.log(e);
});
server.listen(conf.httpsPort);

let redirectServer=http.createServer(function(req,res)
{
    let domain=req.headers["host"];
    if (domain.indexOf(":")!=-1)
    {
        domain=domain.split(":")[0];
    }
    if (conf.httpsPort!=443)
    {
        domain+=":"+conf.httpsPort;
    }
    let url=domain+req.url;
    res.setHeader("Location","https://"+url);
    res.statusCode=302;
    res.end();
});
redirectServer.listen(conf.redirPort);
<|endoftext|>


<commit_before>const moment = require('moment')
const { EventEmitter } = require('events')
const Article = require('../../structs/Article.js')
const ArticleIDResolver = require('../../structs/ArticleIDResolver.js')
const { defaultConfigs } = require('../../util/checkConfig.js')
const dbCmds = require('../db/commands.js')
const log = require('../../util/logger.js')

/**
 * @typedef {Object} LinkData
 * @property {Object<string, Object>} rssList - Aggregated feed objects by feed ID from all guilds who use the same feed URL
 * @property {Object[]} articleList - Feed articles
 * @property {string[]} debugFeeds - Array of feed IDs to show debug info for
 * @property {string} link - The feed URL
 * @property {number} shardId - The shard ID of the parent process, if the bot is sharded
 * @property {Object} config - config.js values
 * @property {string} scheduleName - The calling process's schedule name
 * @property {number} runNum - Number of times this schedule has run so far
 * @property {string} useIdType - The ID type that should be used for article differentiation
 * @property {Object<string, Object[]>} [feedData] - Databaseless in-memory collection of all stored articles
 */

/**
 * @typedef {Object} SourceSettings
 * @property {boolean} checkTitle
 * @property {boolean} checkDate
 */

/**
 * @typedef {Object} FormattedArticle
 * @property {Object} source
 * @property {string} rssName
 */

class LinkLogic extends EventEmitter {
  /**
   * @param {LinkData} data
   */
  constructor (data) {
    super()
    const { rssList, articleList, debugFeeds, link, shardId, config, feedData, scheduleName, runNum, useIdType } = data // feedData is only defined when config.database.uri is set to a databaseless folder path
    this.rssList = rssList
    this.articleList = articleList
    this.link = link
    this.shardId = shardId
    this.config = config
    this.feedData = feedData
    this.scheduleName = scheduleName
    this.runNum = runNum
    this.useIdType = useIdType
    this.debug = new Set(debugFeeds || [])

    // The 4 properties below are set during run()
    this.dbTitles = new Set()
    this.dbIds = new Set()
    /**
     * @type {Object<string, Set<string>>}
     * */
    this.dbCustomComparisons = {}
    this.customComparisonsToUpdate = new Set()
    this.dbCustomComparisonsToDelete = new Set()

    this.cutoffDay = moment().subtract(config.feeds.cycleMaxAge, 'days')
    this.toUpdate = {} // Article's resolved IDs as key and the article as value

    /**
     * @type {Object<string, SourceSettings>}
     */
    this.memoizedSourceSettings = {}
  }

  static get DEFAULT_CONFIGS () {
    return defaultConfigs
  }

  /**
   * @typedef {object} DatabaseData
   * @property {Set<string>} dbIds - Set of stored database IDs
   * @property {Set<string>} dbTitles - Set of stored database titles
   * @property {Object<string, Set<string>>} dbCustomComparisons - Comparison names as keys, and their relevant article values in arrays
   */

  /**
   * @param {import('mongoose').Model|Object[]} collection
   * @param {Object} dbCustomComparisons
   * @returns {DatabaseData}
   */
  async getDataFromDocuments (collection) {
    const { dbIds, dbTitles, dbCustomComparisons } = this

    const docs = await dbCmds.findAll(collection)
    for (const doc of docs) {
      // Push the main data for built in comparisons
      dbIds.add(doc.id)
      dbTitles.add(doc.title)

      // Now deal with custom comparisons
      const docCustomComparisons = doc.customComparisons
      if (docCustomComparisons !== undefined && Object.keys(docCustomComparisons).length > 0) {
        for (const articleProperty in docCustomComparisons) { // n = customComparison's name (such as description, author, etc.)
          const value = docCustomComparisons[articleProperty]
          if (!dbCustomComparisons[articleProperty]) {
            dbCustomComparisons[articleProperty] = new Set([value])
          } else {
            dbCustomComparisons[articleProperty].add(value)
          }
        }
      }
    }

    return { dbIds, dbTitles }
  }

  /**
   * @param {Object[]} collection
   * @param {Set<string>} dbIds
   * @param {string} useIdType
   * @param {Object[]} articleList
   * @param {Object<string, Set<string>>} dbCustomComparisons
   * @param {Set<string>} dbCustomComparisonsToDelete
   * @returns {Set<string>}
   */
  async articleListTasks (collection) {
    const { dbIds, useIdType, articleList, dbCustomComparisons, dbCustomComparisonsToDelete } = this
    const toInsert = []

    const checkCustomComparisons = Object.keys(dbCustomComparisons).length > 0
    for (const article of articleList) {
      article._id = ArticleIDResolver.getIDTypeValue(article, useIdType)
      if (!dbIds.has(article._id)) {
        toInsert.push(article)
      }
      if (!checkCustomComparisons) {
        continue
      }
      // Iterate over the values stored in the db, and see if the custom comparison names in the db exist in any of the articles. If they do, then it is marked valid
      for (const compName in dbCustomComparisons) {
        const validValue = article[compName] !== undefined && (typeof article[compName] !== 'object' || article[compName] === null))
        if (!validValue) {
          dbCustomComparisonsToDelete.add(compName)
        }
      }
    }

    await dbCmds.bulkInsert(collection, toInsert)
  }

  /**
   * @param {Object} article
   * @param {Object} source
   * @param {string} rssName
   * @returns {FormattedArticle}
   */
  static formatArticle (article, source, rssName) {
    // For ArticleMessage to access once ScheduleManager receives this article
    article._delivery = {
      guildId: source.guildId, // Originally set in source through FeedSchedule.js
      dateSettings: source.dateSettings, // Not the actual date, but settings concerning date format/language/timezone. Originally set in source through FeedSchedule.js
      rssName,
      channelId: source.channel,
      source
    }

    return article
  }

  /**
   * @param {Object} config - The default config.json
   * @param {Object} source - User's source config
   * @param {string} rssName - Feed ID
   * @returns {SourceSettings}
   */
  determineArticleChecks (config, source, rssName) {
    const memoized = this.memoizedSourceSettings[rssName]
    if (memoized) {
      return this.memoizedSourceSettings[rssName]
    }

    const globalDateCheck = config.feeds.checkDates != null ? config.feeds.checkDates : LinkLogic.DEFAULT_CONFIGS.feeds.checkDates.default
    const localDateCheck = source.checkDates
    const checkDate = typeof localDateCheck !== 'boolean' ? globalDateCheck : localDateCheck

    const globalTitleCheck = config.feeds.checkTitles != null ? config.feeds.checkTitles : LinkLogic.DEFAULT_CONFIGS.feeds.checkTitles.default
    const localTitleCheck = source.checkTitles
    const checkTitle = typeof globalTitleCheck !== 'boolean' ? globalTitleCheck : localTitleCheck

    if (!memoized) {
      this.memoizedSourceSettings[rssName] = { checkDate, checkTitle }
    }

    return this.memoizedSourceSettings[rssName]
  }

  /**
   * @param {Object} source - Feed object
   */
  validateCustomComparisons (source) {
    const { dbCustomComparisons, dbCustomComparisonsToDelete, customComparisonsToUpdate } = this
    const { customComparisons } = source // Array of names
    if (!Array.isArray(customComparisons)) {
      return
    }
    for (let n = customComparisons.length - 1; n >= 0; --n) {
      const name = customComparisons[n]
      if (name === 'title' || name === 'guid' || name === 'pubdate') { // Forbidden custom comparisons since these are already used by the bot
        customComparisons.splice(n, 1)
        continue
      }
      if (!dbCustomComparisons[name] && !dbCustomComparisonsToDelete.has(name) && !customComparisonsToUpdate.has(name)) {
        customComparisonsToUpdate.add(name) // Since this custom comparison wasn't found in the db, it might be uninitialized or not found in any articles (as checked previously)
      }
    }
  }

  /**
   * @event LinkLogic#article
   * @type {FormattedArticle}
   */

  /**
   * @param {string} rssName
   * @param {Object} source
   * @param {Object} article
   * @param {Set<string>} sentTitles - Previous titles stored in case of title checks
   * @param {boolean} toDebug - Whether to log progress
   * @fires LinkLogic#article
   */
  checkIfNewArticle (rssName, source, article, sentTitles, toDebug) {
    const { config, dbIds, dbTitles, runNum, cutoffDay } = this
    const { checkDate, checkTitle } = this.determineArticleChecks(config, source)

    const matchedID = dbIds.has(article._id)
    const matchedTitle = checkTitle && (dbTitles.has(article.title) || sentTitles.has(article.title))
    const matchedDate = checkDate && ((!article.pubdate || article.pubdate.toString() === 'Invalid Date') || (article.pubdate && article.pubdate.toString() !== 'Invalid Date' && article.pubdate < cutoffDay))
    let seen = false
    if (matchedID || matchedTitle || matchedDate) {
      if (toDebug) log.debug.info(`${rssName}: Not sending article (ID: ${article._id}, TITLE: ${article.title}) Matched ${matchedID ? 'ID' : matchedTitle ? 'title' : matchedDate ? 'date' : 'UNKNOWN CASE'}.`)
      seen = true
    } else if (checkTitle && article.title) {
      sentTitles.add(article.title)
    }

    if (runNum === 0 && config.feeds.sendOldOnFirstCycle === false) {
      if (toDebug) {
        log.debug.warning(`${rssName}: Not sending article (ID: ${article._id}, TITLE: ${article.title}), config.feeds.sendOldOnFirstCycle is false`)
      }
      return
    }

    // Check for extra user-specified comparisons
    if (!seen) {
      this.emit(LinkLogic.formatArticle(article, source, rssName))
      return
    }

    this.checkIfNewArticleByCC(rssName, source, article, toDebug)
  }

  /**
   * @param {string} rssName
   * @param {Object} source
   * @param {Object} article
   * @param {boolean} toDebug - Whether to log progress
   */
  checkIfNewArticleByCC (rssName, source, article, toDebug) {
    const { dbCustomComparisons, toUpdate, customComparisonsToUpdate, dbCustomComparisonsToDelete } = this
    const { customComparisons } = source
    if (!Array.isArray(customComparisons)) {
      return
    }
    for (const comparisonName of customComparisons) {
      const dbCustomComparisonValues = dbCustomComparisons[comparisonName] // Might be an array of descriptions, authors, etc.
      const articleCustomComparisonValue = article[comparisonName]

      const pendingComparisonUpdate = customComparisonsToUpdate.has(comparisonName)
      const noComparisonsAvailable = !dbCustomComparisonValues
      const articleValueFound = dbCustomComparisonValues && dbCustomComparisonValues.has(articleCustomComparisonValue)
      const articleValueInvalid = dbCustomComparisonsToDelete.has(comparisonName)

      // Prepare it for update in the database
      if (pendingComparisonUpdate) {
        if (!toUpdate[article._id]) {
          if (!article.customComparisons) {
            article.customComparisons = {}
          }
          article.customComparisons[comparisonName] = articleCustomComparisonValue
          toUpdate[article._id] = article
        } else {
          toUpdate[article._id].customComparisons[customComparisonName] = articleCustomComparisonValue
        }
      } else if (articleValueInvalid) {
        delete article.customComparisons[comparisonName]
        toUpdate[article._id] = article
      }

      if (noComparisonsAvailable || articleValueFound || articleValueInvalid) {
        if (toDebug) {
          log.debug.info(`${rssName}: Not sending article (ID: ${article._id}, TITLE: ${article.title}) due to custom comparison check for ${comparisonName}. noComparisonsAvailable: ${noComparisonsAvailable}, pendingComparisonUpdate: ${pendingComparisonUpdate}, articleValueFound: ${articleValueFound}, articleValueInvalid: ${articleValueInvalid}.\ndbCustomComparisonValues:\n${dbCustomComparisonValues ? JSON.stringify(dbCustomComparisonValues, null, 2) : undefined}`)
        }
        return
      }

      if (toDebug) {
        log.debug.info(`${rssName}: Sending article (ID: ${article._id}, TITLE: ${article.title}) due to custom comparison check for ${comparisonName}`)
      }
      this.emit(LinkLogic.formatArticle(article, source, rssName))
    }
  }

  async run () {
    const { scheduleName, link, shardId, feedData, rssList, toUpdate, dbIds } = this
    if (!scheduleName) {
      throw new Error('Missing schedule name for shared logic')
    }
    const collectionID = Article.getCollectionID(link, shardId, scheduleName)
    const Feed = Article.modelByID(collectionID)
    const feedCollectionId = feedData ? collectionID : undefined
    const feedCollection = feedData ? (feedData[feedCollectionId] || []) : undefined

    await LinkLogic.getDataFromDocuments(feedCollection || Feed)
    await LinkLogic.articleListTasks(feedCollection || Feed)

    if (dbIds.size === 0) {
      // Tthe database collection has not been initialized. If a feed has 100 articles, skip everything past this point so it doesn't send a crazy number of articles.
      return { link, feedCollection, feedCollectionId }
    }

    for (const rssName in rssList) {
      const source = rssList[rssName]
      const { articleList, dbIds, dbTitles } = this
      const totalArticles = this.articleList.length
      const toDebug = this.debug.has(rssName)

      this.validateCustomComparisons(source)

      if (toDebug) {
        log.debug.info(`${rssName}: Processing collection. Total article list length: ${totalArticles}.\nDatabase IDs:\n${JSON.stringify(Array.from(dbIds), null, 2)}\nDatabase Titles:\n${JSON.stringify(Array.from(dbTitles), null, 2)}`)
      }

      const sentTitles = new Set()
      for (let a = totalArticles - 1; a >= 0; --a) { // Loop from oldest to newest so the queue that sends articleMessages work properly, sending the older ones first
        this.checkIfNewArticle(rssName, source, articleList[a], sentTitles, toDebug)
      }
    }

    // Update anything if necessary
    const updates = []
    for (const id in toUpdate) {
      const article = toUpdate[id]
      updates.push(dbCmds.update(feedCollection || Feed, article))
    }
    await Promise.all(updates)
    return { link, feedCollection, feedCollectionId }
  }
}

module.exports = LinkLogic
<commit_msg>Document var types in LinkLogic<commit_after>const moment = require('moment')
const { EventEmitter } = require('events')
const Article = require('../../structs/Article.js')
const ArticleIDResolver = require('../../structs/ArticleIDResolver.js')
const { defaultConfigs } = require('../../util/checkConfig.js')
const dbCmds = require('../db/commands.js')
const log = require('../../util/logger.js')

/**
 * @typedef {Object} LinkData
 * @property {Object<string, Object>} rssList - Aggregated feed objects by feed ID from all guilds who use the same feed URL
 * @property {Object[]} articleList - Feed articles
 * @property {string[]} debugFeeds - Array of feed IDs to show debug info for
 * @property {string} link - The feed URL
 * @property {number} shardId - The shard ID of the parent process, if the bot is sharded
 * @property {Object} config - config.js values
 * @property {string} scheduleName - The calling process's schedule name
 * @property {number} runNum - Number of times this schedule has run so far
 * @property {string} useIdType - The ID type that should be used for article differentiation
 * @property {Object<string, Object[]>} [feedData] - Databaseless in-memory collection of all stored articles
 */

/**
 * @typedef {Object} SourceSettings
 * @property {boolean} checkTitle
 * @property {boolean} checkDate
 */

/**
 * @typedef {Object} FormattedArticle
 * @property {Object} source
 * @property {string} rssName
 */

class LinkLogic extends EventEmitter {
  /**
   * @param {LinkData} data
   */
  constructor (data) {
    super()
    const { rssList, articleList, debugFeeds, link, shardId, config, feedData, scheduleName, runNum, useIdType } = data // feedData is only defined when config.database.uri is set to a databaseless folder path
    this.rssList = rssList
    this.articleList = articleList
    this.link = link
    this.shardId = shardId
    this.config = config
    this.feedData = feedData
    this.scheduleName = scheduleName
    this.runNum = runNum
    this.useIdType = useIdType

    /**
     * @type {Set<string, string>}
     */
    this.debug = new Set(debugFeeds || [])

    /**
     * @type {Set<string, string>}
     */
    this.dbTitles = new Set()

    /**
     * @type {Set<string, string>}
     */
    this.dbIds = new Set()

    /**
     * @type {Object<string, Set<string>>}
     * */
    this.dbCustomComparisons = {}

    /**
     * @type {Set<string, string>}
     */
    this.customComparisonsToUpdate = new Set()

    /**
     * @type {Set<string, string>}
     */
    this.dbCustomComparisonsToDelete = new Set()

    /**
     * @type {Object<string, Object>}
     */
    this.toUpdate = {} // Article's resolved IDs as key and the article as value

    /**
     * @type {Object<string, SourceSettings>}
     */
    this.memoizedSourceSettings = {}

    this.cutoffDay = moment().subtract(config.feeds.cycleMaxAge, 'days')
  }

  static get DEFAULT_CONFIGS () {
    return defaultConfigs
  }

  /**
   * @typedef {object} DatabaseData
   * @property {Set<string>} dbIds - Set of stored database IDs
   * @property {Set<string>} dbTitles - Set of stored database titles
   * @property {Object<string, Set<string>>} dbCustomComparisons - Comparison names as keys, and their relevant article values in arrays
   */

  /**
   * @param {import('mongoose').Model|Object[]} collection
   * @param {Object} dbCustomComparisons
   * @returns {DatabaseData}
   */
  async getDataFromDocuments (collection) {
    const { dbIds, dbTitles, dbCustomComparisons } = this

    const docs = await dbCmds.findAll(collection)
    for (const doc of docs) {
      // Push the main data for built in comparisons
      dbIds.add(doc.id)
      dbTitles.add(doc.title)

      // Now deal with custom comparisons
      const docCustomComparisons = doc.customComparisons
      if (docCustomComparisons !== undefined && Object.keys(docCustomComparisons).length > 0) {
        for (const articleProperty in docCustomComparisons) { // n = customComparison's name (such as description, author, etc.)
          const value = docCustomComparisons[articleProperty]
          if (!dbCustomComparisons[articleProperty]) {
            dbCustomComparisons[articleProperty] = new Set([value])
          } else {
            dbCustomComparisons[articleProperty].add(value)
          }
        }
      }
    }

    return { dbIds, dbTitles }
  }

  /**
   * @param {Object[]} collection
   * @param {Set<string>} dbIds
   * @param {string} useIdType
   * @param {Object[]} articleList
   * @param {Object<string, Set<string>>} dbCustomComparisons
   * @param {Set<string>} dbCustomComparisonsToDelete
   * @returns {Set<string>}
   */
  async articleListTasks (collection) {
    const { dbIds, useIdType, articleList, dbCustomComparisons, dbCustomComparisonsToDelete } = this
    const toInsert = []

    const checkCustomComparisons = Object.keys(dbCustomComparisons).length > 0
    for (const article of articleList) {
      article._id = ArticleIDResolver.getIDTypeValue(article, useIdType)
      if (!dbIds.has(article._id)) {
        toInsert.push(article)
      }
      if (!checkCustomComparisons) {
        continue
      }
      // Iterate over the values stored in the db, and see if the custom comparison names in the db exist in any of the articles. If they do, then it is marked valid
      for (const compName in dbCustomComparisons) {
        const validValue = article[compName] !== undefined && (typeof article[compName] !== 'object' || article[compName] === null))
        if (!validValue) {
          dbCustomComparisonsToDelete.add(compName)
        }
      }
    }

    await dbCmds.bulkInsert(collection, toInsert)
  }

  /**
   * @param {Object} article
   * @param {Object} source
   * @param {string} rssName
   * @returns {FormattedArticle}
   */
  static formatArticle (article, source, rssName) {
    // For ArticleMessage to access once ScheduleManager receives this article
    article._delivery = {
      guildId: source.guildId, // Originally set in source through FeedSchedule.js
      dateSettings: source.dateSettings, // Not the actual date, but settings concerning date format/language/timezone. Originally set in source through FeedSchedule.js
      rssName,
      channelId: source.channel,
      source
    }

    return article
  }

  /**
   * @param {Object} config - The default config.json
   * @param {Object} source - User's source config
   * @param {string} rssName - Feed ID
   * @returns {SourceSettings}
   */
  determineArticleChecks (config, source, rssName) {
    const memoized = this.memoizedSourceSettings[rssName]
    if (memoized) {
      return this.memoizedSourceSettings[rssName]
    }

    const globalDateCheck = config.feeds.checkDates != null ? config.feeds.checkDates : LinkLogic.DEFAULT_CONFIGS.feeds.checkDates.default
    const localDateCheck = source.checkDates
    const checkDate = typeof localDateCheck !== 'boolean' ? globalDateCheck : localDateCheck

    const globalTitleCheck = config.feeds.checkTitles != null ? config.feeds.checkTitles : LinkLogic.DEFAULT_CONFIGS.feeds.checkTitles.default
    const localTitleCheck = source.checkTitles
    const checkTitle = typeof globalTitleCheck !== 'boolean' ? globalTitleCheck : localTitleCheck

    if (!memoized) {
      this.memoizedSourceSettings[rssName] = { checkDate, checkTitle }
    }

    return this.memoizedSourceSettings[rssName]
  }

  /**
   * @param {Object} source - Feed object
   */
  validateCustomComparisons (source) {
    const { dbCustomComparisons, dbCustomComparisonsToDelete, customComparisonsToUpdate } = this
    const { customComparisons } = source // Array of names
    if (!Array.isArray(customComparisons)) {
      return
    }
    for (let n = customComparisons.length - 1; n >= 0; --n) {
      const name = customComparisons[n]
      if (name === 'title' || name === 'guid' || name === 'pubdate') { // Forbidden custom comparisons since these are already used by the bot
        customComparisons.splice(n, 1)
        continue
      }
      if (!dbCustomComparisons[name] && !dbCustomComparisonsToDelete.has(name) && !customComparisonsToUpdate.has(name)) {
        customComparisonsToUpdate.add(name) // Since this custom comparison wasn't found in the db, it might be uninitialized or not found in any articles (as checked previously)
      }
    }
  }

  /**
   * @event LinkLogic#article
   * @type {FormattedArticle}
   */

  /**
   * @param {string} rssName
   * @param {Object} source
   * @param {Object} article
   * @param {Set<string>} sentTitles - Previous titles stored in case of title checks
   * @param {boolean} toDebug - Whether to log progress
   * @fires LinkLogic#article
   */
  checkIfNewArticle (rssName, source, article, sentTitles, toDebug) {
    const { config, dbIds, dbTitles, runNum, cutoffDay } = this
    const { checkDate, checkTitle } = this.determineArticleChecks(config, source)

    const matchedID = dbIds.has(article._id)
    const matchedTitle = checkTitle && (dbTitles.has(article.title) || sentTitles.has(article.title))
    const matchedDate = checkDate && ((!article.pubdate || article.pubdate.toString() === 'Invalid Date') || (article.pubdate && article.pubdate.toString() !== 'Invalid Date' && article.pubdate < cutoffDay))
    let seen = false
    if (matchedID || matchedTitle || matchedDate) {
      if (toDebug) log.debug.info(`${rssName}: Not sending article (ID: ${article._id}, TITLE: ${article.title}) Matched ${matchedID ? 'ID' : matchedTitle ? 'title' : matchedDate ? 'date' : 'UNKNOWN CASE'}.`)
      seen = true
    } else if (checkTitle && article.title) {
      sentTitles.add(article.title)
    }

    if (runNum === 0 && config.feeds.sendOldOnFirstCycle === false) {
      if (toDebug) {
        log.debug.warning(`${rssName}: Not sending article (ID: ${article._id}, TITLE: ${article.title}), config.feeds.sendOldOnFirstCycle is false`)
      }
      return
    }

    // Check for extra user-specified comparisons
    if (!seen) {
      this.emit(LinkLogic.formatArticle(article, source, rssName))
      return
    }

    this.checkIfNewArticleByCC(rssName, source, article, toDebug)
  }

  /**
   * @param {string} rssName
   * @param {Object} source
   * @param {Object} article
   * @param {boolean} toDebug - Whether to log progress
   */
  checkIfNewArticleByCC (rssName, source, article, toDebug) {
    const { dbCustomComparisons, toUpdate, customComparisonsToUpdate, dbCustomComparisonsToDelete } = this
    const { customComparisons } = source
    if (!Array.isArray(customComparisons)) {
      return
    }
    for (const comparisonName of customComparisons) {
      const dbCustomComparisonValues = dbCustomComparisons[comparisonName] // Might be an array of descriptions, authors, etc.
      const articleCustomComparisonValue = article[comparisonName]

      const pendingComparisonUpdate = customComparisonsToUpdate.has(comparisonName)
      const noComparisonsAvailable = !dbCustomComparisonValues
      const articleValueFound = dbCustomComparisonValues && dbCustomComparisonValues.has(articleCustomComparisonValue)
      const articleValueInvalid = dbCustomComparisonsToDelete.has(comparisonName)

      // Prepare it for update in the database
      if (pendingComparisonUpdate) {
        if (!toUpdate[article._id]) {
          if (!article.customComparisons) {
            article.customComparisons = {}
          }
          article.customComparisons[comparisonName] = articleCustomComparisonValue
          toUpdate[article._id] = article
        } else {
          toUpdate[article._id].customComparisons[customComparisonName] = articleCustomComparisonValue
        }
      } else if (articleValueInvalid) {
        delete article.customComparisons[comparisonName]
        toUpdate[article._id] = article
      }

      if (noComparisonsAvailable || articleValueFound || articleValueInvalid) {
        if (toDebug) {
          log.debug.info(`${rssName}: Not sending article (ID: ${article._id}, TITLE: ${article.title}) due to custom comparison check for ${comparisonName}. noComparisonsAvailable: ${noComparisonsAvailable}, pendingComparisonUpdate: ${pendingComparisonUpdate}, articleValueFound: ${articleValueFound}, articleValueInvalid: ${articleValueInvalid}.\ndbCustomComparisonValues:\n${dbCustomComparisonValues ? JSON.stringify(dbCustomComparisonValues, null, 2) : undefined}`)
        }
        return
      }

      if (toDebug) {
        log.debug.info(`${rssName}: Sending article (ID: ${article._id}, TITLE: ${article.title}) due to custom comparison check for ${comparisonName}`)
      }
      this.emit(LinkLogic.formatArticle(article, source, rssName))
    }
  }

  async run () {
    const { scheduleName, link, shardId, feedData, rssList, toUpdate, dbIds } = this
    if (!scheduleName) {
      throw new Error('Missing schedule name for shared logic')
    }
    const collectionID = Article.getCollectionID(link, shardId, scheduleName)
    const Feed = Article.modelByID(collectionID)
    const feedCollectionId = feedData ? collectionID : undefined
    const feedCollection = feedData ? (feedData[feedCollectionId] || []) : undefined

    await LinkLogic.getDataFromDocuments(feedCollection || Feed)
    await LinkLogic.articleListTasks(feedCollection || Feed)

    if (dbIds.size === 0) {
      // Tthe database collection has not been initialized. If a feed has 100 articles, skip everything past this point so it doesn't send a crazy number of articles.
      return { link, feedCollection, feedCollectionId }
    }

    for (const rssName in rssList) {
      const source = rssList[rssName]
      const { articleList, dbIds, dbTitles } = this
      const totalArticles = this.articleList.length
      const toDebug = this.debug.has(rssName)

      this.validateCustomComparisons(source)

      if (toDebug) {
        log.debug.info(`${rssName}: Processing collection. Total article list length: ${totalArticles}.\nDatabase IDs:\n${JSON.stringify(Array.from(dbIds), null, 2)}\nDatabase Titles:\n${JSON.stringify(Array.from(dbTitles), null, 2)}`)
      }

      const sentTitles = new Set()
      for (let a = totalArticles - 1; a >= 0; --a) { // Loop from oldest to newest so the queue that sends articleMessages work properly, sending the older ones first
        this.checkIfNewArticle(rssName, source, articleList[a], sentTitles, toDebug)
      }
    }

    // Update anything if necessary
    const updates = []
    for (const id in toUpdate) {
      const article = toUpdate[id]
      updates.push(dbCmds.update(feedCollection || Feed, article))
    }
    await Promise.all(updates)
    return { link, feedCollection, feedCollectionId }
  }
}

module.exports = LinkLogic
<|endoftext|>


<commit_before>  var music1 = document.getElementById("audiotag1");
  var music2 = document.getElementById("audiotag2");
  function pauseAudio() {
    $.each($('audio'), function () { this.pause(); });
	}

$(function() {
  $('.navbar').load('../navbar.html');
  $('#landing').load('../landing.html');
});

$(window).load(function() {
	console.log("hopeful");
  $(".navbar").on("click", "a", function(event){
  	event.preventDefault();
  	$("#landing").hide();
  	$("#canvas").remove();
  	pauseAudio();

  	var link = $(this).attr("href");
  	var request = $.ajax({url: link});
  	request.done(function(response){
	  	if(link == "http://peterhinners.github.io"){
	  		$(".main-content").html("");
	  		$("#landing").show();

	  	} else {
	  		$(".main-content").html(response);
	  	}
  	});
	});

	$(".main-content").on("click", "a", function(event){
		var link = $(this).attr("href");
		if(link == "http://catbreak.herokuapp.com"){
			// } else if(link == "http://peterhinners.github.io/blog/TomJerry.html"){
			// event.preventDefault();
			// $(".navbar").hide();
			// // $(".main-content").html("");
			// var request = $.ajax({url: link});
			//   request.done(function(response){
			//   	// $(".navbar").hide();
			//   	$(".main-content").html(response);
			//   });
			}	else {
			event.preventDefault();
			$(".main-content").html("");
			var request = $.ajax({url: link});
			  request.done(function(response){
			  	$(".main-content").html(response);
			  });
		}

	});

});

window.addEventListener("keydown", function(e) {
    // space and arrow keys
    if([32, 37, 38, 39, 40].indexOf(e.keyCode) > -1) {
        e.preventDefault();
    }
}, false);

<commit_msg>sound<commit_after>  var music1 = document.getElementById("audiotag1");
  var music2 = document.getElementById("audiotag2");
  function pauseAudio() {
    $.each($('audio'), function () {
    	$(this).AudioPlayerV1('pause');
		});
	}

$(function() {
  $('.navbar').load('../navbar.html');
  $('#landing').load('../landing.html');
});

$(window).load(function() {
	console.log("hmm");
  $(".navbar").on("click", "a", function(event){
  	event.preventDefault();
  	$("#landing").hide();
  	$("#canvas").remove();
  	pauseAudio();

  	var link = $(this).attr("href");
  	var request = $.ajax({url: link});
  	request.done(function(response){
	  	if(link == "http://peterhinners.github.io"){
	  		$(".main-content").html("");
	  		$("#landing").show();
	  		pauseAudio();

	  	} else {
	  		$(".main-content").html(response);
	  		pauseAudio();
	  	}
  	});
	});

	$(".main-content").on("click", "a", function(event){
		var link = $(this).attr("href");
		if(link == "http://catbreak.herokuapp.com"){
			// } else if(link == "http://peterhinners.github.io/blog/TomJerry.html"){
			// event.preventDefault();
			// $(".navbar").hide();
			// // $(".main-content").html("");
			// var request = $.ajax({url: link});
			//   request.done(function(response){
			//   	// $(".navbar").hide();
			//   	$(".main-content").html(response);
			//   });
			}	else {
			event.preventDefault();
			$(".main-content").html("");
			var request = $.ajax({url: link});
			  request.done(function(response){
			  	pauseAudio();
			  	$(".main-content").html(response);
			  });
		}

	});

});

window.addEventListener("keydown", function(e) {
    // space and arrow keys
    if([32, 37, 38, 39, 40].indexOf(e.keyCode) > -1) {
        e.preventDefault();
    }
}, false);

<|endoftext|>