<commit_before>// HOMEPAGE CLASS INFO
todayInfo = false;
userInfo = false;
schedule = false;

function updateHomepageInfo(){
  var content = "
";

  if(schedule){
    var cInfo = todayInfo.currentInfo;
    $("#classInfo").html("");
    $("#cInfo-table tr").removeClass("sucess");

    //console.log(sInfo);
    if(cInfo.nowClass !== false){
      if((cInfo.nowClass.className == "Unstructured Time" || cInfo.justStarted.className == "Unstructured Time") && sessionStorage['user-status'] == "In Class")
        setStatus("Available");
      else if(cInfo.nowClass.className != "Unstructured Time" && cInfo.justStarted.className != "Unstructured Time")
        setStatus("In Class");

      if(cInfo.nowClass == "between"){
        content += "
"+cInfo.justEnded.className+" has just ended.
";
        if(cInfo.justStarted != false){
          content += "
Get to "+cInfo.justStarted.room+" for "+cInfo.justStarted.className+"
";
          $("#cInfo-table td:contains('"+moment(cInfo.justStarted.startTime, "hh:mm A").format("h:mm A")+"')").parent().addClass("success");
        }
      }else if(cInfo.nowClass.className == "Unstructured Time") {
        // FREE PERIOD
        content += "You currently have a Free Period for "+moment(cInfo.nowClass.endTime, "hh:mm A").fromNow(true)+"";
      }else{
        // Regular class
        content += "
You should currently be in "+cInfo.nowClass.room+" for "+cInfo.nowClass.className+"
";
      }
      $("#cInfo-table tbody tr td:contains('"+moment(cInfo.nowClass.className, "hh:mm A").format("h:mm A")+"')").parent().addClass("success");
      if(cInfo.nextClass !== false && cInfo.nextClass.className !== "Afternoon Advisement"){
        content += "
Your next class is "+cInfo.nextClass.className+" in "+cInfo.nextClass.room+" in "+moment(cInfo.nextClass.startTime, "hh:mm A").fromNow(true)+"
";
      }else{
        if(cInfo.nowClass !== "between")
          content += "
This is the last class of your day!
";
      }

      $("#classInfo").html(content);
    }else{
      // out of school
      if(sessionStorage['user-status'] == "In Class"){
        setStatus("Available");
      }
    }
  }
}

function updateSidebarClassInfo(){
  var content = "";
  if(schedule){
    var cInfo = todayInfo.currentInfo;
    if(cInfo.nowClass !== false){
      $("#sidebar-class-info").show();
      if(cInfo.nowClass == "between"){
        if(cInfo.justStarted != false){
          $("#sidebar-now-class").text(cInfo.justStarted.className.split(")")[0]+")");
          $("#nowclass-room").text(cInfo.justStarted.room);
        }
      }else if(cInfo.nowClass.className == "Unstructured Time") {
        // FREE PERIOD
        $("#sidebar-now-class").text("Free Period");
      }else{
        // Regular class
        $("#sidebar-now-class").text(cInfo.nowClass.className.split(")")[0]+")");
        $("#nowclass-room").text(cInfo.nowClass.room);
      }

      if(cInfo.nextClass !== false && cInfo.nextClass.className !== "Afternoon Advisement"){
        $("#sd-nextc").show();
        $("#sidebar-next-class").text(cInfo.nextClass.className.split(")")[0]+")");
        if(cInfo.nextClass.room !== "Anywhere")
          $("#nextclass-room").text(cInfo.nextClass.room);
      }else{
        $("#sd-nextc").hide();
      }
    }else{
      $("#sidebar-class-info").hide();
    }
  }
}

var hasClasses = false;
var registered = true;
var profileUserInfo = null;
function updateProfileSchedule(){
  var profileUsername = window.location.href.split("/")[2];
  if(!profileUserInfo){
    $.get('/api/user/'+username, function(data){
      if(data){
        console.log(data);
        registered = data.registered;
        if(data.registered == false){
          $("#profile-schedule").hide();
          return;
        }
        hasClasses = true;
        profileUserInfo = data;
        updateProfileSchedule();
      }
    });
  }else{
    if(profileUserInfo.todaysClassesInfo.currentInfo.inSchool == true && profileUserInfo.todaysClassesInfo.currentInfo !== false){
      var cInfo = profileUserInfo.todaysClassesInfo.currentInfo;
      if(cInfo.now !== false || cInfo.justStarted !== false){
        var n = false;
        if(cInfo.now != false)
          n = cInfo.now;
        if(cInfo.justStarted != false)
          n = cInfo.justStarted;

        if(n !== false){
          console.log(n);
          $("#profile-schedule").show();
          $("#profile-schedule p").html(""+profileUserInfo.firstName+" currently has "+n.className+" in "+n.room+" until "+moment(n.endTime, "hh:mm A").format("h:mm A")+"");
        }
      }
    }else{
      $("#profile-schedule").hide();
    }
  }
}

function clientSchedule(){
  $.get('/api/user/'+username, function(data){
    console.log(data);
    userInfo = data;
    todayInfo = userInfo.todaysClassesInfo;
    console.log(todayInfo);

    if(todayInfo){
      schedule = getCurrentClassInfo(todayInfo.periods);
      updateDayInfo();
      setInterval(updateDayInfo, 60000);
    }else{
      console.log("Not a school day, not updating class info.");
    }
  });
}

function updateDayInfo(){
  if(todayInfo)
    schedule = getCurrentClassInfo(todayInfo.periods);
  if($("#classInfo").length)
    updateHomepageInfo();

  if($("#profile-schedule").length && registered)
    updateProfileSchedule();

  updateSidebarClassInfo();
  console.log("Updated schedule info");
}

function getCurrentClassInfo(periods){
  var mom = moment();
  var dayStart = moment("08:40 AM", "hh:mm A");
  var dayEnd = moment("02:50 PM", "hh:mm A");

  var now = false;
  var next = false;
  var justEnded = false;
  var justStarted = false;
  var inSchool = false;

  if(periods !== false && mom.isBetween(dayStart, dayEnd)){
    inSchool = true;
    // THERE ARE CLASSES SCHEDULED FOR TODAY

    // Loop through today's classes to find the currently ongoing
    now = periods.filter(function(period) {
      if(mom.isBetween(moment(period.startTime, "hh:mm A"), moment(period.endTime, "hh:mm A"))){
        return true;
      }
      return false;
    });
    if(now.length >= 1) now = now[0];
    // If there is no such period (and it is during the school day)

    // Try to find a class that just ended
    var cur = mom;
    var found = periods.filter(function(period) {
      if(cur.isSame(moment(period.endTime, "hh:mm A"))){
        return true;
      }
      return false;
    });
    if(found.length == 1){ justEnded = found[0];}else{justEnded = false;}
    // If none found set to false

    // Try to find a class that just started
    var found = periods.filter(function(period) {
      if(cur.isSame(moment(period.startTime, "hh:mm A"))){
        return true;
      }
      return false;
    });
    if(found.length == 1){ justStarted = found[0];}else{justStarted = false;}
    // If none set to false

    // If a class jas just ended and another jas just started, you are in between adjacent classes
    if(justStarted !== false && justEnded !== false){
      now = "between";
    }

    // Get the next class
    if(now !== "between")
      next = ((periods.length-1 > periods.indexOf(now)) ? periods[periods.indexOf(now)+1] : false);

  }

  return {
    nowClass: now,
    nextClass: next,
    justStarted: justStarted,
    justEnded: justEnded,
    inSchool: inSchool
  };
};
<commit_msg>All schedule info views are actually fixed<commit_after>// HOMEPAGE CLASS INFO
todayInfo = false;
userInfo = false;
schedule = false;

function updateHomepageInfo(){
  var content = "
";

  if(schedule){
    var cInfo = schedule;
    $("#classInfo").html("");
    $("#cInfo-table tr").removeClass("sucess");

    //console.log(sInfo);
    if(cInfo.nowClass !== false){
      if((cInfo.nowClass.className == "Unstructured Time" || cInfo.justStarted.className == "Unstructured Time") && sessionStorage['user-status'] == "In Class")
        setStatus("Available");
      else if(cInfo.nowClass.className != "Unstructured Time" && cInfo.justStarted.className != "Unstructured Time")
        setStatus("In Class");

      if(cInfo.nowClass == "between"){
        content += "
"+cInfo.justEnded.className+" has just ended.
";
        if(cInfo.justStarted != false){
          content += "
Get to "+cInfo.justStarted.room+" for "+cInfo.justStarted.className+"
";
          $("#cInfo-table td:contains('"+moment(cInfo.justStarted.startTime, "hh:mm A").format("h:mm A")+"')").parent().addClass("success");
        }
      }else if(cInfo.nowClass.className == "Unstructured Time") {
        // FREE PERIOD
        content += "You currently have a Free Period for "+moment(cInfo.nowClass.endTime, "hh:mm A").fromNow(true)+"";
      }else{
        // Regular class
        content += "
You should currently be in "+cInfo.nowClass.room+" for "+cInfo.nowClass.className+"
";
      }
      $("#cInfo-table tbody tr td:contains('"+moment(cInfo.nowClass.className, "hh:mm A").format("h:mm A")+"')").parent().addClass("success");
      if(cInfo.nextClass !== false && cInfo.nextClass.className !== "Afternoon Advisement"){
        content += "
Your next class is "+cInfo.nextClass.className+" in "+cInfo.nextClass.room+" in "+moment(cInfo.nextClass.startTime, "hh:mm A").fromNow(true)+"
";
      }else{
        if(cInfo.nowClass !== "between")
          content += "
This is the last class of your day!
";
      }

      $("#classInfo").html(content);
    }else{
      // out of school
      if(sessionStorage['user-status'] == "In Class"){
        setStatus("Available");
      }
    }
  }
}

function updateSidebarClassInfo(){
  var content = "";
  if(schedule){
    var cInfo = schedule;
    if(cInfo.nowClass !== false){
      $("#sidebar-class-info").show();
      if(cInfo.nowClass == "between"){
        if(cInfo.justStarted != false){
          $("#sidebar-now-class").text(cInfo.justStarted.className.split(")")[0]+")");
          $("#nowclass-room").text(cInfo.justStarted.room);
        }
      }else if(cInfo.nowClass.className == "Unstructured Time") {
        // FREE PERIOD
        $("#sidebar-now-class").text("Free Period");
      }else{
        // Regular class
        $("#sidebar-now-class").text(cInfo.nowClass.className.split(")")[0]+")");
        $("#nowclass-room").text(cInfo.nowClass.room);
      }

      if(cInfo.nextClass !== false && cInfo.nextClass.className !== "Afternoon Advisement"){
        $("#sd-nextc").show();
        $("#sidebar-next-class").text(cInfo.nextClass.className.split(")")[0]+")");
        if(cInfo.nextClass.room !== "Anywhere")
          $("#nextclass-room").text(cInfo.nextClass.room);
      }else{
        $("#sd-nextc").hide();
      }
    }else{
      $("#sidebar-class-info").hide();
    }
  }
}

var hasClasses = false;
var registered = true;
var profileUserInfo = null;
function updateProfileSchedule(){
  var profileUsername = window.location.href.split("/")[2];
  if(!profileUserInfo){
    $.get('/api/user/'+username, function(data){
      if(data){
        console.log(data);
        registered = data.registered;
        if(data.registered == false){
          $("#profile-schedule").hide();
          return;
        }
        hasClasses = true;
        profileUserInfo = data;
        updateProfileSchedule();
      }
    });
  }else{
    var cInfo = getCurrentClassInfo(profileUserInfo.todaysClassesInfo.periods);
    if(cInfo.inSchool == true && cInfo !== false){
      if(cInfo.now !== false || cInfo.justStarted !== false){
        var n = false;
        if(cInfo.now != false)
          n = cInfo.now;
        if(cInfo.justStarted != false)
          n = cInfo.justStarted;

        if(n !== false){
          console.log(n);
          $("#profile-schedule").show();
          $("#profile-schedule p").html(""+profileUserInfo.firstName+" currently has "+n.className+" in "+n.room+" until "+moment(n.endTime, "hh:mm A").format("h:mm A")+"");
        }
      }
    }else{
      $("#profile-schedule").hide();
    }
  }
}

