<commit_before>const electron = require('electron');

const app = electron.app;
const Menu = electron.Menu;
const MenuItem = electron.MenuItem;

module.exports = {
  setup: function (config) {
    var name = app.getName();

    var app_menu = {
      label: name,
      submenu: [
        {
          label: 'About ' + name,
          role: 'about'
        },
        {
          type: 'separator'
        },
        {
          label: 'Preferences…',
          accelerator: 'Cmd+,',
          click: function (item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.webContents.executeJavaScript('if (SESSIONVIEW) { SESSIONVIEW.openSettings(); }', true);
            }
          }
        },
        {
          type: 'separator'
        },
        {
          label: 'Services',
          role: 'services',
          submenu: []
        },
        {
          type: 'separator'
        },
        {
          label: 'Hide ' + name,
          accelerator: 'Cmd+H',
          role: 'hide'
        },
        {
          label: 'Hide Others',
          accelerator: 'Cmd+Alt+H',
          role: 'hideothers'
        },
        {
          label: 'Show All',
          role: 'unhide'
        },
        {
          type: 'separator'
        },
        {
          label: 'Quit',
          accelerator: 'Cmd+Q',
          click: function(item, focusedWindow) {
            app.userInitiatedQuit = true;
            app.quit();
          }
        }
      ]
    };
    var file_menu = {
        label: 'File',
        submenu: [
            {
              label: 'Open in Browser',
              click: function(item, focusedWindow) {
                var url = focusedWindow ? focusedWindow.webContents.getURL() : config.get('host');
                require('electron').shell.openExternal(url);
              }
            },
            {
              type: 'separator'
            },
            {
              label: 'Add a Network…',
              click: function (item, focusedWindow) {
                if (focusedWindow) {
                  focusedWindow.webContents.executeJavaScript('if (SESSIONVIEW) { SESSIONVIEW.addNetwork(); }', true);
                }
              }
            },
            {
              type: 'separator'
            },
            {
              label: 'Jump to…',
              accelerator: 'CmdOrCtrl+K',
              click: function (item, focusedWindow) {
                if (focusedWindow) {
                  focusedWindow.webContents.executeJavaScript('if (SESSIONVIEW) { SESSIONVIEW.channelSwitcher.toggle(); }', true);
                }
              }
            },
            {
              label: 'Select Next in List',
              accelerator: 'Alt+Down',
              click: function (item, focusedWindow) {
                if (focusedWindow) {
                  focusedWindow.webContents.executeJavaScript('if (SESSIONVIEW) { SESSIONVIEW.sidebar.bufferList.selectNextBuffer(); }', true);
                }
              }
            },
            {
              label: 'Select Previous in List',
              accelerator: 'Alt+Up',
              click: function (item, focusedWindow) {
                if (focusedWindow) {
                  focusedWindow.webContents.executeJavaScript('if (SESSIONVIEW) { SESSIONVIEW.sidebar.bufferList.selectPreviousBuffer(); }', true);
                }
              }
            },
            {
              label: 'Select Next Unread in List',
              accelerator: 'Alt+Shift+Down',
              click: function (item, focusedWindow) {
                if (focusedWindow) {
                  focusedWindow.webContents.executeJavaScript('if (SESSIONVIEW) { SESSIONVIEW.sidebar.bufferList.selectNextUnreadBuffer(); }', true);
                }
              }
            },
            {
              label: 'Select Previous Unread in List',
              accelerator: 'Alt+Shift+Up',
              click: function (item, focusedWindow) {
                if (focusedWindow) {
                  focusedWindow.webContents.executeJavaScript('if (SESSIONVIEW) { SESSIONVIEW.sidebar.bufferList.selectPreviousUnreadBuffer(); }', true);
                }
              }
            },
            {
              type: 'separator'
            },
            {
              label: 'Mark Current as Read',
              accelerator: 'Esc',
              click: function (item, focusedWindow) {
                if (focusedWindow) {
                  focusedWindow.webContents.executeJavaScript('if (SESSION && SESSION.currentBuffer) { SESSION.currentBuffer.read(); }', true);
                }
              }
            },
            {
              label: 'Mark All as Read',
              accelerator: 'Shift+Esc',
              click: function (item, focusedWindow) {
                if (focusedWindow) {
                  focusedWindow.webContents.executeJavaScript('if (SESSION) { SESSION.markAllAsRead(); }', true);
                }
              }
            },
            {
              type: 'separator'
            },
            {
              label: 'Upload a File…',
              click: function (item, focusedWindow) {
                if (focusedWindow) {
                  focusedWindow.webContents.executeJavaScript('if (SESSION && SESSION.currentBuffer) { SESSION.currentBuffer.trigger("uploadPrompt"); }', true);
                }
              }
            },
            {
              label: 'Start a Pastebin…',
              click: function (item, focusedWindow) {
                if (focusedWindow) {
                  focusedWindow.webContents.executeJavaScript('if (SESSION && SESSION.currentBuffer) { SESSION.currentBuffer.trigger("pastePrompt"); }', true);
                }
              }
            }
        ]
    };
    if (process.platform != 'darwin') {
      file_menu.submenu.push({
        type: 'separator'
      });
      file_menu.submenu.push({
        label: 'Preferences…',
        accelerator: 'Ctrl+,',
        click: function (item, focusedWindow) {
          if (focusedWindow) {
            focusedWindow.webContents.executeJavaScript('if (SESSIONVIEW) { SESSIONVIEW.openSettings(); }', true);
          }
        }
      });
      file_menu.submenu.push({
        type: 'separator'
      });
      file_menu.submenu.push({
        label: 'Show in Tray',
        type:  'checkbox',
        checked: Boolean(app.config.get('tray')),
        click: function(item, focusedWindow){
            app.config.set('tray', item.checked);
            app.toggleTray();
        }
      });
      file_menu.submenu.push({
        label: 'Quit',
        accelerator: (function() {
            if (process.platform == 'win32') {
                return 'Alt+F4';
            } else {
                return 'Ctrl+Q';
            }
        })(),
        click: function(item, focusedWindow) {
          app.quit();
        }
      });
    }

    var edit_menu = {
      label: 'Edit',
      submenu: [
        {
          label: 'Undo',
          accelerator: 'CmdOrCtrl+Z',
          role: 'undo'
        },
        {
          label: 'Redo',
          accelerator: 'Shift+CmdOrCtrl+Z',
          role: 'redo'
        },
        {
          type: 'separator'
        },
        {
          label: 'Cut',
          accelerator: 'CmdOrCtrl+X',
          role: 'cut'
        },
        {
          label: 'Copy',
          accelerator: 'CmdOrCtrl+C',
          role: 'copy'
        },
        {
          label: 'Paste',
          accelerator: 'CmdOrCtrl+V',
          role: 'paste'
        },
        {
          label: 'Select All',
          accelerator: 'CmdOrCtrl+A',
          role: 'selectall'
        },
      ]
    };

    var view_menu = {
      label: 'View',
      id: 'view',
      submenu: [
        {
          label: 'Reload',
          accelerator: 'CmdOrCtrl+R',
          click: function(item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.webContents.reloadIgnoringCache();
            }
          }
        },
        {
          type: 'separator'
        },
        {
          label: 'Toggle Full Screen',
          accelerator: (function() {
            if (process.platform == 'darwin') {
              return 'Ctrl+Cmd+F';
            } else {
              return 'F11';
            }
          })(),
          click: function(item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.setFullScreen(!focusedWindow.isFullScreen());
            }
          }
        },
        {
          label: 'Actual Size',
          id: 'zoomReset',
          enabled: false,
          accelerator: 'CmdOrCtrl+0',
          click: function(item, focusedWindow) {
            app.resetZoom();
          }
        },
        {
          label: 'Zoom In',
          id: 'zoomIn',
          enabled: false,
          accelerator: 'CmdOrCtrl+=',
          click: function(item, focusedWindow) {
            app.zoomIn();
          }
        },
        {
          label: 'Zoom Out',
          id: 'zoomOut',
          enabled: false,
          accelerator: 'CmdOrCtrl+-',
          click: function(item, focusedWindow) {
            app.zoomOut();
          }
        },
        {
          type: 'separator'
        },
        {
          label: 'Toggle Developer Tools',
          accelerator: (function() {
            if (process.platform == 'darwin') {
              return 'Cmd+Alt+I';
            } else {
              return 'Ctrl+Shift+I';
            }
          })(),
          click: function(item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.toggleDevTools();
            }
          }
        },
      ]
    };
    if (process.platform != 'darwin') {
        view_menu.submenu.splice(2, 0, {
            label: 'Toggle Menu Bar',
            accelerator: 'Ctrl+Shift+M',
            click: function(item, focusedWindow) {
                if (focusedWindow.isMenuBarAutoHide()) {
                    app.config.set('menu-bar', true);
                    app.toggleMenuBar(focusedWindow);
                } else {
                    app.config.set('menu-bar', false);
                    app.toggleMenuBar(focusedWindow);
                }
            }
        });
    }

    var go_menu = {
      label: 'Go',
      id: 'history',
      submenu: [
        {
          label: 'Back',
          accelerator: 'CmdOrCtrl+[',
          enabled: false,
          id: 'backMenu',
          click: function (item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.webContents.goBack();
            }
          }
        },
        {
          label: 'Forward',
          accelerator: 'CmdOrCtrl+]',
          enabled: false,
          id: 'fwdMenu',
          click: function (item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.webContents.goForward();
            }
          }
        },
        {
          type: 'separator'
        },
        {
          label: 'File Uploads',
          click: function (item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.webContents.executeJavaScript('if (SESSIONVIEW) { SESSIONVIEW.files.show(); }', true);
            }
          }
        },
        {
          label: 'Pastebins',
          click: function (item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.webContents.executeJavaScript('if (SESSIONVIEW) { SESSIONVIEW.pastebins.show(); }', true);
            }
          }
        }
      ]
    };

    var window_menu = {
      label: 'Window',
      role: 'window',
      submenu: [
        {
          label: 'Minimize',
          accelerator: 'CmdOrCtrl+M',
          role: 'minimize'
        },
        {
          type: 'separator'
        },
        {
          label: 'Bring All to Front',
          role: 'front'
        }
      ]
    };

    var help_menu = {
      label: 'Help',
      role: 'help',
      submenu: [
        {
          label: 'Keyboard Shortcuts',
          accelerator: 'CmdOrCtrl+/',
          click: function (item, focusedWindow) {
            focusedWindow.webContents.executeJavaScript('if (SESSIONVIEW) { SESSIONVIEW.navigate("?/shortcuts", {trigger: true}); }', true);
          }
        },
        {
          type: 'separator'
        },
        {
          label: 'Known Issues',
          click: function(item, focusedWindow) {
            require('electron').shell.openExternal('https://github.com/irccloud/irccloud-desktop/issues');
          }
        },
      ]
    };

    var menu;
    if (process.platform == 'darwin') {
      menu = Menu.buildFromTemplate([app_menu, file_menu, edit_menu, view_menu, go_menu, window_menu, help_menu]);
    } else {
      menu = Menu.buildFromTemplate([file_menu, edit_menu, view_menu, go_menu, help_menu]);
    }
    Menu.setApplicationMenu(menu);
    return menu;
  },
  setup_tray: function(app){
    var menu;
    menu = Menu.buildFromTemplate([
        {
          label: 'Quit',
          click: function(item, focusedWindow) {
            app.userInitiatedQuit = true;
            app.quit();
          }
        }
    ]);
    return menu;
  }
};
<commit_msg>Devtools menu item: toggle -> open<commit_after>const electron = require('electron');