function clientSchedule(){
  $.get('/api/user/'+username, function(data){
    console.log(data);
    userInfo = data;
    todayInfo = userInfo.todaysClassesInfo;
    console.log(todayInfo);

    if(todayInfo){
      schedule = getCurrentClassInfo(todayInfo.periods);
      updateDayInfo();
      setInterval(updateDayInfo, 60000);
    }else{
      console.log("Not a school day, not updating class info.");
    }
  });
}

function updateDayInfo(){
  schedule = getCurrentClassInfo(todayInfo.periods);

  if($("#classInfo").length)
    updateHomepageInfo();

  if($("#profile-schedule").length && registered)
    updateProfileSchedule();

  updateSidebarClassInfo();
  console.log("Updated schedule info");
}

function getCurrentClassInfo(periods){
  var mom = moment();
  var dayStart = moment("08:40 AM", "hh:mm A");
  var dayEnd = moment("02:50 PM", "hh:mm A");

  var now = false;
  var next = false;
  var justEnded = false;
  var justStarted = false;
  var inSchool = false;

  if(periods !== false && mom.isBetween(dayStart, dayEnd)){
    inSchool = true;
    // THERE ARE CLASSES SCHEDULED FOR TODAY

    // Loop through today's classes to find the currently ongoing
    now = periods.filter(function(period) {
      if(mom.isBetween(moment(period.startTime, "hh:mm A"), moment(period.endTime, "hh:mm A"))){
        return true;
      }
      return false;
    });
    if(now.length >= 1) now = now[0];
    // If there is no such period (and it is during the school day)

    // Try to find a class that just ended
    var cur = mom;
    var found = periods.filter(function(period) {
      if(cur.isSame(moment(period.endTime, "hh:mm A"))){
        return true;
      }
      return false;
    });
    if(found.length == 1){ justEnded = found[0];}else{justEnded = false;}
    // If none found set to false

    // Try to find a class that just started
    var found = periods.filter(function(period) {
      if(cur.isSame(moment(period.startTime, "hh:mm A"))){
        return true;
      }
      return false;
    });
    if(found.length == 1){ justStarted = found[0];}else{justStarted = false;}
    // If none set to false

    // If a class jas just ended and another jas just started, you are in between adjacent classes
    if(justStarted !== false && justEnded !== false){
      now = "between";
    }

    // Get the next class
    if(now !== "between")
      next = ((periods.length-1 > periods.indexOf(now)) ? periods[periods.indexOf(now)+1] : false);

  }

  return {
    nowClass: now,
    nextClass: next,
    justStarted: justStarted,
    justEnded: justEnded,
    inSchool: inSchool
  };
};
<|endoftext|>


<commit_before>// javascript:

var u;
try {
    u = document.querySelector("link[rel=canonical]").href;
} catch (e) {}

if (u) {
    if (prompt("Canonical URL: - Press OK to navigate", u)) {
        window.location = u;
    } else {
        /* Chrome goes silly if I don't do something here */
        u = null;
    }
} else {
    u = window.location.href;
    /* Lack of PCRE lookahead/behind assertions makes me sad :( */
    u = u
        .replace(/([?&])((utm_(source|medium|term|content|campaign))(=?.*?)(&|$))+/g, '$1')
        .replace(/[?&]$/g, '');
    prompt("No canonical URL found. Displaying current URL:", u);
}
<commit_msg>Tweaked so that utm_* args get stripped from the canonical URL, too. Thanks, Patreon!<commit_after>// javascript:

var u, loc=window.location, canon=false;
try {
    u = document.querySelector("link[rel=canonical]").href;
    canon = true;
} catch (e) {
    u = loc.href;
}

/* Some sites (Patreon) actually include tracking spam in the canonical URL! So we have to strip out these args whether the URL came from the `link[rel=canonical]` tag or from `window.location`... */
/* Lack of PCRE lookahead/behind assertions makes me sad :( */
u = u
    .replace(/([?&])((utm_(source|medium|term|content|campaign))(=.*?)?(&|$))+/g, '$1')
    .replace(/[?&]$/g, '');

if (canon) {
    if (prompt("Canonical URL: - Press OK to navigate", u)) {
        loc.replace(u);
    } else {
        /* Chrome goes silly if I don't do something here */
        u = null;
    }
} else {
    if (prompt("No rel=canonical tag found. Displaying (cleaned) current URL: - Press OK to navigate", u)) {
        loc.replace(u);
    } else {
        /* Chrome goes silly if I don't do something here */
        u = null;
    }
}
<|endoftext|>


<commit_before>import React from 'react'
import styled from 'styled-components'
import PropTypes from 'prop-types'
import {
	compose,
	withContext,
	withState,
	lifecycle,
	getContext,
} from 'recompact'
import { ToastContainer } from 'react-toastify'
import { setsCampaign, withCampaignData } from '../data/campaign'
import Icon from '../visual/icon'
import { H3 } from '../visual/heading'
import { withUserData, logout } from '../utils/logged-in'
import Logo from '../visual/logo'
import Grid from '../visual/grid'
import Title from '../utils/title'
import User from '../document/user'
import { iAmOwner } from '../data/owner'
import { Toolbar, MenuLink, Divider, NavArea, Space } from '../visual/menu'
import Ribbon from '../visual/ribbon'

import 'react-toastify/dist/ReactToastify.min.css'

const LogoutButton = withUserData(({ user }) =>
	user ? (
		<User user={user} component={MenuLink} href='/logout' onClick={logout} />
	) : null,
)

const MenuTitle = styled(H3)`
	display: inline-block;
	margin: 0;
	align-self: center;
	white-space: nowrap;
`

const MenuLogo = styled(Logo)`
	margin: -0.3rem 0;
`

const CampaignTitle = ({ campaign }) => (
	<MenuLink href={`/${campaign._id}`}>
		<MenuTitle>{campaign.title}</MenuTitle>
	</MenuLink>
)

const connectNav = compose(
	withCampaignData,
	iAmOwner('campaign'),
	withUserData,
)

const Nav = connectNav(({ campaign, isOwner, extraItems }) => (
	<Toolbar>
		<NavArea>
			<MenuLink href='/'>
				<MenuLogo />
			</MenuLink>

			{campaign && (
				<>
					<Divider />
					<CampaignTitle campaign={campaign} />

					{isOwner && (
						<>
							<MenuLink href={`/${campaign._id}/dashboard-control`}>
								<Icon icon='wooden-sign' />
								Dashboard
							</MenuLink>

							<MenuLink href={`/${campaign._id}/players`}>
								<Icon icon='double-team' />
								Players
							</MenuLink>

							<MenuLink href={`/${campaign._id}/settings`}>
								<Icon icon='gears' />
								Settings
							</MenuLink>
						</>
					)}
				</>
			)}
		</NavArea>

		<NavArea>
			<Space />
			{extraItems}
			<LogoutButton />
		</NavArea>
	</Toolbar>
))

const navContext = {
	setExtraNavItems: PropTypes.func,
	setNavShown: PropTypes.func,
}

export const withNavContext = getContext(navContext)

const navState = withState('state', 'setState', {
	extraItems: [],
	navShown: true,
})

const setNavContext = withContext(navContext, ({ setState, state }) => ({
	setExtraNavItems(...extraItems) {
		setState({
			...state,
			extraItems,
		})
	},

	setNavShown(navShown = true) {
		setState({
			...state,
			navShown,
		})
	},
}))

export const hidesNav = compose(
	withNavContext,
	lifecycle({
		componentDidMount() {
			this.props.setNavShown(false)
		},

		componentWillUnmount() {
			this.props.setNavShown(true)
		},
	}),
)

export const withExtraNavItems = (...navItems) =>
	compose(
		withNavContext,
		lifecycle({
			componentDidMount() {
				this.props.setExtraNavItems(
					...navItems.map(NavItem => (
						<NavItem {...this.props} key={NavItem.name} />
					)),
				)
			},

			componentWillUnmount() {
				this.props.setExtraNavItems()
			},
		}),
	)

const connectLayout = compose(
	navState,
	setNavContext,
	setsCampaign,
)

const BasicLayout = ({ children }) => (
	<>
		<Title />
		<ToastContainer autoClose={10000} />
		{children}
	</>
)

export const Basic = setsCampaign(BasicLayout)

const Layout = connectLayout(({ state, children, ready }) => (
	<BasicLayout>
		<Ribbon
			href='https://github.com/quarterto/almanac/wiki/Almanac-is-in-beta'
			target='_blank'
		>
			Beta
		</Ribbon>
		{state.navShown && <Nav extraItems={state.extraItems} />}

		<Grid>{ready ? children : 'loading...'}</Grid>
	</BasicLayout>
))

export default Layout
<commit_msg>add non-control dashboard link for non-owners<commit_after>import React from 'react'
import styled from 'styled-components'
import PropTypes from 'prop-types'
import {
	compose,
	withContext,
	withState,
	lifecycle,
	getContext,
} from 'recompact'
import { ToastContainer } from 'react-toastify'
import { setsCampaign, withCampaignData } from '../data/campaign'
import Icon from '../visual/icon'
import { H3 } from '../visual/heading'
import { withUserData, logout } from '../utils/logged-in'
import Logo from '../visual/logo'
import Grid from '../visual/grid'
import Title from '../utils/title'
import User from '../document/user'
import { iAmOwner } from '../data/owner'
import { Toolbar, MenuLink, Divider, NavArea, Space } from '../visual/menu'
import Ribbon from '../visual/ribbon'

import 'react-toastify/dist/ReactToastify.min.css'

const LogoutButton = withUserData(({ user }) =>
	user ? (
		<User user={user} component={MenuLink} href='/logout' onClick={logout} />
	) : null,
)

const MenuTitle = styled(H3)`
	display: inline-block;
	margin: 0;
	align-self: center;
	white-space: nowrap;
`

const MenuLogo = styled(Logo)`
	margin: -0.3rem 0;
`

const CampaignTitle = ({ campaign }) => (
	<MenuLink href={`/${campaign._id}`}>
		<MenuTitle>{campaign.title}</MenuTitle>
	</MenuLink>
)

const connectNav = compose(
	withCampaignData,
	iAmOwner('campaign'),
	withUserData,
)

const Nav = connectNav(({ campaign, isOwner, extraItems }) => (
	<Toolbar>
		<NavArea>
			<MenuLink href='/'>
				<MenuLogo />
			</MenuLink>

			{campaign && (
				<>
					<Divider />
					<CampaignTitle campaign={campaign} />

					{isOwner ? (
						<>
							<MenuLink href={`/${campaign._id}/dashboard-control`}>
								<Icon icon='wooden-sign' />
								Dashboard
							</MenuLink>

							<MenuLink href={`/${campaign._id}/players`}>
								<Icon icon='double-team' />
								Players
							</MenuLink>

							<MenuLink href={`/${campaign._id}/settings`}>
								<Icon icon='gears' />
								Settings
							</MenuLink>
						</>
					) : (
						<MenuLink href={`/${campaign._id}/dashboard`}>
							<Icon icon='wooden-sign' />
							Dashboard
						</MenuLink>
					)}
				</>
			)}
		</NavArea>

		<NavArea>
			<Space />
			{extraItems}
			<LogoutButton />
		</NavArea>
	</Toolbar>
))

const navContext = {
	setExtraNavItems: PropTypes.func,
	setNavShown: PropTypes.func,
}

export const withNavContext = getContext(navContext)

const navState = withState('state', 'setState', {
	extraItems: [],
	navShown: true,
})

const setNavContext = withContext(navContext, ({ setState, state }) => ({
	setExtraNavItems(...extraItems) {
		setState({
			...state,
			extraItems,
		})
	},

	setNavShown(navShown = true) {
		setState({
			...state,
			navShown,
		})
	},
}))

export const hidesNav = compose(
	withNavContext,
	lifecycle({
		componentDidMount() {
			this.props.setNavShown(false)
		},

		componentWillUnmount() {
			this.props.setNavShown(true)
		},
	}),
)

export const withExtraNavItems = (...navItems) =>
	compose(
		withNavContext,
		lifecycle({
			componentDidMount() {
				this.props.setExtraNavItems(
					...navItems.map(NavItem => (
						<NavItem {...this.props} key={NavItem.name} />
					)),
				)
			},

			componentWillUnmount() {
				this.props.setExtraNavItems()
			},
		}),
	)

const connectLayout = compose(
	navState,
	setNavContext,
	setsCampaign,
)

const BasicLayout = ({ children }) => (
	<>
		<Title />
		<ToastContainer autoClose={10000} />
		{children}
	</>
)

export const Basic = setsCampaign(BasicLayout)

const Layout = connectLayout(({ state, children, ready }) => (
	<BasicLayout>
		<Ribbon
			href='https://github.com/quarterto/almanac/wiki/Almanac-is-in-beta'
			target='_blank'
		>
			Beta
		</Ribbon>
		{state.navShown && <Nav extraItems={state.extraItems} />}

		<Grid>{ready ? children : 'loading...'}</Grid>
	</BasicLayout>
))

export default Layout
<|endoftext|>


<commit_before>import Vue from 'vue'
import Router from 'vue-router'
import Index from '../pages/Index.vue'
import Availability from '../pages/Availability.vue'

Vue.use(Router);

export default new Router({
    mode: 'history',
    routes: [
        {
            path: '/',
            name: 'index',
            component: Availability //TODO Swap these back
        },
        {
            path: '/availability',
            name: 'availability',
            component: Index
        }
    ]
})<commit_msg>Changed the pages back to their proper locations<commit_after>import Vue from 'vue'
import Router from 'vue-router'
import Index from '../pages/Index.vue'
import Availability from '../pages/Availability.vue'

Vue.use(Router);

export default new Router({
    mode: 'history',
    routes: [
        {
            path: '/',
            name: 'index',
            component: Index
        },
        {
            path: '/availability',
            name: 'availability',
            component: Availability
        }
    ]
})<|endoftext|>


<commit_before>import Numeral from "numeral";
import { sampleSize, get, startCase, capitalize } from "lodash";

export const taskDisplayNames = {
    nuvs: "NuVs",
    pathoscope_bowtie: "PathoscopeBowtie",
    pathoscope_snap: "PathoscopeSNAP"
};

export const getTaskDisplayName = (taskPrefix) => get(taskDisplayNames, taskPrefix, startCase(taskPrefix));

export const numberDictionary = {
    0: "zero",
    1: "one",
    2: "two",
    3: "three",
    4: "four",
    5: "five",
    6: "six",
    7: "seven",
    8: "eight",
    9: "nine"
};

const alphanumeric = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

export const createRandomString = (length=8) => {
    return sampleSize(alphanumeric, length).join("")
};

export const numberToWord = (number) => numberDictionary[Number(number)] || number;

export const byteSize = bytes => Numeral(bytes).format("0.0 b");

export const toScientificNotation = (number) => {
    if (number < 0.01 || number > 1000) {
        const split = number.toExponential().split("e");
        const exponent = split[1].replace("+", "");
        return Numeral(split[0]).format("0.00") + "ₑ" + exponent;
    }

    return Numeral(number).format("0.000");
};

export const formatIsolateName = (isolate) => {
    if (
        isolate.source_type && isolate.source_type !== "unknown" ||
        isolate.sourceType && isolate.sourceType !== "unknown"
    ) {
        return (
            capitalize(isolate.source_type || isolate.sourceType) + " "
            + (isolate.source_name || isolate.sourceName)
        );
    }

    return "Unnamed";
};

export const followDownload = (path) => {
    const a = document.createElement("A");
    a.href = path;
    a.download = path.substr(path.lastIndexOf("/") + 1);
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
};

export const versionComparator = (a, b) => {
    let splitA = a.replace("v", "").split("-")[0];
    let splitB = b.replace("v", "").split("-")[0];

    if (splitA === splitB) {
        return 0;
    }

    splitA = splitA.split(".");
    splitB = splitB.split(".");

    for (let i = 0; i < 3; i++) {
        if (splitA[i] > splitB[i]) {
            return 1;
        }

        if (splitA[i] < splitB[i]) {
            return -1;
        }
    }

    throw("Could not compare versions");
};
<commit_msg>Add client util for downloading dynamically generated files<commit_after>import Numeral from "numeral";
import { sampleSize, get, startCase, capitalize } from "lodash";

export const taskDisplayNames = {
    nuvs: "NuVs",
    pathoscope_bowtie: "PathoscopeBowtie",
    pathoscope_snap: "PathoscopeSNAP"
};

export const getTaskDisplayName = (taskPrefix) => get(taskDisplayNames, taskPrefix, startCase(taskPrefix));

export const numberDictionary = {
    0: "zero",
    1: "one",
    2: "two",
    3: "three",
    4: "four",
    5: "five",
    6: "six",
    7: "seven",
    8: "eight",
    9: "nine"
};

const alphanumeric = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

export const createRandomString = (length=8) => {
    return sampleSize(alphanumeric, length).join("")
};

export const numberToWord = (number) => numberDictionary[Number(number)] || number;

export const byteSize = bytes => Numeral(bytes).format("0.0 b");

export const toScientificNotation = (number) => {
    if (number < 0.01 || number > 1000) {
        const split = number.toExponential().split("e");
        const exponent = split[1].replace("+", "");
        return Numeral(split[0]).format("0.00") + "ₑ" + exponent;
    }

    return Numeral(number).format("0.000");
};

export const formatIsolateName = (isolate) => {
    if (
        isolate.source_type && isolate.source_type !== "unknown" ||
        isolate.sourceType && isolate.sourceType !== "unknown"
    ) {
        return (
            capitalize(isolate.source_type || isolate.sourceType) + " "
            + (isolate.source_name || isolate.sourceName)
        );
    }

    return "Unnamed";
};

export const followDownload = (path) => {
    const a = document.createElement("A");
    a.href = path;
    a.download = path.substr(path.lastIndexOf("/") + 1);
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
};

export const followDynamicDownload = (filename, text) => {
    const a = document.createElement("a");
    a.href = `data:text/plain;charset=utf-8,${encodeURIComponent(text)}`;
    a.download = filename;

    a.style.display = "none";
    document.body.appendChild(a);

    a.click();

    document.body.removeChild(a);
};

export const versionComparator = (a, b) => {
    let splitA = a.replace("v", "").split("-")[0];
    let splitB = b.replace("v", "").split("-")[0];

    if (splitA === splitB) {
        return 0;
    }

    splitA = splitA.split(".");
    splitB = splitB.split(".");

    for (let i = 0; i < 3; i++) {
        if (splitA[i] > splitB[i]) {
            return 1;
        }

        if (splitA[i] < splitB[i]) {
            return -1;
        }
    }

    throw("Could not compare versions");
};
<|endoftext|>


<commit_before>// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