const app = electron.app;
const Menu = electron.Menu;
const MenuItem = electron.MenuItem;

module.exports = {
  setup: function (config) {
    var name = app.getName();

    var app_menu = {
      label: name,
      submenu: [
        {
          label: 'About ' + name,
          role: 'about'
        },
        {
          type: 'separator'
        },
        {
          label: 'Preferences…',
          accelerator: 'Cmd+,',
          click: function (item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.webContents.executeJavaScript('if (SESSIONVIEW) { SESSIONVIEW.openSettings(); }', true);
            }
          }
        },
        {
          type: 'separator'
        },
        {
          label: 'Services',
          role: 'services',
          submenu: []
        },
        {
          type: 'separator'
        },
        {
          label: 'Hide ' + name,
          accelerator: 'Cmd+H',
          role: 'hide'
        },
        {
          label: 'Hide Others',
          accelerator: 'Cmd+Alt+H',
          role: 'hideothers'
        },
        {
          label: 'Show All',
          role: 'unhide'
        },
        {
          type: 'separator'
        },
        {
          label: 'Quit',
          accelerator: 'Cmd+Q',
          click: function(item, focusedWindow) {
            app.userInitiatedQuit = true;
            app.quit();
          }
        }
      ]
    };
    var file_menu = {
        label: 'File',
        submenu: [
            {
              label: 'Open in Browser',
              click: function(item, focusedWindow) {
                var url = focusedWindow ? focusedWindow.webContents.getURL() : config.get('host');
                require('electron').shell.openExternal(url);
              }
            },
            {
              type: 'separator'
            },
            {
              label: 'Add a Network…',
              click: function (item, focusedWindow) {
                if (focusedWindow) {
                  focusedWindow.webContents.executeJavaScript('if (SESSIONVIEW) { SESSIONVIEW.addNetwork(); }', true);
                }
              }
            },
            {
              type: 'separator'
            },
            {
              label: 'Jump to…',
              accelerator: 'CmdOrCtrl+K',
              click: function (item, focusedWindow) {
                if (focusedWindow) {
                  focusedWindow.webContents.executeJavaScript('if (SESSIONVIEW) { SESSIONVIEW.channelSwitcher.toggle(); }', true);
                }
              }
            },
            {
              label: 'Select Next in List',
              accelerator: 'Alt+Down',
              click: function (item, focusedWindow) {
                if (focusedWindow) {
                  focusedWindow.webContents.executeJavaScript('if (SESSIONVIEW) { SESSIONVIEW.sidebar.bufferList.selectNextBuffer(); }', true);
                }
              }
            },
            {
              label: 'Select Previous in List',
              accelerator: 'Alt+Up',
              click: function (item, focusedWindow) {
                if (focusedWindow) {
                  focusedWindow.webContents.executeJavaScript('if (SESSIONVIEW) { SESSIONVIEW.sidebar.bufferList.selectPreviousBuffer(); }', true);
                }
              }
            },
            {
              label: 'Select Next Unread in List',
              accelerator: 'Alt+Shift+Down',
              click: function (item, focusedWindow) {
                if (focusedWindow) {
                  focusedWindow.webContents.executeJavaScript('if (SESSIONVIEW) { SESSIONVIEW.sidebar.bufferList.selectNextUnreadBuffer(); }', true);
                }
              }
            },
            {
              label: 'Select Previous Unread in List',
              accelerator: 'Alt+Shift+Up',
              click: function (item, focusedWindow) {
                if (focusedWindow) {
                  focusedWindow.webContents.executeJavaScript('if (SESSIONVIEW) { SESSIONVIEW.sidebar.bufferList.selectPreviousUnreadBuffer(); }', true);
                }
              }
            },
            {
              type: 'separator'
            },
            {
              label: 'Mark Current as Read',
              accelerator: 'Esc',
              click: function (item, focusedWindow) {
                if (focusedWindow) {
                  focusedWindow.webContents.executeJavaScript('if (SESSION && SESSION.currentBuffer) { SESSION.currentBuffer.read(); }', true);
                }
              }
            },
            {
              label: 'Mark All as Read',
              accelerator: 'Shift+Esc',
              click: function (item, focusedWindow) {
                if (focusedWindow) {
                  focusedWindow.webContents.executeJavaScript('if (SESSION) { SESSION.markAllAsRead(); }', true);
                }
              }
            },
            {
              type: 'separator'
            },
            {
              label: 'Upload a File…',
              click: function (item, focusedWindow) {
                if (focusedWindow) {
                  focusedWindow.webContents.executeJavaScript('if (SESSION && SESSION.currentBuffer) { SESSION.currentBuffer.trigger("uploadPrompt"); }', true);
                }
              }
            },
            {
              label: 'Start a Pastebin…',
              click: function (item, focusedWindow) {
                if (focusedWindow) {
                  focusedWindow.webContents.executeJavaScript('if (SESSION && SESSION.currentBuffer) { SESSION.currentBuffer.trigger("pastePrompt"); }', true);
                }
              }
            }
        ]
    };
    if (process.platform != 'darwin') {
      file_menu.submenu.push({
        type: 'separator'
      });
      file_menu.submenu.push({
        label: 'Preferences…',
        accelerator: 'Ctrl+,',
        click: function (item, focusedWindow) {
          if (focusedWindow) {
            focusedWindow.webContents.executeJavaScript('if (SESSIONVIEW) { SESSIONVIEW.openSettings(); }', true);
          }
        }
      });
      file_menu.submenu.push({
        type: 'separator'
      });
      file_menu.submenu.push({
        label: 'Show in Tray',
        type:  'checkbox',
        checked: Boolean(app.config.get('tray')),
        click: function(item, focusedWindow){
            app.config.set('tray', item.checked);
            app.toggleTray();
        }
      });
      file_menu.submenu.push({
        label: 'Quit',
        accelerator: (function() {
            if (process.platform == 'win32') {
                return 'Alt+F4';
            } else {
                return 'Ctrl+Q';
            }
        })(),
        click: function(item, focusedWindow) {
          app.quit();
        }
      });
    }

    var edit_menu = {
      label: 'Edit',
      submenu: [
        {
          label: 'Undo',
          accelerator: 'CmdOrCtrl+Z',
          role: 'undo'
        },
        {
          label: 'Redo',
          accelerator: 'Shift+CmdOrCtrl+Z',
          role: 'redo'
        },
        {
          type: 'separator'
        },
        {
          label: 'Cut',
          accelerator: 'CmdOrCtrl+X',
          role: 'cut'
        },
        {
          label: 'Copy',
          accelerator: 'CmdOrCtrl+C',
          role: 'copy'
        },
        {
          label: 'Paste',
          accelerator: 'CmdOrCtrl+V',
          role: 'paste'
        },
        {
          label: 'Select All',
          accelerator: 'CmdOrCtrl+A',
          role: 'selectall'
        },
      ]
    };

    var view_menu = {
      label: 'View',
      id: 'view',
      submenu: [
        {
          label: 'Reload',
          accelerator: 'CmdOrCtrl+R',
          click: function(item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.webContents.reloadIgnoringCache();
            }
          }
        },
        {
          type: 'separator'
        },
        {
          label: 'Toggle Full Screen',
          accelerator: (function() {
            if (process.platform == 'darwin') {
              return 'Ctrl+Cmd+F';
            } else {
              return 'F11';
            }
          })(),
          click: function(item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.setFullScreen(!focusedWindow.isFullScreen());
            }
          }
        },
        {
          label: 'Actual Size',
          id: 'zoomReset',
          enabled: false,
          accelerator: 'CmdOrCtrl+0',
          click: function(item, focusedWindow) {
            app.resetZoom();
          }
        },
        {
          label: 'Zoom In',
          id: 'zoomIn',
          enabled: false,
          accelerator: 'CmdOrCtrl+=',
          click: function(item, focusedWindow) {
            app.zoomIn();
          }
        },
        {
          label: 'Zoom Out',
          id: 'zoomOut',
          enabled: false,
          accelerator: 'CmdOrCtrl+-',
          click: function(item, focusedWindow) {
            app.zoomOut();
          }
        },
        {
          type: 'separator'
        },
        {
          label: 'Developer Tools',
          accelerator: (function() {
            if (process.platform == 'darwin') {
              return 'Cmd+Alt+I';
            } else {
              return 'Ctrl+Shift+I';
            }
          })(),
          click: function(item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.openDevTools();
            }
          }
        },
      ]
    };
    if (process.platform != 'darwin') {
        view_menu.submenu.splice(2, 0, {
            label: 'Toggle Menu Bar',
            accelerator: 'Ctrl+Shift+M',
            click: function(item, focusedWindow) {
                if (focusedWindow.isMenuBarAutoHide()) {
                    app.config.set('menu-bar', true);
                    app.toggleMenuBar(focusedWindow);
                } else {
                    app.config.set('menu-bar', false);
                    app.toggleMenuBar(focusedWindow);
                }
            }
        });
    }

    var go_menu = {
      label: 'Go',
      id: 'history',
      submenu: [
        {
          label: 'Back',
          accelerator: 'CmdOrCtrl+[',
          enabled: false,
          id: 'backMenu',
          click: function (item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.webContents.goBack();
            }
          }
        },
        {
          label: 'Forward',
          accelerator: 'CmdOrCtrl+]',
          enabled: false,
          id: 'fwdMenu',
          click: function (item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.webContents.goForward();
            }
          }
        },
        {
          type: 'separator'
        },
        {
          label: 'File Uploads',
          click: function (item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.webContents.executeJavaScript('if (SESSIONVIEW) { SESSIONVIEW.files.show(); }', true);
            }
          }
        },
        {
          label: 'Pastebins',
          click: function (item, focusedWindow) {
            if (focusedWindow) {
              focusedWindow.webContents.executeJavaScript('if (SESSIONVIEW) { SESSIONVIEW.pastebins.show(); }', true);
            }
          }
        }
      ]
    };

    var window_menu = {
      label: 'Window',
      role: 'window',
      submenu: [
        {
          label: 'Minimize',
          accelerator: 'CmdOrCtrl+M',
          role: 'minimize'
        },
        {
          type: 'separator'
        },
        {
          label: 'Bring All to Front',
          role: 'front'
        }
      ]
    };

    var help_menu = {
      label: 'Help',
      role: 'help',
      submenu: [
        {
          label: 'Keyboard Shortcuts',
          accelerator: 'CmdOrCtrl+/',
          click: function (item, focusedWindow) {
            focusedWindow.webContents.executeJavaScript('if (SESSIONVIEW) { SESSIONVIEW.navigate("?/shortcuts", {trigger: true}); }', true);
          }
        },
        {
          type: 'separator'
        },
        {
          label: 'Known Issues',
          click: function(item, focusedWindow) {
            require('electron').shell.openExternal('https://github.com/irccloud/irccloud-desktop/issues');
          }
        },
      ]
    };

    var menu;
    if (process.platform == 'darwin') {
      menu = Menu.buildFromTemplate([app_menu, file_menu, edit_menu, view_menu, go_menu, window_menu, help_menu]);
    } else {
      menu = Menu.buildFromTemplate([file_menu, edit_menu, view_menu, go_menu, help_menu]);
    }
    Menu.setApplicationMenu(menu);
    return menu;
  },
  setup_tray: function(app){
    var menu;
    menu = Menu.buildFromTemplate([
        {
          label: 'Quit',
          click: function(item, focusedWindow) {
            app.userInitiatedQuit = true;
            app.quit();
          }
        }
    ]);
    return menu;
  }
};
<|endoftext|>