cr.define('options', function() {

  var Preferences = options.Preferences;
  /////////////////////////////////////////////////////////////////////////////
  // PrefCheckbox class:

  // Define a constructor that uses an input element as its underlying element.
  var PrefCheckbox = cr.ui.define('input');

  PrefCheckbox.prototype = {
    // Set up the prototype chain
    __proto__: HTMLInputElement.prototype,

    /**
     * Initialization function for the cr.ui framework.
     */
    decorate: function() {
      this.type = 'checkbox';
      var self = this;

      // Listen to pref changes.
      Preferences.getInstance().addEventListener(this.pref,
          function(event) {
            var value = event.value && event.value['value'] != undefined ?
                event.value['value'] : event.value;
            // Invert pref value if inverted_pref == true.
            if (self.inverted_pref)
              self.checked = !Boolean(value);
            else
              self.checked = Boolean(value);
            self.managed = event.value && event.value['managed'] != undefined ?
                event.value['managed'] : false;
            self.disabled = self.managed;
            // Honor manually_disabled property, so options pages can
            // disable preferences manually when needed.
            if (self.manually_disabled) {
              self.disabled = true;
            }
          });

      // Listen to user events.
      this.addEventListener('click',
          function(e) {
            var value = self.inverted_pref ? !self.checked : self.checked;
            switch(self.valueType) {
              case 'number':
                Preferences.setIntegerPref(self.pref,
                    Number(value), self.metric);
                break;
              case 'boolean':
                Preferences.setBooleanPref(self.pref,
                    value, self.metric);
                break;
            }
          });

      // Initialize options.
      this.ownerDocument.addEventListener('DOMContentLoaded',
          function() {
            self.initializeValueType(self.getAttribute('value-type'));
          });
    },

    /**
     * Sets up options in checkbox element.
     * @param {String} valueType The preference type for this checkbox.
     */
    initializeValueType: function(valueType) {
      this.valueType = valueType || 'boolean';
    }
  };

  /**
   * The preference name.
   * @type {string}
   */
  cr.defineProperty(PrefCheckbox, 'pref', cr.PropertyKind.ATTR);

  /**
   * The user metric string.
   * @type {string}
   */
  cr.defineProperty(PrefCheckbox, 'metric', cr.PropertyKind.ATTR);

  /**
   * Whether to use inverted pref value.
   * @type {boolean}
   */
  cr.defineProperty(PrefCheckbox, 'inverted_pref', cr.PropertyKind.BOOL_ATTR);

  /////////////////////////////////////////////////////////////////////////////
  // PrefRadio class:

  //Define a constructor that uses an input element as its underlying element.
  var PrefRadio = cr.ui.define('input');

  PrefRadio.prototype = {
    // Set up the prototype chain
    __proto__: HTMLInputElement.prototype,

    /**
     * Initialization function for the cr.ui framework.
     */
    decorate: function() {
      this.type = 'radio';
      var self = this;

      // Listen to pref changes.
      Preferences.getInstance().addEventListener(this.pref,
          function(event) {
            var value = event.value && event.value['value'] != undefined ?
                event.value['value'] : event.value;
            self.managed = event.value && event.value['managed'] != undefined ?
                event.value['managed'] : false;
            self.checked = String(value) == self.value;
            self.disabled = self.managed;
            // Honor manually_disabled property, so options pages can
            // disable preferences manually when needed.
            if (self.manually_disabled) {
              self.disabled = true;
            }
          });

      // Listen to user events.
      this.addEventListener('change',
          function(e) {
            if(self.value == 'true' || self.value == 'false') {
              Preferences.setBooleanPref(self.pref,
                  self.value == 'true', self.metric);
            } else {
              Preferences.setIntegerPref(self.pref,
                  parseInt(self.value, 10), self.metric);
            }
          });
    },

    /**
     * Getter for preference name attribute.
     */
    get pref() {
      return this.getAttribute('pref');
    },

    /**
     * Setter for preference name attribute.
     */
    set pref(name) {
      this.setAttribute('pref', name);
    }
  };

  /**
   * The user metric string.
   * @type {string}
   */
  cr.defineProperty(PrefRadio, 'metric', cr.PropertyKind.ATTR);

  /////////////////////////////////////////////////////////////////////////////
  // PrefNumeric class:

  // Define a constructor that uses an input element as its underlying element.
  var PrefNumeric = function() {};
  PrefNumeric.prototype = {
    // Set up the prototype chain
    __proto__: HTMLInputElement.prototype,

    /**
     * Initialization function for the cr.ui framework.
     */
    decorate: function() {
      var self = this;

      // Listen to pref changes.
      Preferences.getInstance().addEventListener(this.pref,
          function(event) {
            self.value = event.value && event.value['value'] != undefined ?
                event.value['value'] : event.value;
            self.managed = event.value && event.value['managed'] != undefined ?
                event.value['managed'] : false;
            self.disabled = self.managed;
          });

      // Listen to user events.
      this.addEventListener('change',
          function(e) {
            if (this.validity.valid) {
              Preferences.setIntegerPref(self.pref, self.value, self.metric);
            }
          });
    }
  };

  /**
   * The preference name.
   * @type {string}
   */
  cr.defineProperty(PrefNumeric, 'pref', cr.PropertyKind.ATTR);

  /**
   * The user metric string.
   * @type {string}
   */
  cr.defineProperty(PrefNumeric, 'metric', cr.PropertyKind.ATTR);

  /////////////////////////////////////////////////////////////////////////////
  // PrefNumber class:

  // Define a constructor that uses an input element as its underlying element.
  var PrefNumber = cr.ui.define('input');

  PrefNumber.prototype = {
    // Set up the prototype chain
    __proto__: PrefNumeric.prototype,

    /**
     * Initialization function for the cr.ui framework.
     */
    decorate: function() {
      this.type = 'number';
      PrefNumeric.prototype.decorate.call(this);

      // Listen to user events.
      this.addEventListener('input',
          function(e) {
            if (this.validity.valid) {
              Preferences.setIntegerPref(self.pref, self.value, self.metric);
            }
          });
    }
  };

  /////////////////////////////////////////////////////////////////////////////
  // PrefRange class:

  // Define a constructor that uses an input element as its underlying element.
  var PrefRange = cr.ui.define('input');

  PrefRange.prototype = {
    // Set up the prototype chain
    __proto__: PrefNumeric.prototype,

    /**
     * Initialization function for the cr.ui framework.
     */
    decorate: function() {
      this.type = 'range';
      PrefNumeric.prototype.decorate.call(this);
      var self = this;

      // Additionally change the indicator as well.
      Preferences.getInstance().addEventListener(this.pref,
          function(event) {
            self.updateIndicator();
          });

      // Listen to user events.
      this.addEventListener('input',
          function(e) {
            this.updateIndicator();
          });
    },

    updateIndicator: function() {
      if ($(this.id + '-value')) {
        $(this.id + '-value').textContent = this.value;
      }
    }
  };

  /////////////////////////////////////////////////////////////////////////////
  // PrefSelect class:

  // Define a constructor that uses an select element as its underlying element.
  var PrefSelect = cr.ui.define('select');

  PrefSelect.prototype = {
    // Set up the prototype chain
    __proto__: HTMLSelectElement.prototype,

    /**
    * Initialization function for the cr.ui framework.
    */
    decorate: function() {
      var self = this;
      // Listen to pref changes.
      Preferences.getInstance().addEventListener(this.pref,
          function(event) {
            var value = event.value && event.value['value'] != undefined ?
                event.value['value'] : event.value;
            self.managed = event.value && event.value['managed'] != undefined ?
                event.value['managed'] : false;
            self.disabled = self.managed;
            // Honor manually_disabled property, so options pages can
            // disable preferences manually when needed.
            if (self.manually_disabled) {
              self.disabled = true;
            }
            for (var i = 0; i < self.options.length; i++) {
              if (self.options[i].value == value) {
                self.selectedIndex = i;
                return;
              }
            }
            // Item not found, select first item.
            self.selectedIndex = 0;
          });

      // Listen to user events.
      this.addEventListener('change',
          function(e) {
            switch(self.dataType) {
              case 'number':
                Preferences.setIntegerPref(self.pref,
                    self.options[self.selectedIndex].value, self.metric);
                break;
              case 'boolean':
                Preferences.setBooleanValue(self.pref,
                    self.options[self.selectedIndex].value, self.metric);
                break;
              case 'string':
                Preferences.setStringPref(self.pref,
                    self.options[self.selectedIndex].value, self.metric);
                break;
            }
          });

      // Initialize options.
      this.ownerDocument.addEventListener('DOMContentLoaded',
          function() {
            var values = self.getAttribute('data-values');
            if (values) {
              self.initializeValues(templateData[values]);
            }
          });
    },

    /**
     * Sets up options in select element.
     * @param {Array} options List of option and their display text.
     * Each element in the array is an array of length 2 which contains options
     * value in the first element and display text in the second element.
     *
     * TODO(zelidrag): move this to that i18n template classes.
     */
    initializeValues: function(options) {
      options.forEach(function (values) {
        if (this.dataType == undefined)
          this.dataType = typeof values[0];
        this.appendChild(new Option(values[1], values[0]));
      }, this);
    }
  };

  /**
   * The preference name.
   * @type {string}
   */
  cr.defineProperty(PrefSelect, 'pref', cr.PropertyKind.ATTR);

  /**
   * The user metric string.
   * @type {string}
   */
  cr.defineProperty(PrefSelect, 'metric', cr.PropertyKind.ATTR);

  /////////////////////////////////////////////////////////////////////////////
  // PrefTextField class:

  // Define a constructor that uses an input element as its underlying element.
  var PrefTextField = cr.ui.define('input');

  PrefTextField.prototype = {
    // Set up the prototype chain
    __proto__: HTMLInputElement.prototype,

    /**
     * Initialization function for the cr.ui framework.
     */
    decorate: function() {
      var self = this;

      // Listen to pref changes.
      Preferences.getInstance().addEventListener(this.pref,
          function(event) {
            self.value = event.value && event.value['value'] != undefined ?
                event.value['value'] : event.value;
            self.managed = event.value && event.value['managed'] != undefined ?
                event.value['managed'] : false;
            self.disabled = self.managed;
          });

      // Listen to user events.
      this.addEventListener('change',
          function(e) {
            Preferences.setStringPref(self.pref, self.value, self.metric);
          });

      window.addEventListener('unload',
          function() {
            if (document.activeElement == self)
              self.blur();
          });
    }
  };

  /**
   * The preference name.
   * @type {string}
   */
  cr.defineProperty(PrefTextField, 'pref', cr.PropertyKind.ATTR);

  /**
   * The user metric string.
   * @type {string}
   */
  cr.defineProperty(PrefTextField, 'metric', cr.PropertyKind.ATTR);

  // Export
  return {
    PrefCheckbox: PrefCheckbox,
    PrefNumber: PrefNumber,
    PrefNumeric: PrefNumeric,
    PrefRadio: PrefRadio,
    PrefRange: PrefRange,
    PrefSelect: PrefSelect,
    PrefTextField: PrefTextField
  };

});
<commit_msg>DOMUI: Fix storing checkbox prefs.<commit_after>// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

cr.define('options', function() {

  var Preferences = options.Preferences;
  /////////////////////////////////////////////////////////////////////////////
  // PrefCheckbox class:

  // Define a constructor that uses an input element as its underlying element.
  var PrefCheckbox = cr.ui.define('input');

  PrefCheckbox.prototype = {
    // Set up the prototype chain
    __proto__: HTMLInputElement.prototype,

    /**
     * Initialization function for the cr.ui framework.
     */
    decorate: function() {
      this.type = 'checkbox';
      var self = this;

      self.initializeValueType(self.getAttribute('value-type'));

      // Listen to pref changes.
      Preferences.getInstance().addEventListener(this.pref,
          function(event) {
            var value = event.value && event.value['value'] != undefined ?
                event.value['value'] : event.value;
            // Invert pref value if inverted_pref == true.
            if (self.inverted_pref)
              self.checked = !Boolean(value);
            else
              self.checked = Boolean(value);
            self.managed = event.value && event.value['managed'] != undefined ?
                event.value['managed'] : false;
            self.disabled = self.managed;
            // Honor manually_disabled property, so options pages can
            // disable preferences manually when needed.
            if (self.manually_disabled) {
              self.disabled = true;
            }
          });

      // Listen to user events.
      this.addEventListener('click',
          function(e) {
            var value = self.inverted_pref ? !self.checked : self.checked;
            switch(self.valueType) {
              case 'number':
                Preferences.setIntegerPref(self.pref,
                    Number(value), self.metric);
                break;
              case 'boolean':
                Preferences.setBooleanPref(self.pref,
                    value, self.metric);
                break;
            }
          });
    },

    /**
     * Sets up options in checkbox element.
     * @param {String} valueType The preference type for this checkbox.
     */
    initializeValueType: function(valueType) {
      this.valueType = valueType || 'boolean';
    }
  };

  /**
   * The preference name.
   * @type {string}
   */
  cr.defineProperty(PrefCheckbox, 'pref', cr.PropertyKind.ATTR);

  /**
   * The user metric string.
   * @type {string}
   */
  cr.defineProperty(PrefCheckbox, 'metric', cr.PropertyKind.ATTR);

  /**
   * Whether to use inverted pref value.
   * @type {boolean}
   */
  cr.defineProperty(PrefCheckbox, 'inverted_pref', cr.PropertyKind.BOOL_ATTR);

  /////////////////////////////////////////////////////////////////////////////
  // PrefRadio class:

  //Define a constructor that uses an input element as its underlying element.
  var PrefRadio = cr.ui.define('input');

  PrefRadio.prototype = {
    // Set up the prototype chain
    __proto__: HTMLInputElement.prototype,

    /**
     * Initialization function for the cr.ui framework.
     */
    decorate: function() {
      this.type = 'radio';
      var self = this;

      // Listen to pref changes.
      Preferences.getInstance().addEventListener(this.pref,
          function(event) {
            var value = event.value && event.value['value'] != undefined ?
                event.value['value'] : event.value;
            self.managed = event.value && event.value['managed'] != undefined ?
                event.value['managed'] : false;
            self.checked = String(value) == self.value;
            self.disabled = self.managed;
            // Honor manually_disabled property, so options pages can
            // disable preferences manually when needed.
            if (self.manually_disabled) {
              self.disabled = true;
            }
          });

      // Listen to user events.
      this.addEventListener('change',
          function(e) {
            if(self.value == 'true' || self.value == 'false') {
              Preferences.setBooleanPref(self.pref,
                  self.value == 'true', self.metric);
            } else {
              Preferences.setIntegerPref(self.pref,
                  parseInt(self.value, 10), self.metric);
            }
          });
    },

    /**
     * Getter for preference name attribute.
     */
    get pref() {
      return this.getAttribute('pref');
    },

    /**
     * Setter for preference name attribute.
     */
    set pref(name) {
      this.setAttribute('pref', name);
    }
  };

  /**
   * The user metric string.
   * @type {string}
   */
  cr.defineProperty(PrefRadio, 'metric', cr.PropertyKind.ATTR);

  /////////////////////////////////////////////////////////////////////////////
  // PrefNumeric class:

  // Define a constructor that uses an input element as its underlying element.
  var PrefNumeric = function() {};
  PrefNumeric.prototype = {
    // Set up the prototype chain
    __proto__: HTMLInputElement.prototype,

    /**
     * Initialization function for the cr.ui framework.
     */
    decorate: function() {
      var self = this;

      // Listen to pref changes.
      Preferences.getInstance().addEventListener(this.pref,
          function(event) {
            self.value = event.value && event.value['value'] != undefined ?
                event.value['value'] : event.value;
            self.managed = event.value && event.value['managed'] != undefined ?
                event.value['managed'] : false;
            self.disabled = self.managed;
          });

      // Listen to user events.
      this.addEventListener('change',
          function(e) {
            if (this.validity.valid) {
              Preferences.setIntegerPref(self.pref, self.value, self.metric);
            }
          });
    }
  };

  /**
   * The preference name.
   * @type {string}
   */
  cr.defineProperty(PrefNumeric, 'pref', cr.PropertyKind.ATTR);

  /**
   * The user metric string.
   * @type {string}
   */
  cr.defineProperty(PrefNumeric, 'metric', cr.PropertyKind.ATTR);

  /////////////////////////////////////////////////////////////////////////////
  // PrefNumber class:

  // Define a constructor that uses an input element as its underlying element.
  var PrefNumber = cr.ui.define('input');

  PrefNumber.prototype = {
    // Set up the prototype chain
    __proto__: PrefNumeric.prototype,

    /**
     * Initialization function for the cr.ui framework.
     */
    decorate: function() {
      this.type = 'number';
      PrefNumeric.prototype.decorate.call(this);

      // Listen to user events.
      this.addEventListener('input',
          function(e) {
            if (this.validity.valid) {
              Preferences.setIntegerPref(self.pref, self.value, self.metric);
            }
          });
    }
  };

  /////////////////////////////////////////////////////////////////////////////
  // PrefRange class:

  // Define a constructor that uses an input element as its underlying element.
  var PrefRange = cr.ui.define('input');

  PrefRange.prototype = {
    // Set up the prototype chain
    __proto__: PrefNumeric.prototype,

    /**
     * Initialization function for the cr.ui framework.
     */
    decorate: function() {
      this.type = 'range';
      PrefNumeric.prototype.decorate.call(this);
      var self = this;

      // Additionally change the indicator as well.
      Preferences.getInstance().addEventListener(this.pref,
          function(event) {
            self.updateIndicator();
          });

      // Listen to user events.
      this.addEventListener('input',
          function(e) {
            this.updateIndicator();
          });
    },

    updateIndicator: function() {
      if ($(this.id + '-value')) {
        $(this.id + '-value').textContent = this.value;
      }
    }
  };

  /////////////////////////////////////////////////////////////////////////////
  // PrefSelect class:

  // Define a constructor that uses an select element as its underlying element.
  var PrefSelect = cr.ui.define('select');

  PrefSelect.prototype = {
    // Set up the prototype chain
    __proto__: HTMLSelectElement.prototype,

    /**
    * Initialization function for the cr.ui framework.
    */
    decorate: function() {
      var self = this;

      var values = self.getAttribute('data-values');
      if (values) {
        self.initializeValues(templateData[values]);
      }

      // Listen to pref changes.
      Preferences.getInstance().addEventListener(this.pref,
          function(event) {
            var value = event.value && event.value['value'] != undefined ?
                event.value['value'] : event.value;
            self.managed = event.value && event.value['managed'] != undefined ?
                event.value['managed'] : false;
            self.disabled = self.managed;
            // Honor manually_disabled property, so options pages can
            // disable preferences manually when needed.
            if (self.manually_disabled) {
              self.disabled = true;
            }
            for (var i = 0; i < self.options.length; i++) {
              if (self.options[i].value == value) {
                self.selectedIndex = i;
                return;
              }
            }
            // Item not found, select first item.
            self.selectedIndex = 0;
          });

      // Listen to user events.
      this.addEventListener('change',
          function(e) {
            switch(self.dataType) {
              case 'number':
                Preferences.setIntegerPref(self.pref,
                    self.options[self.selectedIndex].value, self.metric);
                break;
              case 'boolean':
                Preferences.setBooleanValue(self.pref,
                    self.options[self.selectedIndex].value, self.metric);
                break;
              case 'string':
                Preferences.setStringPref(self.pref,
                    self.options[self.selectedIndex].value, self.metric);
                break;
            }
          });
    },

    /**
     * Sets up options in select element.
     * @param {Array} options List of option and their display text.
     * Each element in the array is an array of length 2 which contains options
     * value in the first element and display text in the second element.
     *
     * TODO(zelidrag): move this to that i18n template classes.
     */
    initializeValues: function(options) {
      options.forEach(function (values) {
        if (this.dataType == undefined)
          this.dataType = typeof values[0];
        this.appendChild(new Option(values[1], values[0]));
      }, this);
    }
  };

  /**
   * The preference name.
   * @type {string}
   */
  cr.defineProperty(PrefSelect, 'pref', cr.PropertyKind.ATTR);

  /**
   * The user metric string.
   * @type {string}
   */
  cr.defineProperty(PrefSelect, 'metric', cr.PropertyKind.ATTR);

  /////////////////////////////////////////////////////////////////////////////
  // PrefTextField class:

  // Define a constructor that uses an input element as its underlying element.
  var PrefTextField = cr.ui.define('input');

  PrefTextField.prototype = {
    // Set up the prototype chain
    __proto__: HTMLInputElement.prototype,

    /**
     * Initialization function for the cr.ui framework.
     */
    decorate: function() {
      var self = this;

      // Listen to pref changes.
      Preferences.getInstance().addEventListener(this.pref,
          function(event) {
            self.value = event.value && event.value['value'] != undefined ?
                event.value['value'] : event.value;
            self.managed = event.value && event.value['managed'] != undefined ?
                event.value['managed'] : false;
            self.disabled = self.managed;
          });

      // Listen to user events.
      this.addEventListener('change',
          function(e) {
            Preferences.setStringPref(self.pref, self.value, self.metric);
          });

      window.addEventListener('unload',
          function() {
            if (document.activeElement == self)
              self.blur();
          });
    }
  };

  /**
   * The preference name.
   * @type {string}
   */
  cr.defineProperty(PrefTextField, 'pref', cr.PropertyKind.ATTR);

  /**
   * The user metric string.
   * @type {string}
   */
  cr.defineProperty(PrefTextField, 'metric', cr.PropertyKind.ATTR);

  // Export
  return {
    PrefCheckbox: PrefCheckbox,
    PrefNumber: PrefNumber,
    PrefNumeric: PrefNumeric,
    PrefRadio: PrefRadio,
    PrefRange: PrefRange,
    PrefSelect: PrefSelect,
    PrefTextField: PrefTextField
  };

});
<|endoftext|>


<commit_before>// Invoked by: API Gateway
// Returns: Error, or API Gateway proxy response object
//
// Handles GitHub webhook event payload requests. It does a bit of validation on
// the request, and then forwards the payload to an SNS topic, where other
// Lambdas will pick it up and process the event.

const crypto = require('crypto');
const AWS = require('aws-sdk');

const OK_RESPONSE = { statusCode: 200, headers: {}, body: null };

const sns = new AWS.SNS({ apiVersion: '2010-03-31' });

function publishEvent(event) {
    console.log('Publishing event to SNS');

    return sns.publish({
        TopicArn: process.env.GITHUB_EVENT_HANDLER_TOPIC_ARN,
        Message: event.isBase64Encoded
            ? Buffer.from(event.body, 'base64').toString('utf-8')
            : event.body,
        MessageAttributes: {
            githubEvent: {
                DataType: 'String',
                StringValue: event.headers['x-github-event'],
            },
            githubDeliveryId: {
                DataType: 'String',
                StringValue: event.headers['x-github-delivery'],
            },
        },
    }).promise();
}

exports.handler = async (event) => {
    const githubSignature = event.headers['x-hub-signature'].split('=')[1];

    console.log(`Checking event signature: ${githubSignature}`);

    const key = process.env.GITHUB_WEBHOOK_SECRET;
    const data = event.body;
    const check = crypto.createHmac('sha1', key).update(data).digest('hex');

    if (githubSignature !== check) {
        throw new Error('Invalid signature!');
    }

    console.log(`Handling event: ${event.headers['x-github-event']}`);

    switch (event.headers['x-github-event']) {
        case 'ping':
            // Blackhole `ping` events
            break;
        default:
            await publishEvent(event);
    }

    return OK_RESPONSE;
};
<commit_msg>Improve type checking in GitHub event endpoint handler<commit_after>/**
 * Invoked by: API Gateway
 * Returns: Error, or API Gateway proxy response object
 *
 * Handles GitHub webhook event payload requests. It does a bit of validation
 * on the request, and then forwards the payload to an SNS topic, where other
 * Lambdas will pick it up and process the event. This is done in two steps so
 * the GitHub request can return quickly.
 */

const crypto = require('crypto');
const AWS = require('aws-sdk');

const sns = new AWS.SNS({ apiVersion: '2010-03-31' });

/** @typedef { import('aws-lambda').APIGatewayProxyStructuredResultV2 } APIGatewayProxyStructuredResultV2 */
/** @typedef { import('aws-lambda').APIGatewayProxyEventV2 } APIGatewayProxyEventV2 */

/** @type {APIGatewayProxyStructuredResultV2} */
const OK_RESPONSE = { statusCode: 200 };

/**
 *
 * @param {APIGatewayProxyEventV2} event
 * @returns {Promise<AWS.SNS.PublishResponse, AWS.AWSError>}
 */
function publishEvent(event) {
    console.log('Publishing event to SNS');

    return sns.publish({
        TopicArn: process.env.GITHUB_EVENT_HANDLER_TOPIC_ARN,
        Message: event.isBase64Encoded
            ? Buffer.from(event.body, 'base64').toString('utf-8')
            : event.body,
        MessageAttributes: {
            githubEvent: {
                DataType: 'String',
                StringValue: event.headers['x-github-event'],
            },
            githubDeliveryId: {
                DataType: 'String',
                StringValue: event.headers['x-github-delivery'],
            },
        },
    }).promise();
}

/**
 *
 * @param {APIGatewayProxyEventV2} event Proxy integration payload
 * @returns {Promise<APIGatewayProxyStructuredResultV2>} Proxy integration response
 * @throws GitHub webhook validation error
 */
exports.handler = async (event) => {
    const githubSignature = event.headers['x-hub-signature'].split('=')[1];

    console.log(`Checking event signature: ${githubSignature}`);

    const key = process.env.GITHUB_WEBHOOK_SECRET;
    const data = event.body;
    const check = crypto.createHmac('sha1', key).update(data).digest('hex');

    if (githubSignature !== check) {
        throw new Error('Invalid signature!');
    }

    console.log(`Handling event: ${event.headers['x-github-event']}`);

    switch (event.headers['x-github-event']) {
        case 'ping':
            // Blackhole `ping` events
            break;
        default:
            await publishEvent(event);
    }

    return OK_RESPONSE;
};
<|endoftext|>