<commit_before>// Edit this file to add your cloud design!

// Just write a function with a unique name like:
// function perlinNoiseCloud() {}
// and then add a line of code that "registers" it:
// register(perlinNoiseCloud, "Perlin Noise Cloud", "Daniel Shiffman");
// Here's an example below!

// Example rounded rectangle cloud
function rectangle() {
  // Draw your cloud here
  rect(50, 50, width - 100, height - 100, 50);
  // Return an internal rectangle that it is safe to draw text within. Of the
  // form [top_left, top_right, width, height]
  return [100, 100, width - 200, height - 200];
}

// Register your function with register(function, style_name, author_name)
register(rectangle, "Example", "example");

/* ------  Add your custom cloud generators below! ------ */
<commit_msg>Add ellipse cloud<commit_after>// Edit this file to add your cloud design!

// Just write a function with a unique name like:
// function perlinNoiseCloud() {}
// and then add a line of code that "registers" it:
// register(perlinNoiseCloud, "Perlin Noise Cloud", "Daniel Shiffman");
// Here's an example below!

// Example rounded rectangle cloud
function rectangle() {
  // Draw your cloud here
  rect(50, 50, width - 100, height - 100, 50);
  // Return an internal rectangle that it is safe to draw text within. Of the
  // form [top_left, top_right, width, height]
  return [100, 100, width - 200, height - 200];
}

// Register your function with register(function, style_name, author_name)
register(rectangle, "Example", "example");

/* ------  Add your custom cloud generators below! ------ */

function ellipseCloud() {
  const CIRC_RADIUS = width / 8;

  const cloudWidth = width - 100 - CIRC_RADIUS;
  const cloudHeight = height - 100 - CIRC_RADIUS;

  // Getting number of circles based on the width of the cloud and the cloud radius size.
  const NUM_CIRCS = cloudWidth / (CIRC_RADIUS * 0.5) * 2;

  push();
  translate(width / 2, height / 2);

  // Drawing outside circles (with stroke)
  for (let i = 0; i < NUM_CIRCS; i++) {
    drawOuterCirc(i);
  }

  // Drawing inner circles which hide the inner stroke of the outer circles
  for (let i = 0; i < NUM_CIRCS; i++) {
    drawInnerCirc(i);
  }

  // Filling the middle with white for name
  noStroke();
  ellipse(0, 0, cloudWidth, cloudHeight);
  pop();

  function drawOuterCirc(num) {
    const angle = TWO_PI / NUM_CIRCS * num;

    const randomX = cloudWidth / 2 * cos(angle);
    const randomY = cloudHeight / 2 * sin(angle);

    push();
    translate(randomX, randomY);
    ellipse(0, 0, CIRC_RADIUS, CIRC_RADIUS);
    pop();
  }

  function drawInnerCirc(num) {
    const angle = TWO_PI / NUM_CIRCS * num;

    // * 0.99 to still show the stroke of the outer circles
    const randomX = cloudWidth / 2 * cos(angle) * 0.99;
    const randomY = cloudHeight / 2 * sin(angle) * 0.99;

    push();
    noStroke();
    translate(randomX, randomY);
    ellipse(0, 0, CIRC_RADIUS, CIRC_RADIUS);
    pop();
  }

  return [100, 100, width - 200, height - 200];
}

register(ellipseCloud, "Ellipse Cloud", "Raqbit");<|endoftext|>


<commit_before>export {
  default
} from 'https://www.lactame.com//lib/molecular-formula/0.16.1/molecular-formula.js';
<commit_msg>update peptide to 1.7.0<commit_after>export {
  default
} from 'https://www.lactame.com//lib/molecular-formula/0.17.0/molecular-formula.js';
<|endoftext|>


<commit_before>/**
    MarketplaceJobtitles activity
**/
'use strict';

var Activity = require('../components/Activity'),
    ko = require('knockout'),
    AlertLink = require('../viewmodels/AlertLink');
var user = require('../data/userProfile').data;
var userJobProfile = require('../data/userJobProfile');
var serviceAddresses = require('../data/serviceAddresses');
var serviceProfessionalServices = require('../data/serviceProfessionalServices');
var workPhotos = require('../data/workPhotos');
var userLicensesCertifications = require('../data/userLicensesCertifications');
var jobTitleLicenses = require('../data/jobTitleLicenses');

var A = Activity.extend(function MarketplaceJobtitlesActivity() {

    Activity.apply(this, arguments);

    this.accessLevel = this.app.UserType.serviceProfessional;
    this.viewModel = new ViewModel(this.app);
    this.navBar = Activity.createSubsectionNavBar('Your listings', {
        backLink: '/marketplaceProfile' , helpLink: this.viewModel.helpLink
    });

    // On changing jobTitleID:
    // - load addresses
    // - load job title information
    // - load pricing
    this.registerHandler({
        target: this.viewModel.jobTitleID,
        handler: function(jobTitleID) {

            if (jobTitleID) {
                ////////////
                // User Job Title
                // Get data for the Job Title and User Profile
                userJobProfile.getUserJobTitleAndJobTitle(jobTitleID)
                //jobTitles.getJobTitle(jobTitleID)
                .then(function(job) {
                    // Fill the job title record
                    this.viewModel.jobTitle(job.jobTitle);
                    this.viewModel.userJobTitle(job.userJobTitle);
                }.bind(this))
                .catch(function(err) {
                    this.app.modals.showError({
                        title: 'There was an error loading your listing.',
                        error: err
                    });
                }.bind(this));

                /* NOTE: job title comes in the previous userJobProfile call, so is no need to duplicate the task
                ////////////
                // Job Title
                // Get data for the Job title ID
                jobTitles.getJobTitle(jobTitleID)
                .then(function(jobTitle) {

                    // Fill in job title name
                    this.viewModel.jobTitleName(jobTitle.singularName());
                }.bind(this))
                .catch(function(err) {
                    this.app.modals.showError({
                        title: 'There was an error while loading the job title.',
                        error: err
                    });
                }.bind(this));*/

                ////////////
                // Addresses
                serviceAddresses.getList(jobTitleID)
                .then(function(list) {

                    list = serviceAddresses.asModel(list);
                    this.viewModel.addresses(list);

                }.bind(this))
                .catch(function (err) {
                    this.app.modals.showError({
                        title: 'There was an error loading your locations.',
                        error: err
                    });
                }.bind(this));

                ////////////
                // Pricing/Services
                serviceProfessionalServices.getList(jobTitleID)
                .then(function(list) {

                    list = serviceProfessionalServices.asModel(list);
                    this.viewModel.pricing(list);

                }.bind(this))
                .catch(function (err) {
                    this.app.modals.showError({
                        title: 'There was an error loading your offerings.',
                        error: err
                    });
                }.bind(this));

                ////////////
                // Work Photos
                workPhotos.getList(jobTitleID)
                .then(function(list) {
                    list = workPhotos.asModel(list);
                    this.viewModel.workPhotos(list);

                }.bind(this))
                .catch(function (err) {
                    this.app.modals.showError({
                        title: 'There was an error while your work photos.',
                        error: err
                    });
                }.bind(this));
                ////////////
                // Submitted Licenses
                userLicensesCertifications.getList(jobTitleID)
                .then(function(list) {
                    // Save for use in the view
                    this.viewModel.submittedUserLicensesCertifications(userLicensesCertifications.asModel(list));
                }.bind(this))
                .catch(function (err) {
                    this.app.modals.showError({
                        title: 'There was an error while loading.',
                        error: err
                    });
                }.bind(this));
                // Get required licenses for the Job title ID - an object, not a list
                jobTitleLicenses.getItem(jobTitleID)
                .then(function(item) {
                    // Save for use in the view
                    this.viewModel.jobTitleApplicableLicences(item);
                }.bind(this))
                .catch(function (err) {
                    this.app.modals.showError({
                        title: 'There was an error while loading.',
                        error: err
                    });
                }.bind(this));
            }
            else {
                this.viewModel.addresses([]);
                this.viewModel.pricing([]);
                this.viewModel.jobTitle(null);
                this.viewModel.userJobTitle(null);
                //this.viewModel.jobTitleName('Job Title');
            }
        }.bind(this)
    });
});

exports.init = A.init;

A.prototype.show = function show(state) {
    Activity.prototype.show.call(this, state);

    // Reset: avoiding errors because persisted data for different ID on loading
    // or outdated info forcing update
    this.viewModel.jobTitleID(0);

    // Parameters
    var params = state && state.route && state.route.segments || {};

    // Set the job title
    var jobID = params[0] |0;
    this.viewModel.jobTitleID(jobID);

    //Get the return nav text
    var returnText = state && state.route && state.route.query.returnText || 'Back';
    this.viewModel.returnText(decodeURIComponent(returnText));
};

function ViewModel(app) {
    //jshint maxstatements: 40

    this.helpLink = '/help/relatedArticles/202034083-managing-your-marketplace-profile';

    this.jobTitleID = ko.observable(0);
    this.jobTitle = ko.observable(null);
    this.userJobTitle = ko.observable(null);
    this.returnText = ko.observable('Back');
    //this.jobTitleName = ko.observable('Job Title');
    this.jobTitleName = ko.pureComputed(function() {
        return this.jobTitle() && this.jobTitle().singularName() || 'Job Title';
    }, this);
    this.userID = user.userID;

    this.addresses = ko.observable([]);
    this.pricing = ko.observable([]);

    // Computed since it can check several externa loadings
    this.isLoading = ko.pureComputed(function() {
        return (
            serviceAddresses.state.isLoading() ||
            serviceProfessionalServices.state.isLoading()
        );

    }, this);

    this.addressesCount = ko.pureComputed(function() {

        // TODO l10n.
        // Use i18next plural localization support rather than this manual.
        var count = this.addresses().length,
            one = '1 location',
            more = ' locations';

        if (count === 1)
            return one;
        else
            // Small numbers, no need for formatting
            return count + more;

    }, this);

    // Retrieves a computed that will link to the given named activity adding the current
    // jobTitleID and a mustReturn URL to point this page so its remember the back route
    this.getJobUrlTo = function(name) {
        // Sample '/serviceProfessionalServices/' + jobTitleID()
        return ko.pureComputed(function() {
            return (
                '/' + name + '/' + this.jobTitleID() + '?mustReturn=marketplaceJobtitles/' + this.jobTitleID() +
                '&returnText=' + encodeURIComponent(this.jobTitleName())
            );
        }, this);
    };

    var UserJobTitle = require('../models/UserJobTitle');

    this.isToggleReady = ko.pureComputed(function() {
        return this.userJobTitle() && this.userJobTitle().isComplete();
    }, this);

    this.isActiveStatus = ko.pureComputed({
        read: function() {
            var j = this.userJobTitle();
            return j && j.statusID() === UserJobTitle.status.on || false;
        },
        write: function(v) {
            var status = this.userJobTitle() && this.userJobTitle().statusID();
            if (v === true && status === UserJobTitle.status.off) {
                this.userJobTitle().statusID(UserJobTitle.status.on);
                // Push change to back-end
                userJobProfile.reactivateUserJobTitle(this.jobTitleID())
                .catch(function(err) {
                    app.modals.showError({ title: 'Error enabling your listing', error: err });
                });
            }
            else if (v === false && status === UserJobTitle.status.on) {
                this.userJobTitle().statusID(UserJobTitle.status.off);
                // Push change to back-end
                userJobProfile.deactivateUserJobTitle(this.jobTitleID())
                .catch(function(err) {
                    app.modals.showError({ title: 'Error disabling your listing', error: err });
                });
                // Per #1001, notify user about availability of bookMeNow button even with public marketplace profile
                // disabled/hidden
                app.modals.showNotification({
                    message: 'Clients will no longer be able to find you in the marketplace. However, any "book me now" links you have posted will still be active.',
                    buttonText: 'Got it!'
                });
            }
        },
        owner: this
    });

    this.statusLabel = ko.pureComputed(function() {
        var statusID = this.userJobTitle() && this.userJobTitle().statusID();
        switch (statusID) {
            case UserJobTitle.status.on:
                return 'This listing is active';
            case UserJobTitle.status.off:
                return 'This listing is inactive';
            //case UserJobTitle.status.incomplete:
            default:
                return 'Steps Remaining';
        }
    }, this);

    /// Related models information
    this.submittedUserLicensesCertifications = ko.observableArray([]);
    this.jobTitleApplicableLicences = ko.observable(null);
    this.workPhotos = ko.observable([]);

    // Computed since it can check several externa loadings
    this.isLoading = ko.pureComputed(function() {
        return (
            serviceAddresses.state.isLoading() ||
            serviceProfessionalServices.state.isLoading()
        );

    }, this);

    this.licensesCertificationsSummary = ko.pureComputed(function() {
        var lc = this.submittedUserLicensesCertifications();
        //jshint maxcomplexity:8
        if (lc && lc.length) {
            var verified = 0,
                other = 0,
                pending = 0;
            lc.forEach(function(l) {
                if (l && l.statusID() === 1)
                    verified++;
                else if (l && l.statusID() === 2)
                    pending++;
                else if (l && l.statusID() === 4)
                    return 'Expired, please update';
                else if (l && l.statusID() === 5)
                    other++;
                else if (l && l.statusID() === 6)
                    return 'Expiring soon, please update';
                else if (l && l.statusID() === 3)
                    return 'Please contact us';
            });
            return verified + ' verified, ' + pending + ' pending, ' + other + ' supplemental';
        }
        else {
            return 'None verified';
        }
    }, this);

    this.workPhotosSummary = ko.pureComputed(function() {
        var wp = this.workPhotos();
        // L18N
        if (wp && wp.length > 1)
            return wp.length + ' images';
        else if (wp && wp.length === 1)
            return '1 image';
        else
            return 'No images';
    }, this);

    this.pricingCount = ko.pureComputed(function() {

        // TODO l10n.
        // Use i18next plural localization support rather than this manual.
        var count = this.pricing().length,
            one = '1 offering',
            more = ' offerings';

        if (count === 1)
            return one;
        else
            // Small numbers, no need for formatting
            return count + more;

    }, this);

    this.deleteJobTitle = function() {
        var jid = this.jobTitleID();
        var jname = this.jobTitleName();
        if (jid) {
            app.modals.confirm({
                title: 'Delete ' + jname + ' listing',
                message: 'Are you sure you really want to delete your ' + jname +' listing?',
                yes: 'Delete',
                no: 'Keep'
            }).then(function() {
                app.shell.goBack();
                return userJobProfile.deleteUserJobTitle(jid);
            })
            .catch(function(err) {
                if (err) {
                    app.modals.showError({ error: err, title: 'Error while deleting your listing' });
                }
            });
        }
    }.bind(this);

    this.requiredAlertLinks = ko.pureComputed(function() {
        var userJobTitle = this.userJobTitle(),
            jobTitleID = userJobTitle && userJobTitle.jobTitleID(),
            requiredAlerts = (userJobTitle && userJobTitle.requiredAlerts()) || [];

        return requiredAlerts.map(function(profileAlert) {
            return AlertLink.fromProfileAlert(profileAlert, { jobTitleID: jobTitleID });
        });
    }, this);
}
<commit_msg>Removed duplicated code at marketplaceJobtitles<commit_after>/**
    MarketplaceJobtitles activity
**/
'use strict';

var Activity = require('../components/Activity'),
    ko = require('knockout'),
    AlertLink = require('../viewmodels/AlertLink');
var user = require('../data/userProfile').data;
var userJobProfile = require('../data/userJobProfile');
var serviceAddresses = require('../data/serviceAddresses');
var serviceProfessionalServices = require('../data/serviceProfessionalServices');
var workPhotos = require('../data/workPhotos');
var userLicensesCertifications = require('../data/userLicensesCertifications');
var jobTitleLicenses = require('../data/jobTitleLicenses');

var A = Activity.extend(function MarketplaceJobtitlesActivity() {

    Activity.apply(this, arguments);

    this.accessLevel = this.app.UserType.serviceProfessional;
    this.viewModel = new ViewModel(this.app);
    this.navBar = Activity.createSubsectionNavBar('Your listings', {
        backLink: '/marketplaceProfile' , helpLink: this.viewModel.helpLink
    });

    // On changing jobTitleID:
    // - load addresses
    // - load job title information
    // - load pricing
    this.registerHandler({
        target: this.viewModel.jobTitleID,
        handler: function(jobTitleID) {

            if (jobTitleID) {
                ////////////
                // User Job Title
                // Get data for the Job Title and User Profile
                userJobProfile.getUserJobTitleAndJobTitle(jobTitleID)
                //jobTitles.getJobTitle(jobTitleID)
                .then(function(job) {
                    // Fill the job title record
                    this.viewModel.jobTitle(job.jobTitle);
                    this.viewModel.userJobTitle(job.userJobTitle);
                }.bind(this))
                .catch(function(err) {
                    this.app.modals.showError({
                        title: 'There was an error loading your listing.',
                        error: err
                    });
                }.bind(this));

                /* NOTE: job title comes in the previous userJobProfile call, so is no need to duplicate the task
                ////////////
                // Job Title
                // Get data for the Job title ID
                jobTitles.getJobTitle(jobTitleID)
                .then(function(jobTitle) {

                    // Fill in job title name
                    this.viewModel.jobTitleName(jobTitle.singularName());
                }.bind(this))
                .catch(function(err) {
                    this.app.modals.showError({
                        title: 'There was an error while loading the job title.',
                        error: err
                    });
                }.bind(this));*/

                ////////////
                // Addresses
                serviceAddresses.getList(jobTitleID)
                .then(function(list) {

                    list = serviceAddresses.asModel(list);
                    this.viewModel.addresses(list);

                }.bind(this))
                .catch(function (err) {
                    this.app.modals.showError({
                        title: 'There was an error loading your locations.',
                        error: err
                    });
                }.bind(this));

                ////////////
                // Pricing/Services
                serviceProfessionalServices.getList(jobTitleID)
                .then(function(list) {

                    list = serviceProfessionalServices.asModel(list);
                    this.viewModel.pricing(list);

                }.bind(this))
                .catch(function (err) {
                    this.app.modals.showError({
                        title: 'There was an error loading your offerings.',
                        error: err
                    });
                }.bind(this));

                ////////////
                // Work Photos
                workPhotos.getList(jobTitleID)
                .then(function(list) {
                    list = workPhotos.asModel(list);
                    this.viewModel.workPhotos(list);

                }.bind(this))
                .catch(function (err) {
                    this.app.modals.showError({
                        title: 'There was an error while your work photos.',
                        error: err
                    });
                }.bind(this));
                ////////////
                // Submitted Licenses
                userLicensesCertifications.getList(jobTitleID)
                .then(function(list) {
                    // Save for use in the view
                    this.viewModel.submittedUserLicensesCertifications(userLicensesCertifications.asModel(list));
                }.bind(this))
                .catch(function (err) {
                    this.app.modals.showError({
                        title: 'There was an error while loading.',
                        error: err
                    });
                }.bind(this));
                // Get required licenses for the Job title ID - an object, not a list
                jobTitleLicenses.getItem(jobTitleID)
                .then(function(item) {
                    // Save for use in the view
                    this.viewModel.jobTitleApplicableLicences(item);
                }.bind(this))
                .catch(function (err) {
                    this.app.modals.showError({
                        title: 'There was an error while loading.',
                        error: err
                    });
                }.bind(this));
            }
            else {
                this.viewModel.addresses([]);
                this.viewModel.pricing([]);
                this.viewModel.jobTitle(null);
                this.viewModel.userJobTitle(null);
                //this.viewModel.jobTitleName('Job Title');
            }
        }.bind(this)
    });
});