<commit_before>/* global define: false */
define([
  'angular',
  'jquery',
  'text!./processDiagram.html',
  'jquery-overscroll',
  'jquery-mousewheel'
], function(angular, $, template) {
  'use strict';
  /* jshint unused: false */
  var _unique = 0;
  function unique(prefix) {
    _unique++;
    return (prefix ? prefix +'_' : '') + _unique;
  }

  var DirectiveController = ['$scope', '$compile', 'Views', '$timeout',
                    function( $scope,   $compile,   Views,   $timeout) {

    $scope.overlayVars = { read: [ 'processData', 'bpmnElement' ] };
    $scope.overlayProviders = Views.getProviders({ component:  $scope.providerComponent });
    var overlay = '

';

    var bpmnElements,
        selection,
        scrollToBpmnElementId;

    $scope.$on('$destroy', function() {
      $scope.processDiagram = null;
      $scope.overlayProviders = null;
    });

    $scope.control = {};

    /**
     * If the process diagram changes, then the diagram will be rendered.
     */
    $scope.$watch('processDiagram', function(newValue) {
      if (newValue && newValue.$loaded !== false) {
        bpmnElements = newValue.bpmnElements;
        $scope.diagramData = newValue.bpmnDefinition;
      }
    });

    $scope.onLoad = function() {
      decorateDiagram($scope.processDiagram.bpmnElements);

      // update selection in case it has been provided earlier
      updateSelection(selection);

      // update scroll to in case it has been provided earlier
      scrollToBpmnElement(scrollToBpmnElementId);
    };

    $scope.onClick = function(element, $event) {
      if(bpmnElements[element.businessObject.id] && bpmnElements[element.businessObject.id].isSelectable) {
        $scope.onElementClick({id: element.businessObject.id, $event: $event});
      } else {
        $scope.onElementClick({id: null, $event: $event});
      }
    };

    $scope.onMouseEnter = function(element, $event) {
      if(bpmnElements[element.businessObject.id] && bpmnElements[element.businessObject.id].isSelectable) {
        $scope.control.getViewer().get('canvas').addMarker(element.businessObject.id, 'selectable');
        $scope.control.highlight(element.businessObject.id);
      }
    };

    $scope.onMouseLeave = function(element, $event) {
      if(bpmnElements[element.businessObject.id] && bpmnElements[element.businessObject.id].isSelectable && (!selection || selection.indexOf(element.businessObject.id) === -1)) {
        $scope.control.clearHighlight(element.businessObject.id);
      }
    };

    /*------------------- Decorate diagram ---------------------*/

    function decorateDiagram(bpmnElements) {
      angular.forEach(bpmnElements, function (bpmnElement) {
        decorateBpmnElement(bpmnElement);
      });
    }

    function decorateBpmnElement(bpmnElement) {

      var elem = $scope.control.getElement(bpmnElement.id);

      if(elem) {
        var childScope = $scope.$new();

        childScope.bpmnElement = bpmnElement;

        var newOverlay = angular.element(overlay);

        newOverlay.css({
          width: elem.width,
          height: elem.height
        });

        $compile(newOverlay)(childScope);

         try {
          $scope.control.createBadge(bpmnElement.id, {
            html: newOverlay,
            position: {
              top: 0,
              left: 0
            }
          });
        } catch (exception) {
          // console.log('exception while creating badge for '+bpmnElement.id+':', exception);
        }
      }
    }

    /*------------------- Handle selected activity id---------------------*/

    $scope.$watch('selection.activityIds', function(newValue, oldValue) {
      updateSelection(newValue);
    });

    function updateSelection(newSelection) {
      if ($scope.control.isLoaded()) {
        if (selection) {
          angular.forEach(selection, function(elementId) {
            if(bpmnElements[elementId] && bpmnElements[elementId].isSelectable) {
              $scope.control.clearHighlight(elementId);
            }
          });
        }

        if (newSelection) {
          angular.forEach(newSelection, function(elementId) {
            if(bpmnElements[elementId] && bpmnElements[elementId].isSelectable) {
              $scope.control.highlight(elementId);
            }
          });
        }
      }

      $scope.$root.$emit('instance-diagram-selection-change', newSelection);

      selection = newSelection;
    }

    /*------------------- Handle scroll to bpmn element ---------------------*/

    $scope.$watch('selection.scrollToBpmnElement', function(newValue) {
      if (newValue) {
        scrollToBpmnElement(newValue);
      }
    });

    function scrollToBpmnElement(bpmnElementId) {
      if ($scope.control.isLoaded() && bpmnElementId) {
        scrollTo(bpmnElementId);
      }
      scrollToBpmnElementId = bpmnElementId;
    }

    function scrollTo(elementId) {
      if(bpmnElements[elementId]) {
        $scope.control.scrollToElement(elementId);
      }
    }

  }];

  var Directive = function ($compile, Views) {
    return {
      restrict: 'EAC',
      scope: {
        processDiagram: '=',
        processDiagramOverlay: '=',
        onElementClick: '&',
        selection: '=',
        processData: '=',
        providerComponent: '@'
      },
      controller: DirectiveController,
      template: template
    };
  };

  Directive.$inject = [ '$compile', 'Views'];

  return Directive;
});
<commit_msg>fix(bpmn diagram): preserve selection after page refresh<commit_after>/* global define: false */
define([
  'angular',
  'jquery',
  'text!./processDiagram.html',
  'jquery-overscroll',
  'jquery-mousewheel'
], function(angular, $, template) {
  'use strict';
  /* jshint unused: false */
  var _unique = 0;
  function unique(prefix) {
    _unique++;
    return (prefix ? prefix +'_' : '') + _unique;
  }

  var DirectiveController = ['$scope', '$compile', 'Views', '$timeout',
                    function( $scope,   $compile,   Views,   $timeout) {

    $scope.overlayVars = { read: [ 'processData', 'bpmnElement' ] };
    $scope.overlayProviders = Views.getProviders({ component:  $scope.providerComponent });
    var overlay = '

';

    var bpmnElements,
        selection,
        scrollToBpmnElementId;

    $scope.$on('$destroy', function() {
      $scope.processDiagram = null;
      $scope.overlayProviders = null;
    });

    $scope.control = {};

    /**
     * If the process diagram changes, then the diagram will be rendered.
     */
    $scope.$watch('processDiagram', function(newValue) {
      if (newValue && newValue.$loaded !== false) {
        bpmnElements = newValue.bpmnElements;
        $scope.diagramData = newValue.bpmnDefinition;
      }
    });

    $scope.onLoad = function() {
      decorateDiagram($scope.processDiagram.bpmnElements);

      // update selection in case it has been provided earlier
      updateSelection(selection);

      // update scroll to in case it has been provided earlier
      scrollToBpmnElement(scrollToBpmnElementId);
    };

    $scope.onClick = function(element, $event) {
      if(bpmnElements[element.businessObject.id] && bpmnElements[element.businessObject.id].isSelectable) {
        $scope.onElementClick({id: element.businessObject.id, $event: $event});
      } else {
        $scope.onElementClick({id: null, $event: $event});
      }
    };

    $scope.onMouseEnter = function(element, $event) {
      if(bpmnElements[element.businessObject.id] && bpmnElements[element.businessObject.id].isSelectable) {
        $scope.control.getViewer().get('canvas').addMarker(element.businessObject.id, 'selectable');
        $scope.control.highlight(element.businessObject.id);
      }
    };

    $scope.onMouseLeave = function(element, $event) {
      if(bpmnElements[element.businessObject.id] && bpmnElements[element.businessObject.id].isSelectable && (!selection || selection.indexOf(element.businessObject.id) === -1)) {
        $scope.control.clearHighlight(element.businessObject.id);
      }
    };

    /*------------------- Decorate diagram ---------------------*/

    function decorateDiagram(bpmnElements) {
      angular.forEach(bpmnElements, function (bpmnElement) {
        decorateBpmnElement(bpmnElement);
      });
    }

    function decorateBpmnElement(bpmnElement) {

      var elem = $scope.control.getElement(bpmnElement.id);

      if(elem) {
        var childScope = $scope.$new();

        childScope.bpmnElement = bpmnElement;

        var newOverlay = angular.element(overlay);

        newOverlay.css({
          width: elem.width,
          height: elem.height
        });

        $compile(newOverlay)(childScope);

         try {
          $scope.control.createBadge(bpmnElement.id, {
            html: newOverlay,
            position: {
              top: 0,
              left: 0
            }
          });
        } catch (exception) {
          // console.log('exception while creating badge for '+bpmnElement.id+':', exception);
        }
      }
    }

    /*------------------- Handle selected activity id---------------------*/

    $scope.$watch('selection.activityIds', function(newValue, oldValue) {
      updateSelection(newValue);
    });

    function updateSelection(newSelection) {
      if ($scope.control.isLoaded()) {
        if (selection) {
          angular.forEach(selection, function(elementId) {
            if(bpmnElements[elementId]) {
              $scope.control.clearHighlight(elementId);
            }
          });
        }

        if (newSelection) {
          angular.forEach(newSelection, function(elementId) {
            if(bpmnElements[elementId]) {
              $scope.control.highlight(elementId);
            }
          });
        }
      }

      $scope.$root.$emit('instance-diagram-selection-change', newSelection);

      selection = newSelection;
    }

    /*------------------- Handle scroll to bpmn element ---------------------*/

    $scope.$watch('selection.scrollToBpmnElement', function(newValue) {
      if (newValue) {
        scrollToBpmnElement(newValue);
      }
    });

    function scrollToBpmnElement(bpmnElementId) {
      if ($scope.control.isLoaded() && bpmnElementId) {
        scrollTo(bpmnElementId);
      }
      scrollToBpmnElementId = bpmnElementId;
    }

    function scrollTo(elementId) {
      if(bpmnElements[elementId]) {
        $scope.control.scrollToElement(elementId);
      }
    }

  }];

  var Directive = function ($compile, Views) {
    return {
      restrict: 'EAC',
      scope: {
        processDiagram: '=',
        processDiagramOverlay: '=',
        onElementClick: '&',
        selection: '=',
        processData: '=',
        providerComponent: '@'
      },
      controller: DirectiveController,
      template: template
    };
  };

  Directive.$inject = [ '$compile', 'Views'];

  return Directive;
});
<|endoftext|>


<commit_before>import $ from 'jquery';
import { cleanForSlug } from '../../utils/text';
import { initCollapsiblePanels } from '../../includes/panels';

function InlinePanel(opts) {
  // lgtm[js/unused-local-variable]
  const self = {};

  // eslint-disable-next-line func-names
  self.initChildControls = function (prefix) {
    const childId = 'inline_child_' + prefix;
    const deleteInputId = 'id_' + prefix + '-DELETE';
    const currentChild = $('#' + childId);
    const $up = currentChild.find('[data-inline-panel-child-move-up]');
    const $down = currentChild.find('[data-inline-panel-child-move-down]');

    $('#' + deleteInputId + '-button').on('click', () => {
      /* set 'deleted' form field to true */
      $('#' + deleteInputId).val('1');
      currentChild.addClass('deleted').slideUp(() => {
        self.updateMoveButtonDisabledStates();
        self.updateAddButtonState();
      });
    });

    if (opts.canOrder) {
      $up.on('click', () => {
        const currentChildOrderElem = currentChild.children(
          'input[name$="-ORDER"]',
        );
        const currentChildOrder = currentChildOrderElem.val();

        /* find the previous visible 'inline_child' li before this one */
        const prevChild = currentChild.prevAll(':not(.deleted)').first();
        if (!prevChild.length) return;
        const prevChildOrderElem = prevChild.children('input[name$="-ORDER"]');
        const prevChildOrder = prevChildOrderElem.val();

        // async swap animation must run before the insertBefore line below, but doesn't need to finish first
        self.animateSwap(currentChild, prevChild);

        currentChild.insertBefore(prevChild);
        currentChildOrderElem.val(prevChildOrder);
        prevChildOrderElem.val(currentChildOrder);

        self.updateMoveButtonDisabledStates();
      });

      $down.on('click', () => {
        const currentChildOrderElem = currentChild.children(
          'input[name$="-ORDER"]',
        );
        const currentChildOrder = currentChildOrderElem.val();

        /* find the next visible 'inline_child' li after this one */
        const nextChild = currentChild.nextAll(':not(.deleted)').first();
        if (!nextChild.length) return;
        const nextChildOrderElem = nextChild.children('input[name$="-ORDER"]');
        const nextChildOrder = nextChildOrderElem.val();

        // async swap animation must run before the insertAfter line below, but doesn't need to finish first
        self.animateSwap(currentChild, nextChild);

        currentChild.insertAfter(nextChild);
        currentChildOrderElem.val(nextChildOrder);
        nextChildOrderElem.val(currentChildOrder);

        self.updateMoveButtonDisabledStates();
      });
    }

    /* Hide container on page load if it is marked as deleted. Remove the error
     message so that it doesn't count towards the number of errors on the tab at the
     top of the page. */
    if ($('#' + deleteInputId).val() === '1') {
      $('#' + childId)
        .addClass('deleted')
        .hide(0, () => {
          self.updateMoveButtonDisabledStates();
          self.updateAddButtonState();
        });

      $('#' + childId)
        .find('.error-message')
        .remove();
    }
  };

  self.formsElt = $('#' + opts.formsetPrefix + '-FORMS');

  self.updateMoveButtonDisabledStates = function updateMoveButtons() {
    if (opts.canOrder) {
      const forms = self.formsElt.children(':not(.deleted)');
      forms.each(function updateButtonStates(i) {
        const isFirst = i === 0;
        const isLast = i === forms.length - 1;
        $('[data-inline-panel-child-move-up]', this).prop('disabled', isFirst);
        $('[data-inline-panel-child-move-down]', this).prop('disabled', isLast);
      });
    }
  };

  // eslint-disable-next-line func-names
  self.updateAddButtonState = function () {
    if (opts.maxForms) {
      const forms = $('> [data-inline-panel-child]', self.formsElt).not(
        '.deleted',
      );
      const addButton = $('#' + opts.formsetPrefix + '-ADD');

      if (forms.length >= opts.maxForms) {
        addButton.prop('disabled', true);
      } else {
        addButton.prop('disabled', false);
      }
    }
  };

  // eslint-disable-next-line func-names
  self.animateSwap = function (item1, item2) {
    const parent = self.formsElt;
    const children = parent.children(':not(.deleted)');

    // Position children absolutely and add hard-coded height
    // to prevent scroll jumps when reordering.
    parent.css({
      position: 'relative',
      height: parent.height(),
    });

    children
      .each(function moveChildTop() {
        $(this).css('top', $(this).position().top);
      })
      .css({
        // Set this after the actual position so the items animate correctly.
        position: 'absolute',
        width: '100%',
      });

    // animate swapping around
    item1.animate(
      {
        top: item2.position().top,
      },
      200,
      () => {
        parent.removeAttr('style');
        children.removeAttr('style');
      },
    );

    item2.animate(
      {
        top: item1.position().top,
      },
      200,
      () => {
        parent.removeAttr('style');
        children.removeAttr('style');
      },
    );
  };

  // eslint-disable-next-line no-undef
  buildExpandingFormset(opts.formsetPrefix, {
    onAdd(formCount) {
      const newChildPrefix = opts.emptyChildFormPrefix.replace(
        /__prefix__/g,
        formCount,
      );
      self.initChildControls(newChildPrefix);
      if (opts.canOrder) {
        /* NB form hidden inputs use 0-based index and only increment formCount *after* this function is run.
        Therefore formcount and order are currently equal and order must be incremented
        to ensure it's *greater* than previous item */
        $('#id_' + newChildPrefix + '-ORDER').val(formCount + 1);
      }

      self.updateMoveButtonDisabledStates();
      self.updateAddButtonState();
      initCollapsiblePanels(
        document.querySelectorAll(
          `#inline_child_${newChildPrefix} [data-panel-toggle]`,
        ),
      );

      if (opts.onAdd) opts.onAdd();
    },
  });

  return self;
}