exports.init = A.init;

A.prototype.show = function show(state) {
    Activity.prototype.show.call(this, state);

    // Reset: avoiding errors because persisted data for different ID on loading
    // or outdated info forcing update
    this.viewModel.jobTitleID(0);

    // Parameters
    var params = state && state.route && state.route.segments || {};

    // Set the job title
    var jobID = params[0] |0;
    this.viewModel.jobTitleID(jobID);

    //Get the return nav text
    var returnText = state && state.route && state.route.query.returnText || 'Back';
    this.viewModel.returnText(decodeURIComponent(returnText));
};

function ViewModel(app) {
    //jshint maxstatements: 40

    this.helpLink = '/help/relatedArticles/202034083-managing-your-marketplace-profile';

    this.jobTitleID = ko.observable(0);
    this.jobTitle = ko.observable(null);
    this.userJobTitle = ko.observable(null);
    this.returnText = ko.observable('Back');
    //this.jobTitleName = ko.observable('Job Title');
    this.jobTitleName = ko.pureComputed(function() {
        return this.jobTitle() && this.jobTitle().singularName() || 'Job Title';
    }, this);
    this.userID = user.userID;

    this.addresses = ko.observable([]);
    this.pricing = ko.observable([]);

    // Computed since it can check several externa loadings
    this.isLoading = ko.pureComputed(function() {
        return (
            serviceAddresses.state.isLoading() ||
            serviceProfessionalServices.state.isLoading()
        );
    }, this);

    this.addressesCount = ko.pureComputed(function() {

        // TODO l10n.
        // Use i18next plural localization support rather than this manual.
        var count = this.addresses().length,
            one = '1 location',
            more = ' locations';

        if (count === 1)
            return one;
        else
            // Small numbers, no need for formatting
            return count + more;

    }, this);

    // Retrieves a computed that will link to the given named activity adding the current
    // jobTitleID and a mustReturn URL to point this page so its remember the back route
    this.getJobUrlTo = function(name) {
        // Sample '/serviceProfessionalServices/' + jobTitleID()
        return ko.pureComputed(function() {
            return (
                '/' + name + '/' + this.jobTitleID() + '?mustReturn=marketplaceJobtitles/' + this.jobTitleID() +
                '&returnText=' + encodeURIComponent(this.jobTitleName())
            );
        }, this);
    };

    var UserJobTitle = require('../models/UserJobTitle');

    this.isToggleReady = ko.pureComputed(function() {
        return this.userJobTitle() && this.userJobTitle().isComplete();
    }, this);

    this.isActiveStatus = ko.pureComputed({
        read: function() {
            var j = this.userJobTitle();
            return j && j.statusID() === UserJobTitle.status.on || false;
        },
        write: function(v) {
            var status = this.userJobTitle() && this.userJobTitle().statusID();
            if (v === true && status === UserJobTitle.status.off) {
                this.userJobTitle().statusID(UserJobTitle.status.on);
                // Push change to back-end
                userJobProfile.reactivateUserJobTitle(this.jobTitleID())
                .catch(function(err) {
                    app.modals.showError({ title: 'Error enabling your listing', error: err });
                });
            }
            else if (v === false && status === UserJobTitle.status.on) {
                this.userJobTitle().statusID(UserJobTitle.status.off);
                // Push change to back-end
                userJobProfile.deactivateUserJobTitle(this.jobTitleID())
                .catch(function(err) {
                    app.modals.showError({ title: 'Error disabling your listing', error: err });
                });
                // Per #1001, notify user about availability of bookMeNow button even with public marketplace profile
                // disabled/hidden
                app.modals.showNotification({
                    message: 'Clients will no longer be able to find you in the marketplace. However, any "book me now" links you have posted will still be active.',
                    buttonText: 'Got it!'
                });
            }
        },
        owner: this
    });

    this.statusLabel = ko.pureComputed(function() {
        var statusID = this.userJobTitle() && this.userJobTitle().statusID();
        switch (statusID) {
            case UserJobTitle.status.on:
                return 'This listing is active';
            case UserJobTitle.status.off:
                return 'This listing is inactive';
            //case UserJobTitle.status.incomplete:
            default:
                return 'Steps Remaining';
        }
    }, this);

    /// Related models information
    this.submittedUserLicensesCertifications = ko.observableArray([]);
    this.jobTitleApplicableLicences = ko.observable(null);
    this.workPhotos = ko.observable([]);

    this.licensesCertificationsSummary = ko.pureComputed(function() {
        var lc = this.submittedUserLicensesCertifications();
        //jshint maxcomplexity:8
        if (lc && lc.length) {
            var verified = 0,
                other = 0,
                pending = 0;
            lc.forEach(function(l) {
                if (l && l.statusID() === 1)
                    verified++;
                else if (l && l.statusID() === 2)
                    pending++;
                else if (l && l.statusID() === 4)
                    return 'Expired, please update';
                else if (l && l.statusID() === 5)
                    other++;
                else if (l && l.statusID() === 6)
                    return 'Expiring soon, please update';
                else if (l && l.statusID() === 3)
                    return 'Please contact us';
            });
            return verified + ' verified, ' + pending + ' pending, ' + other + ' supplemental';
        }
        else {
            return 'None verified';
        }
    }, this);

    this.workPhotosSummary = ko.pureComputed(function() {
        var wp = this.workPhotos();
        // L18N
        if (wp && wp.length > 1)
            return wp.length + ' images';
        else if (wp && wp.length === 1)
            return '1 image';
        else
            return 'No images';
    }, this);

    this.pricingCount = ko.pureComputed(function() {

        // TODO l10n.
        // Use i18next plural localization support rather than this manual.
        var count = this.pricing().length,
            one = '1 offering',
            more = ' offerings';

        if (count === 1)
            return one;
        else
            // Small numbers, no need for formatting
            return count + more;

    }, this);

    this.deleteJobTitle = function() {
        var jid = this.jobTitleID();
        var jname = this.jobTitleName();
        if (jid) {
            app.modals.confirm({
                title: 'Delete ' + jname + ' listing',
                message: 'Are you sure you really want to delete your ' + jname +' listing?',
                yes: 'Delete',
                no: 'Keep'
            }).then(function() {
                app.shell.goBack();
                return userJobProfile.deleteUserJobTitle(jid);
            })
            .catch(function(err) {
                if (err) {
                    app.modals.showError({ error: err, title: 'Error while deleting your listing' });
                }
            });
        }
    }.bind(this);

    this.requiredAlertLinks = ko.pureComputed(function() {
        var userJobTitle = this.userJobTitle(),
            jobTitleID = userJobTitle && userJobTitle.jobTitleID(),
            requiredAlerts = (userJobTitle && userJobTitle.requiredAlerts()) || [];

        return requiredAlerts.map(function(profileAlert) {
            return AlertLink.fromProfileAlert(profileAlert, { jobTitleID: jobTitleID });
        });
    }, this);
}
<|endoftext|>


<commit_before>import React from 'react'

import ReactTable from 'react-table'
import Linkify from 'react-linkify'
import PropTypes from 'prop-types'
import ReactDOMServer from 'react-dom/server'
import { configureUrlQuery, addUrlProps, UrlQueryParamTypes } from 'react-url-query'
import { connect } from 'react-redux'

import history from '../../src/history'

import '!style-loader!css-loader!react-table/react-table.css'

import s from './RuleTable.css'

const urlPropsQueryConfig = {
  data: {type: UrlQueryParamTypes.string}
};

configureUrlQuery({history})

const mapStateToProps = state => {
  return {
    editing: state.editing
  }
}

class RuleTable extends React.PureComponent {
  static defaultState = {
    data: [
      {name: '', location: ''},
      {name: '', location: ''}
    ]
  }

  constructor (props, context) {
    super(props, context)
    this.renderEditable = this.renderEditable.bind(this)
    this.renderRemove = this.renderRemove.bind(this)

    this.state = {}
    Object.assign(this.state, props)
    this.state.data = props.data ? JSON.parse(props.data) : RuleTable.defaultState.data

    this.state.columns = [
      {
        Header: 'Ruleset Name',
        accessor: 'name',
        Cell: this.renderEditable('PHB')
      },
      {
        Header: 'Location',
        accessor: 'location',
        Cell: this.renderEditable('Local Bookstore')
      },
      {
        Header: 'Remove',
        id: 'remove',
        accessor: () => 'x',
        show: this.props.editing,
        Cell: this.renderRemove,
        width: 100
      }
    ]
  }

  componentWillReceiveProps (nextProps) {
    if (!nextProps.editing && this.props.editing) {
      this.state.onChangeData(JSON.stringify(this.state.data))
    }

    const columns = [...this.state.columns]

    columns[2].show = nextProps.editing

    this.setState({columns})
  }

  renderRemove (cellInfo) {
    return ( {
      const data = [...this.state.data]

      if (data[cellInfo.index].name !== '' || data[cellInfo.index].location !== '' && data.length > 1) {
        data.splice(cellInfo.index, 1)
      }

      this.setState({data})
    }}>x)
  }

  renderEditable (defaultValue = '') {
    return (cellInfo) => this.props.editing ? (
<div style={{ backgroundColor: '#fafafa' }} data-ph={defaultValue} className={`${s.editableCell} ${s.tableCell}`} contentEditable suppressContentEditableWarning onBlur={(e) => {
      const data = [...this.state.data]
      data[cellInfo.index][cellInfo.column.id] = e.target.textContent
      const rows = () => {
        const last = data[data.length-2]
        const secondLast = data[data.length-3]
        if (last.name !== '' || last.location !== '') {
          data.push({name: '', location: ''})
        } else if (typeof secondLast !== 'undefined' && secondLast.name == '' && secondLast.location == '') {
          data.pop()
          rows()
        }
      }
      rows()
      this.setState({data: data})
    }}>{this.state.data[cellInfo.index][cellInfo.column.id]}

) : (
<Linkify className={s.tableCell}>{this.state.data[cellInfo.index][cellInfo.column.id]}</Linkify>
)
  }

  render () {
    return (

        <ReactTable
          data={this.state.data}
          columns={this.state.columns}
          pageSize={this.state.data.length}
          showPageSizeOptions={false}
          showPagination={false}
          key={this.props.editing}
        />

    )
  }
}

export default addUrlProps({urlPropsQueryConfig})(connect(mapStateToProps)(RuleTable))
<commit_msg>Re-do encoding mechanism; now much smaller<commit_after>import React from 'react'

import ReactTable from 'react-table'
import Linkify from 'react-linkify'
import PropTypes from 'prop-types'
import ReactDOMServer from 'react-dom/server'
import { configureUrlQuery, addUrlProps, UrlQueryParamTypes } from 'react-url-query'
import { connect } from 'react-redux'

import history from '../../src/history'

import '!style-loader!css-loader!react-table/react-table.css'

import s from './RuleTable.css'

const urlPropsQueryConfig = {
  data: {type: UrlQueryParamTypes.string}
};

configureUrlQuery({history})

const mapStateToProps = state => {
  return {
    editing: state.editing
  }
}

function encode(data) {
  let arr = []

  data.forEach(e => {
    arr.push(e.name)
    arr.push(e.location)
  })

  return arr.join('\u0000')
}

function decode(data) {
  let arr = data.split(/\u0000/g)

  arr.forEach((e,i,a) => {if (i % 2 === 0) a[i/2]={name: e, location: a[i+1]}})

  arr.splice((arr.length - 1) / 2 + 1)

  return arr
}

class RuleTable extends React.PureComponent {
  static defaultState = {
    data: [
      {name: '', location: ''},
      {name: '', location: ''}
    ]
  }

  constructor (props, context) {
    super(props, context)
    this.renderEditable = this.renderEditable.bind(this)
    this.renderRemove = this.renderRemove.bind(this)

    this.state = {}
    Object.assign(this.state, props)
    let data = props.data ? decode(props.data) : RuleTable.defaultState.data

    const rows = () => {
      const last = data[data.length-2]
      const secondLast = data[data.length-3]
      if (last.name !== '' || last.location !== '') {
        data.push({name: '', location: ''})
      } else if (typeof secondLast !== 'undefined' && secondLast.name == '' && secondLast.location == '') {
        data.pop()
        rows()
      }
    }

    rows()
    rows()

    this.state.data = data

    this.state.columns = [
      {
        Header: 'Ruleset Name',
        accessor: 'name',
        Cell: this.renderEditable('PHB')
      },
      {
        Header: 'Location',
        accessor: 'location',
        Cell: this.renderEditable('Local Bookstore')
      },
      {
        Header: 'Remove',
        id: 'remove',
        accessor: () => 'x',
        show: this.props.editing,
        Cell: this.renderRemove,
        width: 100
      }
    ]
  }

  componentWillReceiveProps (nextProps) {
    if (!nextProps.editing && this.props.editing) {
      this.state.onChangeData(encode(this.state.data))
    }

    const columns = [...this.state.columns]

    columns[2].show = nextProps.editing

    this.setState({columns})
  }

  renderRemove (cellInfo) {
    return ( {
      const data = [...this.state.data]

      if (data[cellInfo.index].name !== '' || data[cellInfo.index].location !== '' && data.length > 1) {
        data.splice(cellInfo.index, 1)
      }

      this.setState({data})
    }}>x)
  }

  renderEditable (defaultValue = '') {
    return (cellInfo) => this.props.editing ? (
<div style={{ backgroundColor: '#fafafa' }} data-ph={defaultValue} className={`${s.editableCell} ${s.tableCell}`} contentEditable suppressContentEditableWarning onBlur={(e) => {
      const data = [...this.state.data]
      data[cellInfo.index][cellInfo.column.id] = e.target.textContent
      const rows = () => {
        const last = data[data.length-2]
        const secondLast = data[data.length-3]
        if (last.name !== '' || last.location !== '') {
          data.push({name: '', location: ''})
        } else if (typeof secondLast !== 'undefined' && secondLast.name == '' && secondLast.location == '') {
          data.pop()
          rows()
        }
      }
      rows()
      this.setState({data: data})
    }}>{this.state.data[cellInfo.index][cellInfo.column.id]}

) : (
<Linkify className={s.tableCell}>{this.state.data[cellInfo.index][cellInfo.column.id]}</Linkify>
)
  }

  render () {
    return (

        <ReactTable
          data={this.state.data}
          columns={this.state.columns}
          pageSize={this.state.data.length}
          showPageSizeOptions={false}
          showPagination={false}
          key={this.props.editing}
        />

    )
  }
}

export default addUrlProps({urlPropsQueryConfig})(connect(mapStateToProps)(RuleTable))
<|endoftext|>


<commit_before>import React from 'react';
import styled, { css } from 'styled-components';
import { Github, Bitbucket, Gitlab, Git } from 'styled-icons/fa-brands';
import { Globe } from 'styled-icons/boxicons-regular';
import { phone } from '../../utils/media';

const SlideMeta = styled.div`
  display: flex;
  justify-content: space-between;
  margin-top: 48px;

  ${phone(css`
    position: relative;
    top: -86px;
    flex-direction: column-reverse;
  `)}

  em {
    color: rgb(219, 112, 147);
    font-style: normal;
  }
`;

const SlideMetaLinks = styled.div`
  display: flex;
  flex-direction: column;
  margin-left: 64px;
  margin-bottom: 24px;
  flex-shrink: 0;
  padding-top: 16px;
  align-items: flex-end;

  ${phone(css`
    margin-left: 0;
    justify-content: center;
    flex-direction: row;

    span {
      display: none;
    }
  `)}
`;

const SlideMetaLink = styled.a`
  display: flex;
  align-items: center;
  justify-content: flex-end;
  margin-bottom: 8px;
  margin-right: -16px;
  padding: 4px 16px;
  border-radius: 8px;

  ${phone(css`
    display: inline-flex;
    justify-content: center;
    margin-right: 0;
    margin-bottom: 0;
    padding: 16px;
    border-radius: 50%;
  `)}

  &:hover {
    background-color: rgba(0, 0, 0, 0.05);
  }

  svg {
    display: inline-block;
    height: 24px;
    margin-left: 8px;

    ${phone(css`
      margin-left: 0;
    `)}
  }
`;

const RepoIcon = ({ url }) => {
  if (url.indexOf('github') > -1) return <Github />;
  if (url.indexOf('bitbucket') > -1) return <Bitbucket />;
  if (url.indexOf('gitlab') > -1) return <Gitlab />;
  return <Git />;
};

const ShowcaseBody = ({ title, owner, description, link, repo }) => (
  <SlideMeta>

{title}

        By {owner}

      {description &&
{description}
}

    <SlideMetaLinks>
      <SlideMetaLink href={link} target="_blank">
        Go to website
        <Globe />
      </SlideMetaLink>
      {repo && (
        <SlideMetaLink href={repo} target="_blank">
          Go to repository
          <RepoIcon url={repo} />
        </SlideMetaLink>
      )}
    </SlideMetaLinks>
  </SlideMeta>
);

export default ShowcaseBody;
<commit_msg>🐛 isolating import issue to fa-icons only<commit_after>import React from 'react';
import styled, { css } from 'styled-components';
import { Github, Bitbucket, Gitlab, Git } from 'styled-icons/fa-brands';
// import { Globe } from 'styled-icons/boxicons-regular';
import { phone } from '../../utils/media';

const SlideMeta = styled.div`
  display: flex;
  justify-content: space-between;
  margin-top: 48px;

  ${phone(css`
    position: relative;
    top: -86px;
    flex-direction: column-reverse;
  `)}

  em {
    color: rgb(219, 112, 147);
    font-style: normal;
  }
`;

const SlideMetaLinks = styled.div`
  display: flex;
  flex-direction: column;
  margin-left: 64px;
  margin-bottom: 24px;
  flex-shrink: 0;
  padding-top: 16px;
  align-items: flex-end;

  ${phone(css`
    margin-left: 0;
    justify-content: center;
    flex-direction: row;

    span {
      display: none;
    }
  `)}
`;

const SlideMetaLink = styled.a`
  display: flex;
  align-items: center;
  justify-content: flex-end;
  margin-bottom: 8px;
  margin-right: -16px;
  padding: 4px 16px;
  border-radius: 8px;

  ${phone(css`
    display: inline-flex;
    justify-content: center;
    margin-right: 0;
    margin-bottom: 0;
    padding: 16px;
    border-radius: 50%;
  `)}

  &:hover {
    background-color: rgba(0, 0, 0, 0.05);
  }

  svg {
    display: inline-block;
    height: 24px;
    margin-left: 8px;

    ${phone(css`
      margin-left: 0;
    `)}
  }
`;

const RepoIcon = ({ url }) => {
  if (url.indexOf('github') > -1) return <Github />;
  if (url.indexOf('bitbucket') > -1) return <Bitbucket />;
  if (url.indexOf('gitlab') > -1) return <Gitlab />;
  return <Git />;
};

const ShowcaseBody = ({ title, owner, description, link, repo }) => (
  <SlideMeta>

{title}

        By {owner}

      {description &&
{description}
}

    <SlideMetaLinks>
      <SlideMetaLink href={link} target="_blank">
        Go to website
        {/* <Globe /> */}
      </SlideMetaLink>
      {repo && (
        <SlideMetaLink href={repo} target="_blank">
          Go to repository
          <RepoIcon url={repo} />
        </SlideMetaLink>
      )}
    </SlideMetaLinks>
  </SlideMeta>
);

export default ShowcaseBody;
<|endoftext|>


<commit_before>/**
 * @file
 * ABTest.js
 *
 * Simple A/B Test library.
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://code.google.com/p/abtest-js/wiki/License
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to aldo.armiento@gmail.com so we can send you a copy immediately.
 *
 * @see http://code.google.com/p/abtest-js/
 * @author (C) 2010 Aldo Armiento (aldo.armiento@gmail.com)
 * @author Sponsored by Gruppo Immobiliare.it S.r.l. (http://www.immobiliare.it/)
 * @version $Id$
 */