window.InlinePanel = InlinePanel;

window.cleanForSlug = cleanForSlug;

function initSlugAutoPopulate() {
  let slugFollowsTitle = false;

  // eslint-disable-next-line func-names
  $('#id_title').on('focus', function () {
    /* slug should only follow the title field if its value matched the title's value at the time of focus */
    const currentSlug = $('#id_slug').val();
    const slugifiedTitle = cleanForSlug(this.value, true);
    slugFollowsTitle = currentSlug === slugifiedTitle;
  });

  // eslint-disable-next-line func-names
  $('#id_title').on('keyup keydown keypress blur', function () {
    if (slugFollowsTitle) {
      const slugifiedTitle = cleanForSlug(this.value, true);
      $('#id_slug').val(slugifiedTitle);
    }
  });
}

window.initSlugAutoPopulate = initSlugAutoPopulate;

function initSlugCleaning() {
  // eslint-disable-next-line func-names
  $('#id_slug').on('blur', function () {
    // if a user has just set the slug themselves, don't remove stop words etc, just illegal characters
    $(this).val(cleanForSlug($(this).val(), false));
  });
}

window.initSlugCleaning = initSlugCleaning;

function initErrorDetection() {
  const errorSections = {};

  // first count up all the errors
  // eslint-disable-next-line func-names
  $('.error-message,.help-critical').each(function () {
    const parentSection = $(this).closest('section[role="tabpanel"]');

    if (!errorSections[parentSection.attr('id')]) {
      errorSections[parentSection.attr('id')] = 0;
    }

    errorSections[parentSection.attr('id')] =
      errorSections[parentSection.attr('id')] + 1;
  });

  // now identify them on each tab
  // eslint-disable-next-line guard-for-in
  for (const index in errorSections) {
    $('[data-tabs] a[href="#' + index + '"]')
      .find('[data-tabs-errors]')
      .addClass('!w-flex')
      .find('[data-tabs-errors-count]')
      .text(errorSections[index]);
  }
}

window.initErrorDetection = initErrorDetection;

function initKeyboardShortcuts() {
  // eslint-disable-next-line no-undef
  Mousetrap.bind(['mod+p'], () => {
    const previewToggle = document.querySelector(
      '[data-side-panel-toggle="preview"]',
    );
    if (previewToggle) previewToggle.click();
    return false;
  });

  // eslint-disable-next-line no-undef
  Mousetrap.bind(['mod+s'], () => {
    $('.action-save').trigger('click');
    return false;
  });
}

window.initKeyboardShortcuts = initKeyboardShortcuts;

$(() => {
  /* Only non-live pages should auto-populate the slug from the title */
  if (!$('body').hasClass('page-is-live')) {
    initSlugAutoPopulate();
  }

  initSlugCleaning();
  initErrorDetection();
  initKeyboardShortcuts();
});

let updateFooterTextTimeout = -1;
window.updateFooterSaveWarning = (formDirty, commentsDirty) => {
  const warningContainer = $('[data-unsaved-warning]');
  const warnings = warningContainer.find('[data-unsaved-type]');
  const anyDirty = formDirty || commentsDirty;
  const typeVisibility = {
    all: formDirty && commentsDirty,
    any: anyDirty,
    comments: commentsDirty && !formDirty,
    edits: formDirty && !commentsDirty,
  };

  let hiding = false;
  if (anyDirty) {
    warningContainer.removeClass('footer__container--hidden');
  } else {
    if (!warningContainer.hasClass('footer__container--hidden')) {
      hiding = true;
    }
    warningContainer.addClass('footer__container--hidden');
  }
  clearTimeout(updateFooterTextTimeout);
  const updateWarnings = () => {
    for (const warning of warnings) {
      const visible = typeVisibility[warning.dataset.unsavedType];
      warning.hidden = !visible;
    }
  };
  if (hiding) {
    // If hiding, we want to keep the text as-is before it disappears
    updateFooterTextTimeout = setTimeout(updateWarnings, 1050);
  } else {
    updateWarnings();
  }
};
<commit_msg>fix issue inline panel ordering<commit_after>import $ from 'jquery';
import { cleanForSlug } from '../../utils/text';
import { initCollapsiblePanels } from '../../includes/panels';

function InlinePanel(opts) {
  // lgtm[js/unused-local-variable]
  const self = {};

  // eslint-disable-next-line func-names
  self.initChildControls = function (prefix) {
    const childId = 'inline_child_' + prefix;
    const deleteInputId = 'id_' + prefix + '-DELETE';
    const currentChild = $('#' + childId);
    const $up = currentChild.find('[data-inline-panel-child-move-up]');
    const $down = currentChild.find('[data-inline-panel-child-move-down]');

    $('#' + deleteInputId + '-button').on('click', () => {
      /* set 'deleted' form field to true */
      $('#' + deleteInputId).val('1');
      currentChild.addClass('deleted').slideUp(() => {
        self.updateMoveButtonDisabledStates();
        self.updateAddButtonState();
      });
    });

    if (opts.canOrder) {
      $up.on('click', () => {
        const currentChildOrderElem = currentChild.find(
          `input[name="${prefix}-ORDER"]`,
        );
        const currentChildOrder = currentChildOrderElem.val();

        /* find the previous visible 'inline_child' li before this one */
        const prevChild = currentChild.prevAll(':not(.deleted)').first();
        if (!prevChild.length) return;
        const prevChildPrefix = prevChild[0].id.replace('inline_child_', '');
        const prevChildOrderElem = prevChild.find(
          `input[name="${prevChildPrefix}-ORDER"]`,
        );
        const prevChildOrder = prevChildOrderElem.val();

        // async swap animation must run before the insertBefore line below, but doesn't need to finish first
        self.animateSwap(currentChild, prevChild);

        currentChild.insertBefore(prevChild);
        currentChildOrderElem.val(prevChildOrder);
        prevChildOrderElem.val(currentChildOrder);

        self.updateMoveButtonDisabledStates();
      });

      $down.on('click', () => {
        const currentChildOrderElem = currentChild.find(
          `input[name="${prefix}-ORDER"]`,
        );
        const currentChildOrder = currentChildOrderElem.val();

        /* find the next visible 'inline_child' li after this one */
        const nextChild = currentChild.nextAll(':not(.deleted)').first();
        if (!nextChild.length) return;
        const nextChildPrefix = nextChild[0].id.replace('inline_child_', '');
        const nextChildOrderElem = nextChild.find(
          `input[name="${nextChildPrefix}-ORDER"]`,
        );
        const nextChildOrder = nextChildOrderElem.val();

        // async swap animation must run before the insertAfter line below, but doesn't need to finish first
        self.animateSwap(currentChild, nextChild);

        currentChild.insertAfter(nextChild);
        currentChildOrderElem.val(nextChildOrder);
        nextChildOrderElem.val(currentChildOrder);

        self.updateMoveButtonDisabledStates();
      });
    }

    /* Hide container on page load if it is marked as deleted. Remove the error
     message so that it doesn't count towards the number of errors on the tab at the
     top of the page. */
    if ($('#' + deleteInputId).val() === '1') {
      $('#' + childId)
        .addClass('deleted')
        .hide(0, () => {
          self.updateMoveButtonDisabledStates();
          self.updateAddButtonState();
        });

      $('#' + childId)
        .find('.error-message')
        .remove();
    }
  };

  self.formsElt = $('#' + opts.formsetPrefix + '-FORMS');

  self.updateMoveButtonDisabledStates = function updateMoveButtons() {
    if (opts.canOrder) {
      const forms = self.formsElt.children(':not(.deleted)');
      forms.each(function updateButtonStates(i) {
        const isFirst = i === 0;
        const isLast = i === forms.length - 1;
        $('[data-inline-panel-child-move-up]', this).prop('disabled', isFirst);
        $('[data-inline-panel-child-move-down]', this).prop('disabled', isLast);
      });
    }
  };

  // eslint-disable-next-line func-names
  self.updateAddButtonState = function () {
    if (opts.maxForms) {
      const forms = $('> [data-inline-panel-child]', self.formsElt).not(
        '.deleted',
      );
      const addButton = $('#' + opts.formsetPrefix + '-ADD');

      if (forms.length >= opts.maxForms) {
        addButton.prop('disabled', true);
      } else {
        addButton.prop('disabled', false);
      }
    }
  };

  // eslint-disable-next-line func-names
  self.animateSwap = function (item1, item2) {
    const parent = self.formsElt;
    const children = parent.children(':not(.deleted)');

    // Position children absolutely and add hard-coded height
    // to prevent scroll jumps when reordering.
    parent.css({
      position: 'relative',
      height: parent.height(),
    });

    children
      .each(function moveChildTop() {
        $(this).css('top', $(this).position().top);
      })
      .css({
        // Set this after the actual position so the items animate correctly.
        position: 'absolute',
        width: '100%',
      });

    // animate swapping around
    item1.animate(
      {
        top: item2.position().top,
      },
      200,
      () => {
        parent.removeAttr('style');
        children.removeAttr('style');
      },
    );

    item2.animate(
      {
        top: item1.position().top,
      },
      200,
      () => {
        parent.removeAttr('style');
        children.removeAttr('style');
      },
    );
  };

  // eslint-disable-next-line no-undef
  buildExpandingFormset(opts.formsetPrefix, {
    onAdd(formCount) {
      const newChildPrefix = opts.emptyChildFormPrefix.replace(
        /__prefix__/g,
        formCount,
      );
      self.initChildControls(newChildPrefix);
      if (opts.canOrder) {
        /* NB form hidden inputs use 0-based index and only increment formCount *after* this function is run.
        Therefore formcount and order are currently equal and order must be incremented
        to ensure it's *greater* than previous item */
        $('#id_' + newChildPrefix + '-ORDER').val(formCount + 1);
      }

      self.updateMoveButtonDisabledStates();
      self.updateAddButtonState();
      initCollapsiblePanels(
        document.querySelectorAll(
          `#inline_child_${newChildPrefix} [data-panel-toggle]`,
        ),
      );

      if (opts.onAdd) opts.onAdd();
    },
  });

  return self;
}

window.InlinePanel = InlinePanel;

window.cleanForSlug = cleanForSlug;

function initSlugAutoPopulate() {
  let slugFollowsTitle = false;

  // eslint-disable-next-line func-names
  $('#id_title').on('focus', function () {
    /* slug should only follow the title field if its value matched the title's value at the time of focus */
    const currentSlug = $('#id_slug').val();
    const slugifiedTitle = cleanForSlug(this.value, true);
    slugFollowsTitle = currentSlug === slugifiedTitle;
  });

  // eslint-disable-next-line func-names
  $('#id_title').on('keyup keydown keypress blur', function () {
    if (slugFollowsTitle) {
      const slugifiedTitle = cleanForSlug(this.value, true);
      $('#id_slug').val(slugifiedTitle);
    }
  });
}

window.initSlugAutoPopulate = initSlugAutoPopulate;

function initSlugCleaning() {
  // eslint-disable-next-line func-names
  $('#id_slug').on('blur', function () {
    // if a user has just set the slug themselves, don't remove stop words etc, just illegal characters
    $(this).val(cleanForSlug($(this).val(), false));
  });
}

window.initSlugCleaning = initSlugCleaning;

function initErrorDetection() {
  const errorSections = {};

  // first count up all the errors
  // eslint-disable-next-line func-names
  $('.error-message,.help-critical').each(function () {
    const parentSection = $(this).closest('section[role="tabpanel"]');

    if (!errorSections[parentSection.attr('id')]) {
      errorSections[parentSection.attr('id')] = 0;
    }

    errorSections[parentSection.attr('id')] =
      errorSections[parentSection.attr('id')] + 1;
  });

  // now identify them on each tab
  // eslint-disable-next-line guard-for-in
  for (const index in errorSections) {
    $('[data-tabs] a[href="#' + index + '"]')
      .find('[data-tabs-errors]')
      .addClass('!w-flex')
      .find('[data-tabs-errors-count]')
      .text(errorSections[index]);
  }
}

window.initErrorDetection = initErrorDetection;

function initKeyboardShortcuts() {
  // eslint-disable-next-line no-undef
  Mousetrap.bind(['mod+p'], () => {
    const previewToggle = document.querySelector(
      '[data-side-panel-toggle="preview"]',
    );
    if (previewToggle) previewToggle.click();
    return false;
  });

  // eslint-disable-next-line no-undef
  Mousetrap.bind(['mod+s'], () => {
    $('.action-save').trigger('click');
    return false;
  });
}

window.initKeyboardShortcuts = initKeyboardShortcuts;

$(() => {
  /* Only non-live pages should auto-populate the slug from the title */
  if (!$('body').hasClass('page-is-live')) {
    initSlugAutoPopulate();
  }

  initSlugCleaning();
  initErrorDetection();
  initKeyboardShortcuts();
});

let updateFooterTextTimeout = -1;
window.updateFooterSaveWarning = (formDirty, commentsDirty) => {
  const warningContainer = $('[data-unsaved-warning]');
  const warnings = warningContainer.find('[data-unsaved-type]');
  const anyDirty = formDirty || commentsDirty;
  const typeVisibility = {
    all: formDirty && commentsDirty,
    any: anyDirty,
    comments: commentsDirty && !formDirty,
    edits: formDirty && !commentsDirty,
  };

  let hiding = false;
  if (anyDirty) {
    warningContainer.removeClass('footer__container--hidden');
  } else {
    if (!warningContainer.hasClass('footer__container--hidden')) {
      hiding = true;
    }
    warningContainer.addClass('footer__container--hidden');
  }
  clearTimeout(updateFooterTextTimeout);
  const updateWarnings = () => {
    for (const warning of warnings) {
      const visible = typeVisibility[warning.dataset.unsavedType];
      warning.hidden = !visible;
    }
  };
  if (hiding) {
    // If hiding, we want to keep the text as-is before it disappears
    updateFooterTextTimeout = setTimeout(updateWarnings, 1050);
  } else {
    updateWarnings();
  }
};
<|endoftext|>


<commit_before>"use strict";

Template.course_edit.helpers({
	query: function() {
		return Session.get('search')
	},

	available_categories: function(parent) {
		if (parent) {
			return categories[parent];
		} else {
			return Object.keys(categories);
		}
	},

	available_roles: function() {
		return Roles.find({'preset': { $ne: true }})
	},

	roleDescription: function() {
		return 'roles.'+this.type+'.description';
	},

	// Emit 'checked' string if id shows up as member or property of cats
	checked: function(cat, cats) {
		if (!cats) return;
		if (cats.length) {
			return cats.indexOf(cat) >= 0 ? 'checked' : ''
		}
	},

	show_subcats: function(id, cats) {
		if (!cats) return 'none';
		if (cats.length) {
			return cats.indexOf(id) >= 0 ? 'block' : 'none'
		}
	},

	regions: function(){
	  return Regions.find();
	},

	currentRegion: function(region) {
		var currentRegion = Session.get('region')
		return currentRegion && region._id == currentRegion;
	}
});

Template.course_edit.rendered = function() {
	var desc = this.find('#editform_description');
	if (desc) new MediumEditor(desc);
}

Template.course_edit.events({
	'submit form.course_edit, click button.save': function (ev) {
		ev.preventDefault()
		try {
			if (pleaseLogin()) return;

			var courseId = this._id ? this._id : ''
			var isNew = courseId === ''

			var roles = {}
			$('input.roleselection').each(function(_, rolecheck) {
				roles[rolecheck.name] = rolecheck.checked;
			})

			var changes = {
				description: $('#editform_description').html(),
				categories: $('#editform_categories input:checked').map(function(){ return this.name}).get(),
				name: $('#editform_name').val(),
				roles: roles
			}

			changes.name = saneText(changes.name);

			if (changes.name.length == 0) {
				alert("Please provide a title")
				return;
			}

			if (isNew) {
				changes.region = $('.region_select').val();
				if (!changes.region) {
					alert("Please select a region")
					return;
				}
			}

			Meteor.call("save_course", courseId, changes, function(err, courseId) {
				if (err) {
					addMessage(mf('course.saving.error', { ERROR: err }, 'Saving the course went wrong! Sorry about this. We encountered the following error: {ERROR}'), 'danger');
				} else {
					Router.go('/course/'+courseId); // Router.go('showCourse', courseId) fails for an unknown reason
					addMessage(mf('course.saving.success', { NAME: changes.name }, 'Saved changes to course "{NAME}".'), 'success');
				}
			})

		} catch(err) {
			if (err instanceof String) alert(err)
			else throw err
		}
		return false;
	},

	'click button.cancel': function() {
		if (this._id) {
			Router.go('showCourse', this);
		}
		Router.go('/');
	},

	'click #show_categories_to_edit': function(event){
		$('#show_categories_to_edit').toggle(1000);
		$('#edit_categories').toggle(1000);
	},

	'change .categories input.checkbox': function(){
		$('#cat_' + this +" .subcategories").toggle();
		$('span.caret' + "." + this).toggleClass('active');
		$('label' + "." + this).toggleClass('active');

		var is_checked = $('#cat_' + this +" .checkbox").first().prop('checked');
		if(!is_checked) {
			$('#cat_' + this +" .checkbox_sub").prop('checked', false);
		}
	},

	'change .categories input.checkbox_sub': function() {
		$('label' + "." + this).toggleClass('active');
	},

	'change .roles_list input.roleselection': function() {
		$('label' + "." + this.type).toggleClass('active');
	}
});

<commit_msg>housekeeping<commit_after>"use strict";

Template.course_edit.helpers({
	query: function() {
		return Session.get('search')
	},

	available_categories: function(parent) {
		if (parent) {
			return categories[parent];
		} else {
			return Object.keys(categories);
		}
	},

	available_roles: function() {
		return Roles.find({'preset': { $ne: true }})
	},

	roleDescription: function() {
		return 'roles.'+this.type+'.description';
	},

	// Emit 'checked' string if id shows up as member or property of cats
	checked: function(cat, cats) {
		if (!cats) return;
		if (cats.length) {
			return cats.indexOf(cat) >= 0 ? 'checked' : ''
		}
	},

	show_subcats: function(id, cats) {
		if (!cats) return 'none';
		if (cats.length) {
			return cats.indexOf(id) >= 0 ? 'block' : 'none'
		}
	},

	regions: function() {
	  return Regions.find();
	},

	currentRegion: function(region) {
		var currentRegion = Session.get('region')
		return currentRegion && region._id == currentRegion;
	}
});

Template.course_edit.rendered = function() {
	var desc = this.find('#editform_description');
	if (desc) new MediumEditor(desc);
}

Template.course_edit.events({
	'submit form.course_edit, click button.save': function (ev) {
		ev.preventDefault()
		try {
			if (pleaseLogin()) return;

			var courseId = this._id ? this._id : ''
			var isNew = courseId === ''

			var roles = {}
			$('input.roleselection').each(function(_, rolecheck) {
				roles[rolecheck.name] = rolecheck.checked;
			})

			var changes = {
				description: $('#editform_description').html(),
				categories: $('#editform_categories input:checked').map(function(){ return this.name}).get(),
				name: $('#editform_name').val(),
				roles: roles
			}

			changes.name = saneText(changes.name);

			if (changes.name.length == 0) {
				alert("Please provide a title")
				return;
			}

			if (isNew) {
				changes.region = $('.region_select').val();
				if (!changes.region) {
					alert("Please select a region")
					return;
				}
			}

			Meteor.call("save_course", courseId, changes, function(err, courseId) {
				if (err) {
					addMessage(mf('course.saving.error', { ERROR: err }, 'Saving the course went wrong! Sorry about this. We encountered the following error: {ERROR}'), 'danger');
				} else {
					Router.go('/course/'+courseId); // Router.go('showCourse', courseId) fails for an unknown reason
					addMessage(mf('course.saving.success', { NAME: changes.name }, 'Saved changes to course "{NAME}".'), 'success');
				}
			})

		} catch(err) {
			if (err instanceof String) alert(err)
			else throw err
		}
		return false;
	},

	'click button.cancel': function() {
		if (this._id) {
			Router.go('showCourse', this);
		}
		Router.go('/');
	},

	'click #show_categories_to_edit': function(event){
		$('#show_categories_to_edit').toggle(1000);
		$('#edit_categories').toggle(1000);
	},

	'change .categories input.checkbox': function(){
		$('#cat_' + this +" .subcategories").toggle();
		$('span.caret' + "." + this).toggleClass('active');
		$('label' + "." + this).toggleClass('active');

		var is_checked = $('#cat_' + this +" .checkbox").first().prop('checked');
		if(!is_checked) {
			$('#cat_' + this +" .checkbox_sub").prop('checked', false);
		}
	},

	'change .categories input.checkbox_sub': function() {
		$('label' + "." + this).toggleClass('active');
	},

	'change .roles_list input.roleselection': function() {
		$('label' + "." + this.type).toggleClass('active');
	}
});

<|endoftext|>