if (typeof ABTest == 'undefined') {
	var ABTest = new function () {

		const COOKIE_PREFIX = 'ABTest_'; /**< @type String Cookie prefix. */

		const NOW = new Date().getTime(); /**< @type Number Current timestamp. */

		const TEST_SESSION = { hours: 48 }; /**< @type object Default test session duration. */
		const MAX_TEST_SESSION = { days: 7 }; /**< @type object Default maximum test session duration. */

		var _logEnabled = false; /**< @type Boolean True to enable event log, false otherwise. */
		var _luckiness = false; /**< @type Boolean True if user is lucky, false otherwise. */

		var _running = false; /**< @type Boolean Test is running and can not be re-executed. */
		var _testName; /**< @type String Current A/B Test name. */
		var _testOptions = {}; /**< @type object Current A/B Test options. */

		var _allTests = new Array(); /**< @type Array All loaded tests. */

		/**
		 * Starts the A/B Test.
		 *
		 * @param  name @type String The test name.
		 * @param  options @type object Test options:
		 *		   @li @c session (optional, default TEST_SESSION) Session duration
		 *		   @li @c maxSession (optional, default MAX_TEST_SESSION) Session max duration
		 *		   @li @c probability User lucky/unlucky probability
		 *		   @li @c path (optional, default /) Site path validity (cookie path)
		 *		   @li @c domain (optional, default empty) Site domain validity (cookie domain)
		 *		   @li @c secure (optional, default false) Use https protocol (cookie secure)
		 *		   @li @c test Specific test options (test must exists)
		 */
		this.start = function (name, options) {
			if (_running) {
				_error('An A/B test is already running, exiting.');
				return;
			}
			_running = true;

			if (!name || !options) {
				_error('Wrong number or type of arguments.');
				return;
			}
			options = options || {};

			options.test = options.test || {};
			if (!options.test.name) {
				_error('No test name specified.');
				return;
			}

			if (typeof _allTests[options.test.name] != 'function') {
				_error("Test '" + options.test.name + "' does not loaded.");
				return;
			}

			options.session = options.session || TEST_SESSION;
			options.maxSession = options.maxSession || MAX_TEST_SESSION;

			options.probability = options.probability || 0;
			options.path = options.path || '/';
			options.domain = options.domain || '';
			options.secure = options.secure || false;

			_testName = name;
			_testOptions = options;

			_log('Starting A/B test ' + name + '.');

			if (_userSession()) {
				_runTest();
			}
		}

		/**
		 * Enable/disable log.
		 *
		 * @param  logEnabled @type Boolean True to enable logging functions.
		 */
		this.enableLog = function (logEnabled) {
			_logEnabled = logEnabled;
		}

		/**
		 * Enable/disable log.
		 *
		 * @return @type Boolean True if the current user is lucky.
		 */
		this.isUserLucky = function () {
			return _luckiness;
		}

		/**
		 * Adds custom test.
		 *
		 * @param  testFunction @type Function Test function.
		 */
		this.addTest = function (testFunction) {
			var testName = testFunction.name;

			if (!testName) {
				_error('The test function must have a name that is the test name.');
				return;
			}
			if (_allTests[testName]) {
				_error("The test '" + testName + "' already exists.");
				return false;
			}
			_allTests[testName] = testFunction;
			return true;
		}

		/**
		 * Returns A/B Test name.
		 *
		 * @return @type String A/B Test name.
		 */
		this.getTestName = function () {
			return _testName;
		}

		/**
		 * Returns A/B Test configuration options.
		 *
		 * @return @type Object A/B Test options.
		 */
		this.getTestOptions = function () {
			return _testOptions;
		}

		/**
		 * Tests if a cookie exists.
		 *
		 * @param  name @type String Cookie name
		 * @return @type Boolean True if cookie esists, false otherwise.
		 */
		this.cookieExists = function (name) {
			return typeof _getCookie(COOKIE_PREFIX + _testName + '_' + name) != 'undefined';
		}

		/**
		 * Get a cookie value by name.
		 *
		 * @param  name @type String Cookie name
		 * @return @type String The cookie value or undefined if no cookie
		 *		   exists with that name.
		 */
		this.getCookie = function (name) {
			return _getCookie(COOKIE_PREFIX + _testName + '_' + name);
		}

		/**
		 * Setup a new session only cookie or updates an existing cookie with the
		 * same name.
		 *
		 * @param  name @type String Cookie name.
		 * @param  value @type String Cookie value.
		 */
		this.setCookieSession = function (name, value) {
			_setCookie(COOKIE_PREFIX + _testName + '_' + name, value, null,
				_testOptions.path, _testOptions.domain, _testOptions.secure);
		}

		/**
		 * Delete a new session only cookie or updates an existing cookie with the
		 * same name.
		 *
		 * @param  name @type String Cookie name.
		 */
		this.deleteCookieSession = function (name) {
			_setCookie(COOKIE_PREFIX + _testName + '_' + name, '', new Date(),
				_testOptions.path, _testOptions.domain, _testOptions.secure);
		}

		/**
		 * Runs current test.
		 */
		function _runTest() {
			_allTests[_testOptions.test.name](
				_testOptions.test, {log: _log, error: _error});
		}

		/**
		 * Manages test cookie (creation and expiration) and, if cookie does not
		 * exists, draws the user.
		 *
		 * @return @type Boolean True if cookies are supported, false otherwise.
		 */
		function _userSession() {
			var cookieValue = _getCookie(COOKIE_PREFIX + _testName);
			var cookieValues = cookieValue ? cookieValue.split(';') :
				new Array(NOW, _drawUser(_testOptions.probability));
			cookieValue = cookieValues.join(';');

			var drawTime = parseInt(cookieValues[0]) || 0;
			var wasUserLucky = _luckiness = parseInt(cookieValues[1]) > 0;

			_log('The user has been drawn on '+ new Date(drawTime) +
				(drawTime == NOW ? ' (just now!)' : '') + ' and was ' +
				(wasUserLucky ? '' : 'un') + 'lucky.');

			var expirationDate = NOW - drawTime <= _getTime(_testOptions.maxSession) ?
				new Date(NOW + _getTime(_testOptions.session)) : null;

			_log('The user has' + (expirationDate ? ' not' : '') +
				' reached maxSession. Cookie expiration date is ' +
				(expirationDate ? expirationDate : 'at the end of the current session.'));

			_setCookie(COOKIE_PREFIX + _testName, cookieValue,
				expirationDate, _testOptions.path, _testOptions.domain, _testOptions.secure);

			return _getCookie(COOKIE_PREFIX + _testName) == cookieValue;
		}

		/**
		 * Draws an user.
		 *
		 * @param  probability @type Number Probability percentage (range 0..100).
		 * @return @type Number In range 0..1 0 => User is unlucky, 1 => User is lucky.
		 */
		function _drawUser(probability) {
			var isUserLucky = !Math.floor(Math.random() * 100 / probability);
			_log('Is current user lucky? Probability is ' + probability + '%... ' +
				(isUserLucky ? 'Yes!' : 'No!'));
			return new Number(isUserLucky);
		}

		/**
		 * Converts a duration object to a microseconds time.
		 *
		 * @param  duration @type object Duration object:
		 *		   @li @c days/day Day(s) amount
		 *		   @li @c hours/hour Hour(s) amount
		 *		   @li @c minutes/minute Minute(s) amount
		 * @return @type Number Duration in microseconds.
		 */
		function _getTime(duration) {
			const MIN = 60 * 1000;
			const HOUR = 60 * MIN;
			const DAY = 24 * HOUR;
			duration = duration || {};
			return (duration.days	 || 0) * DAY +
				   (duration.day	 || 0) * DAY +
				   (duration.hours	 || 0) * HOUR +
				   (duration.hour	 || 0) * HOUR +
				   (duration.minutes || 0) * MIN +
				   (duration.minute	 || 0) * MIN;
		}

		/**
		 * Get a cookie value by name.
		 *
		 * @param  name @type String Cookie name
		 * @return @type String The cookie value or undefined if no cookie
		 *		   exists with that name.
		 */
		function _getCookie(name) {
			var documentCookies = document.cookie.split(';');
			if (!documentCookies.length) {
				_log('No cookies exists.');
				return;
			}
			for (var i = 0; i < documentCookies.length; i++) {
				var currentCookie = documentCookies[i].split('=');
				if (currentCookie[0].trim() != name.trim()) {
					continue;
				}
				var cookieValue = unescape(currentCookie[1].trim());
				_log('Cookie name: ' + name + ' - Cookie value: ' + cookieValue);
				return cookieValue;
			}
			_log('Cookie ' + name + ' does not exists.');
		}

		/**
		 * Setup a new cookie or updates an existing cookie with the same name.
		 *
		 * @param  name @type String Cookie name.
		 * @param  value @type String Cookie value.
		 * @param  expires @type Date Cookie expiration date.
		 * @param  path @type String Cookie validity path.
		 * @param  domain @type String Cookie validity domain (part).
		 * @param  secure @type Boolean Is cookie valid only using https?.
		 */
		function _setCookie(name, value, expires, path, domain, secure) {
			var newCookie = name + '=' + escape(value) +
				(expires ? '; expires=' + expires.toGMTString() : '') +
				(path	 ? '; path='	+ path : '') +
				(domain	 ? '; domain='	+ domain : '') +
				(secure	 ? '; secure' : '');
			_log('Setting cookie: ' + newCookie);
			document.cookie = newCookie;
		}

		/**
		 * Logs a message.
		 *
		 * @param  message @type mixed Log message.
		 * @param  priority @type String Message prioriti (log, debug, info, warning, error).
		 */
		function _log(message, priority) {
			_logEnabled && console[priority || 'debug']('[ABTest] %s: %s',
				arguments.callee.caller.name || 'main', message);
		}

		/**
		 * Logs an error message.
		 *
		 * @param  message @type mixed Log message.
		 */
		function _error(message) {
			_log(message, 'error');
		}

		/**
		 * Tests is a function exists in current scope.
		 *
		 * @param  functionName @type String Funcion name.
		 */
		function _functionExists(functionName) {
			return typeof ABTest[functionName] == 'function';
		}

		/**
		 * String trimming function.
		 *
		 * @return @type string Trimmed string.
		 */
		String.prototype.trim = function () {
			return this.replace(/^\s+|\s+$/g, '');
		}
	}
}<commit_msg>Doc fix.<commit_after>/**
 * @file
 * ABTest.js
 *
 * Simple A/B Test library.
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://code.google.com/p/abtest-js/wiki/License
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to aldo.armiento@gmail.com so we can send you a copy immediately.
 *
 * @see http://code.google.com/p/abtest-js/
 * @author (C) 2010 Aldo Armiento (aldo.armiento@gmail.com)
 * @author Sponsored by Gruppo Immobiliare.it S.r.l. (http://www.immobiliare.it/)
 * @version $Id$
 */
if (typeof ABTest == 'undefined') {
	var ABTest = new function () {

		const COOKIE_PREFIX = 'ABTest_'; /**< @type String Cookie prefix. */

		const NOW = new Date().getTime(); /**< @type Number Current timestamp. */

		const TEST_SESSION = { hours: 48 }; /**< @type object Default test session duration. */
		const MAX_TEST_SESSION = { days: 7 }; /**< @type object Default maximum test session duration. */

		var _logEnabled = false; /**< @type Boolean True to enable event log, false otherwise. */
		var _luckiness = false; /**< @type Boolean True if user is lucky, false otherwise. */

		var _running = false; /**< @type Boolean Test is running and can not be re-executed. */
		var _testName; /**< @type String Current A/B Test name. */
		var _testOptions = {}; /**< @type object Current A/B Test options. */

		var _allTests = new Array(); /**< @type Array All loaded tests. */

		/**
		 * Starts the A/B Test.
		 *
		 * @param  name @type String The test name.
		 * @param  options @type object Test options:
		 *		   @li @c session (optional, default TEST_SESSION) Session duration
		 *		   @li @c maxSession (optional, default MAX_TEST_SESSION) Session max duration
		 *		   @li @c probability User lucky/unlucky probability
		 *		   @li @c path (optional, default /) Site path validity (cookie path)
		 *		   @li @c domain (optional, default empty) Site domain validity (cookie domain)
		 *		   @li @c secure (optional, default false) Use https protocol (cookie secure)
		 *		   @li @c test Specific test options (test must exists)
		 */
		this.start = function (name, options) {
			if (_running) {
				_error('An A/B test is already running, exiting.');
				return;
			}
			_running = true;

			if (!name || !options) {
				_error('Wrong number or type of arguments.');
				return;
			}
			options = options || {};

			options.test = options.test || {};
			if (!options.test.name) {
				_error('No test name specified.');
				return;
			}

			if (typeof _allTests[options.test.name] != 'function') {
				_error("Test '" + options.test.name + "' does not loaded.");
				return;
			}

			options.session = options.session || TEST_SESSION;
			options.maxSession = options.maxSession || MAX_TEST_SESSION;

			options.probability = options.probability || 0;
			options.path = options.path || '/';
			options.domain = options.domain || '';
			options.secure = options.secure || false;

			_testName = name;
			_testOptions = options;

			_log('Starting A/B test ' + name + '.');

			if (_userSession()) {
				_runTest();
			}
		}

		/**
		 * Enable/disable log.
		 *
		 * @param  logEnabled @type Boolean True to enable logging functions.
		 */
		this.enableLog = function (logEnabled) {
			_logEnabled = logEnabled;
		}

		/**
		 * Enable/disable log.
		 *
		 * @return @type Boolean True if the current user is lucky.
		 */
		this.isUserLucky = function () {
			return _luckiness;
		}

		/**
		 * Adds custom test.
		 *
		 * @param  testFunction @type Function Test function.
		 */
		this.addTest = function (testFunction) {
			var testName = testFunction.name;

			if (!testName) {
				_error('The test function must have a name that is the test name.');
				return;
			}
			if (_allTests[testName]) {
				_error("The test '" + testName + "' already exists.");
				return false;
			}
			_allTests[testName] = testFunction;
			return true;
		}

		/**
		 * Returns A/B Test name.
		 *
		 * @return @type String A/B Test name.
		 */
		this.getTestName = function () {
			return _testName;
		}

		/**
		 * Returns A/B Test configuration options.
		 *
		 * @return @type Object A/B Test options.
		 */
		this.getTestOptions = function () {
			return _testOptions;
		}

		/**
		 * Tests if a cookie exists.
		 *
		 * @param  name @type String Cookie name
		 * @return @type Boolean True if cookie esists, false otherwise.
		 */
		this.cookieExists = function (name) {
			return typeof _getCookie(COOKIE_PREFIX + _testName + '_' + name) != 'undefined';
		}

		/**
		 * Get a cookie value by name.
		 *
		 * @param  name @type String Cookie name
		 * @return @type String The cookie value or undefined if no cookie
		 *		   exists with that name.
		 */
		this.getCookie = function (name) {
			return _getCookie(COOKIE_PREFIX + _testName + '_' + name);
		}

		/**
		 * Setup a new session only cookie or updates an existing cookie with the
		 * same name.
		 *
		 * @param  name @type String Cookie name.
		 * @param  value @type String Cookie value.
		 */
		this.setCookieSession = function (name, value) {
			_setCookie(COOKIE_PREFIX + _testName + '_' + name, value, null,
				_testOptions.path, _testOptions.domain, _testOptions.secure);
		}

		/**
		 * Delete a new session only cookie.
		 *
		 * @param  name @type String Cookie name.
		 */
		this.deleteCookieSession = function (name) {
			_setCookie(COOKIE_PREFIX + _testName + '_' + name, '', new Date(),
				_testOptions.path, _testOptions.domain, _testOptions.secure);
		}

		/**
		 * Runs current test.
		 */
		function _runTest() {
			_allTests[_testOptions.test.name](
				_testOptions.test, {log: _log, error: _error});
		}

		/**
		 * Manages test cookie (creation and expiration) and, if cookie does not
		 * exists, draws the user.
		 *
		 * @return @type Boolean True if cookies are supported, false otherwise.
		 */
		function _userSession() {
			var cookieValue = _getCookie(COOKIE_PREFIX + _testName);
			var cookieValues = cookieValue ? cookieValue.split(';') :
				new Array(NOW, _drawUser(_testOptions.probability));
			cookieValue = cookieValues.join(';');

			var drawTime = parseInt(cookieValues[0]) || 0;
			var wasUserLucky = _luckiness = parseInt(cookieValues[1]) > 0;

			_log('The user has been drawn on '+ new Date(drawTime) +
				(drawTime == NOW ? ' (just now!)' : '') + ' and was ' +
				(wasUserLucky ? '' : 'un') + 'lucky.');

			var expirationDate = NOW - drawTime <= _getTime(_testOptions.maxSession) ?
				new Date(NOW + _getTime(_testOptions.session)) : null;

			_log('The user has' + (expirationDate ? ' not' : '') +
				' reached maxSession. Cookie expiration date is ' +
				(expirationDate ? expirationDate : 'at the end of the current session.'));

			_setCookie(COOKIE_PREFIX + _testName, cookieValue,
				expirationDate, _testOptions.path, _testOptions.domain, _testOptions.secure);

			return _getCookie(COOKIE_PREFIX + _testName) == cookieValue;
		}

		/**
		 * Draws an user.
		 *
		 * @param  probability @type Number Probability percentage (range 0..100).
		 * @return @type Number In range 0..1 0 => User is unlucky, 1 => User is lucky.
		 */
		function _drawUser(probability) {
			var isUserLucky = !Math.floor(Math.random() * 100 / probability);
			_log('Is current user lucky? Probability is ' + probability + '%... ' +
				(isUserLucky ? 'Yes!' : 'No!'));
			return new Number(isUserLucky);
		}

		/**
		 * Converts a duration object to a microseconds time.
		 *
		 * @param  duration @type object Duration object:
		 *		   @li @c days/day Day(s) amount
		 *		   @li @c hours/hour Hour(s) amount
		 *		   @li @c minutes/minute Minute(s) amount
		 * @return @type Number Duration in microseconds.
		 */
		function _getTime(duration) {
			const MIN = 60 * 1000;
			const HOUR = 60 * MIN;
			const DAY = 24 * HOUR;
			duration = duration || {};
			return (duration.days	 || 0) * DAY +
				   (duration.day	 || 0) * DAY +
				   (duration.hours	 || 0) * HOUR +
				   (duration.hour	 || 0) * HOUR +
				   (duration.minutes || 0) * MIN +
				   (duration.minute	 || 0) * MIN;
		}

		/**
		 * Get a cookie value by name.
		 *
		 * @param  name @type String Cookie name
		 * @return @type String The cookie value or undefined if no cookie
		 *		   exists with that name.
		 */
		function _getCookie(name) {
			var documentCookies = document.cookie.split(';');
			if (!documentCookies.length) {
				_log('No cookies exists.');
				return;
			}
			for (var i = 0; i < documentCookies.length; i++) {
				var currentCookie = documentCookies[i].split('=');
				if (currentCookie[0].trim() != name.trim()) {
					continue;
				}
				var cookieValue = unescape(currentCookie[1].trim());
				_log('Cookie name: ' + name + ' - Cookie value: ' + cookieValue);
				return cookieValue;
			}
			_log('Cookie ' + name + ' does not exists.');
		}

		/**
		 * Setup a new cookie or updates an existing cookie with the same name.
		 *
		 * @param  name @type String Cookie name.
		 * @param  value @type String Cookie value.
		 * @param  expires @type Date Cookie expiration date.
		 * @param  path @type String Cookie validity path.
		 * @param  domain @type String Cookie validity domain (part).
		 * @param  secure @type Boolean Is cookie valid only using https?.
		 */
		function _setCookie(name, value, expires, path, domain, secure) {
			var newCookie = name + '=' + escape(value) +
				(expires ? '; expires=' + expires.toGMTString() : '') +
				(path	 ? '; path='	+ path : '') +
				(domain	 ? '; domain='	+ domain : '') +
				(secure	 ? '; secure' : '');
			_log('Setting cookie: ' + newCookie);
			document.cookie = newCookie;
		}

		/**
		 * Logs a message.
		 *
		 * @param  message @type mixed Log message.
		 * @param  priority @type String Message prioriti (log, debug, info, warning, error).
		 */
		function _log(message, priority) {
			_logEnabled && console[priority || 'debug']('[ABTest] %s: %s',
				arguments.callee.caller.name || 'main', message);
		}

		/**
		 * Logs an error message.
		 *
		 * @param  message @type mixed Log message.
		 */
		function _error(message) {
			_log(message, 'error');
		}

		/**
		 * Tests is a function exists in current scope.
		 *
		 * @param  functionName @type String Funcion name.
		 */
		function _functionExists(functionName) {
			return typeof ABTest[functionName] == 'function';
		}

		/**
		 * String trimming function.
		 *
		 * @return @type string Trimmed string.
		 */
		String.prototype.trim = function () {
			return this.replace(/^\s+|\s+$/g, '');
		}
	}
}<|endoftext|>