<commit_before>var modifiers = [
  "required", "optional", "repeated", "reserved", "default", "extensions", "packed"
]
var types = [
  "bool", "bytes", "double", "float", "string",
  "int32", "int64", "uint32", "uint64", "sint32", "sint64", "fixed32", "fixed64", "sfixed32", "sfixed64"
];

class Rule {
  constructor(name, code, isToken = false) {
    this.name = name
    this.code = code
    this.isToken = isToken
  }

  get cached() {
    return this.isToken
  }
}

const F = {F: true}, C = {C: true}

const space = " \t\r\n"

function succeed(_, v) {
  return v
}

function succeed_drop(m, v) {
  m.backtrackStack.pop()
  return v
}

const Program = new Rule("Program", Program_0)

function Program_0(m) {
  while (m.group(space) != F) {}
  return Program_1(m)
}

function Program_1(m) {
  m.setBacktrack(Program_3, Program_1)
  return m.call(Statement, Program_2)
}

function Program_2(m) {
  m.backtrackStack.pop()
  return Program_1(m)
}

function Program_3(m) {
  return m.eof()
}

const Statement = new Rule("Statement", Statement_0)

function Statement_0(m) {
  m.setBacktrack(Statement_message)
  return m.callWith(kw, "package", Statement_package_1)
}

function Statement_package_1(m) {
  return m.call(identifier, Statement_package_2)
}

function Statement_package_2(m) {
  return m.callWith(p, ";", succeed_drop)
}

function Statement_message(m) {
  return m.callWith(kw, "message", Statement_message_1)
}

function Statement_message_1(m) {
  return m.call(identifier, Statement_message_2)
}

function Statement_message_2(m) {
  return m.callWith(p, "{", Statement_message_3)
}

function Statement_message_3(m) {
  m.setBacktrack(Statement_message_5, Statement_message_3)
  return m.call(Field, Statement_message_4)
}

function Statement_message_4(m) {
  m.backtrackStack.pop()
  return Statement_message_3(m)
}

function Statement_message_5(m) {
  return m.callWith(p, "}", succeed)
}

const Field = new Rule("Field", Field_0)

function Field_0(m) {
  m.setBacktrack(Field_2, Field_0)
  return m.call(modifier, Field_1)
}

function Field_1(m) {
  m.backtrackStack.pop()
  return Field_0(m)
}

function Field_2(m) {
  return m.call(type, Field_3)
}

function Field_3(m) {
  return m.call(identifier, Field_4)
}

function Field_4(m) {
  return m.callWith(p, "=", Field_5)
}

function Field_5(m) {
  return m.call(number, Field_6)
}

function Field_6(m) {
  return m.callWith(p, ";", succeed)
}

const identifier = new Rule("identifier", identifier_0, true)

function identifier_0(m) {
  let r = m.re(/^[a-z]\w*/i)
  if (r == F) return F
  let value = r[0]
  while (m.group(space) != F) {}
  return value
}

const kw = new Rule("kw", kw_0, true)

function kw_0(m) {
  return m.call(identifier, kw_1)
}

function kw_1(m, ident) {
  return m.arg == ident ? null : F
}

const type = new Rule("type", type_0, true)

function type_0(m) {
  return m.call(identifier, type_1)
}

function type_1(_, ident) {
  return types.indexOf(ident) > -1 ? null : F
}

const modifier = new Rule("modifier", modifier_0, true)

function modifier_0() {
  return m.call(identifier, modifier_1)
}

function modifier_1(_, ident) {
  return modifiers.indexOf(ident) > -1 ? null : F
}

const p = new Rule("p", p_0, true)

function p_0(m) {
  if (m.str(m.arg) == F) return F
  while (m.group(space) != F) {}
}

const number = new Rule("number", number_0, true)

function number_0(m) {
  if (m.re(/^\d+/) == F) return F
  while (m.group(space) != F) {}
}

const anyToken = new Rule("anyToken", anyToken_0)

function anyToken_0(m) {
  m.setBacktrack(anyToken_1)
  return m.call(number, succeed_drop)
}

function anyToken_1(m) {
  m.setBacktrack(anyToken_2)
  return m.call(identifier, succeed_drop)
}

function anyToken_2(m) {
  m.any()
  while (m.group(space) != F) {}
}

function anyTokens(m) {
  if (m.eof() == F) return m.call(anyToken, anyTokens)
}

class Match {
  constructor(rule, arg, start, parent) {
    this.rule = rule
    this.arg = arg
    this.start = start
    this.parent = parent
    this.end = -1
    this.value = null
  }
}

class Frame {
  constructor(match, arg, next) {
    this.match = match
    this.arg = arg
    this.next = next
  }
}

class BacktrackFrame {
  constructor(pos, isLookahead, frameDepth, next, recover) {
    this.pos = pos
    this.isLookahead = isLookahead
    this.frameDepth = frameDepth
    this.next = next
    this.recover = recover
  }
}

const MIN_SAVE_DISTANCE = 15, MAX_BACKTRACK_DISTANCE = 100

class ModeMatcher {
  constructor(input) {
    this.pos = 0
    this.input = input
    // :: [Frame]
    this.stack = []
    // :: [BacktrackFrame]
    this.backtrackStack = []
    this.callee = null
    this.cache = []
    this.frontierStack = null
    this.frontierBacktrack = null
    this.frontierPos = -1
    this.skipping = false
  }

  get currentMatch() {
    for (let i = this.stack.length - 1; i >= 0; i--) {
      let m = this.stack[i].match
      if (m) return m
    }
  }

  get arg() {
    return this.stack[this.stack.length - 1].arg
  }

  re(re) {
    let match = re.exec(this.input.slice(this.pos))
    if (match) this.pos += match[0].length
    return match || F
  }

  str(string) {
    if (this.input.slice(this.pos, this.pos + string.length) == string)
      this.pos += string.length
    else
      return F
  }

  eof() {
    return this.pos == this.input.length ? null : F
  }

  any() {
    if (this.pos == this.input.length) return F
    this.pos++
  }

  setBacktrack(next, recover, lookahead) {
    this.backtrackStack.push(new BacktrackFrame(this.pos, lookahead, this.stack.length, next, recover))
  }

  group(str) {
    if (this.pos < this.input.length && str.indexOf(this.input[this.pos]) > -1)
      this.pos++
    else
      return F
  }

  getMatch(rule, arg) {
    let cached = this.cache[this.pos]
    if (cached) for (let i = 0; i < cached.length; i++)
      if (cached[i].rule === rule && cached[i].arg === arg) return cached[i]
  }

  call(rule, next) {
    return this.callWith(rule, F, next)
  }

  callWith(rule, arg, next) {
    let match
    if (rule.cached) {
      let argVal = arg == F ? null : arg
      let cached = this.getMatch(rule, argVal)
      if (cached) {
        cached.parent = this.currentMatch
        this.pos = cached.end
        return next(this, cached.value)
      }
      match = new Match(rule, argVal, this.pos, this.currentMatch)
    }
    this.stack.push(new Frame(match, arg, next))
    this.callee = rule
    return C
  }

  exec(startRule, upto) {
    let result = C
    this.callee = startRule
    for (;;) {
      if (result === C) {
        result = this.callee.code(this)
      } else if (result === F) {
        let backtrack
        do {
          backtrack = this.backtrackStack.pop()
        } while (backtrack && backtrack.pos < this.pos - MAX_BACKTRACK_DISTANCE)
        if (backtrack) {
          if (!this.skipping &&
              !backtrack.isLookahead && this.pos > this.frontierPos &&
              this.pos > backtrack.pos + MIN_SAVE_DISTANCE) {
            this.frontierStack = this.stack.slice()
            this.frontierBacktrack = this.backtrackStack.concat(backtrack)
            this.frontierPos = this.pos
          }
          this.pos = backtrack.pos
          this.stack.length = backtrack.frameDepth
          result = backtrack.next(this)
        } else if (!this.frontierStack) {
          result = anyTokens(this)
        } else {
          if (this.pos >= upto) return
          this.stack = this.frontierStack.slice()
          this.pos = this.frontierPos
          this.skipping = true
          result = this.call(anyToken, m => {
            m.skipping = false
            m.backtrackStack = m.frontierBacktrack.slice()
            m.frontierPos = this.pos
            for (let i = 0; i < m.backtrackStack.length; i++) {
              let bt = m.backtrackStack[i]
              bt.pos = m.pos
              if (bt.recover) {
                m.backtrackStack.splice(++i, 0, new BacktrackFrame(m.pos, false, bt.frameDepth, bt.recover))
                bt.recover = null
              }
            }
            return F
          })
        }
      } else if (this.stack.length == 0) {
        if (this.pos >= upto) return
        this.callee = startRule
        result = C
      } else {
        if (this.pos > upto) return
        let frame = this.stack.pop(), match = frame.match
        if (match) {
          match.end = this.pos
          match.value = result
          ;(this.cache[match.start] || (this.cache[match.start] = [])).push(match)
        }
        result = frame.next(this, result)
      }
    }
  }
}

let m = new ModeMatcher("package foo;\n\nmessage Address {\n  required string foo = 1-\n  optional int32 bar = 2;\n}\n")
m.exec(Program, m.input.length)
<commit_msg>Provide a way to extract token styles<commit_after>// TODO:
//
// - Token extraction
// - CodeMirror integration
// - Compiler
// - More efficient sliding cache data structure
// - Don't make whitespace part of tokens or rules
// - Reconsider rule return values and conditions

var modifiers = [
  "required", "optional", "repeated", "reserved", "default", "extensions", "packed"
]
var types = [
  "bool", "bytes", "double", "float", "string",
  "int32", "int64", "uint32", "uint64", "sint32", "sint64", "fixed32", "fixed64", "sfixed32", "sfixed64"
];

class Rule {
  constructor(name, code, tokenType = null) {
    this.name = name
    this.code = code
    this.tokenType = tokenType
  }

  get cached() {
    return this.tokenType != null
  }
}

const F = {F: true}, C = {C: true}

const space = " \t\r\n"

function succeed(_, v) {
  return v
}

function succeed_drop(m, v) {
  m.backtrackStack.pop()
  return v
}

const Program = new Rule("Program", Program_0)

function Program_0(m) {
  while (m.group(space) != F) {}
  return Program_1(m)
}

function Program_1(m) {
  m.setBacktrack(Program_3, Program_1)
  return m.call(Statement, Program_2)
}

function Program_2(m) {
  m.backtrackStack.pop()
  return Program_1(m)
}

function Program_3(m) {
  return m.eof()
}

const Statement = new Rule("Statement", Statement_0)

function Statement_0(m) {
  m.setBacktrack(Statement_message)
  return m.callWith(kw, "package", Statement_package_1)
}

function Statement_package_1(m) {
  return m.call(identifier, Statement_package_2)
}

function Statement_package_2(m) {
  return m.callWith(p, ";", succeed_drop)
}

function Statement_message(m) {
  return m.callWith(kw, "message", Statement_message_1)
}

function Statement_message_1(m) {
  return m.call(identifier, Statement_message_2)
}

function Statement_message_2(m) {
  return m.callWith(p, "{", Statement_message_3)
}

function Statement_message_3(m) {
  m.setBacktrack(Statement_message_5, Statement_message_3)
  return m.call(Field, Statement_message_4)
}

function Statement_message_4(m) {
  m.backtrackStack.pop()
  return Statement_message_3(m)
}

function Statement_message_5(m) {
  return m.callWith(p, "}", succeed)
}

const Field = new Rule("Field", Field_0)

function Field_0(m) {
  m.setBacktrack(Field_2, Field_0)
  return m.call(modifier, Field_1)
}

function Field_1(m) {
  m.backtrackStack.pop()
  return Field_0(m)
}

function Field_2(m) {
  return m.call(type, Field_3)
}

function Field_3(m) {
  return m.call(identifier, Field_4)
}

function Field_4(m) {
  return m.callWith(p, "=", Field_5)
}

function Field_5(m) {
  return m.call(number, Field_6)
}

function Field_6(m) {
  return m.callWith(p, ";", succeed)
}

const identifier = new Rule("identifier", identifier_0, "variable")

function identifier_0(m) {
  let r = m.re(/^[a-z]\w*/i)
  if (r == F) return F
  let value = r[0]
  while (m.group(space) != F) {}
  return value
}

const kw = new Rule("kw", kw_0, "keyword")

function kw_0(m) {
  return m.call(identifier, kw_1)
}

function kw_1(m, ident) {
  return m.arg == ident ? null : F
}

const type = new Rule("type", type_0, "variable-2")

function type_0(m) {
  return m.call(identifier, type_1)
}

function type_1(_, ident) {
  return types.indexOf(ident) > -1 ? null : F
}

const modifier = new Rule("modifier", modifier_0, "keyword")

function modifier_0() {
  return m.call(identifier, modifier_1)
}

function modifier_1(_, ident) {
  return modifiers.indexOf(ident) > -1 ? null : F
}

const p = new Rule("p", p_0)

function p_0(m) {
  if (m.str(m.arg) == F) return F
  while (m.group(space) != F) {}
}

const number = new Rule("number", number_0, "number")

function number_0(m) {
  if (m.re(/^\d+/) == F) return F
  while (m.group(space) != F) {}
}

const anyToken = new Rule("anyToken", anyToken_0)

function anyToken_0(m) {
  m.setBacktrack(anyToken_1)
  return m.call(number, succeed_drop)
}

function anyToken_1(m) {
  m.setBacktrack(anyToken_2)
  return m.call(identifier, succeed_drop)
}

function anyToken_2(m) {
  m.any()
  while (m.group(space) != F) {}
}

function anyTokens(m) {
  if (m.eof() == F) return m.call(anyToken, anyTokens)
}

class Match {
  constructor(rule, arg, start, parent) {
    this.rule = rule
    this.arg = arg
    this.start = start
    this.parent = parent
    this.end = -1
    this.value = null
  }
}

class Frame {
  constructor(match, arg, next) {
    this.match = match
    this.arg = arg
    this.next = next
  }
}

class BacktrackFrame {
  constructor(pos, isLookahead, frameDepth, next, recover) {
    this.pos = pos
    this.isLookahead = isLookahead
    this.frameDepth = frameDepth
    this.next = next
    this.recover = recover
  }
}

const MIN_SAVE_DISTANCE = 15, MAX_BACKTRACK_DISTANCE = 100

class ModeMatcher {
  constructor(input) {
    this.pos = 0
    this.input = input
    // :: [Frame]
    this.stack = []
    // :: [BacktrackFrame]
    this.backtrackStack = []
    this.callee = null
    this.cache = []
    this.frontierStack = null
    this.frontierBacktrack = null
    this.frontierPos = -1
    this.skipping = false
  }

  get currentMatch() {
    for (let i = this.stack.length - 1; i >= 0; i--) {
      let m = this.stack[i].match
      if (m) return m
    }
  }

  get arg() {
    return this.stack[this.stack.length - 1].arg
  }

  re(re) {
    let match = re.exec(this.input.slice(this.pos))
    if (match) this.pos += match[0].length
    return match || F
  }

  str(string) {
    if (this.input.slice(this.pos, this.pos + string.length) == string)
      this.pos += string.length
    else
      return F
  }

  eof() {
    return this.pos == this.input.length ? null : F
  }

  any() {
    if (this.pos == this.input.length) return F
    this.pos++
  }

  setBacktrack(next, recover, lookahead) {
    this.backtrackStack.push(new BacktrackFrame(this.pos, lookahead, this.stack.length, next, recover))
  }

  group(str) {
    if (this.pos < this.input.length && str.indexOf(this.input[this.pos]) > -1)
      this.pos++
    else
      return F
  }

  getMatch(rule, arg) {
    let cached = this.cache[this.pos]
    if (cached) for (let i = 0; i < cached.length; i++)
      if (cached[i].rule === rule && cached[i].arg === arg) {
        // Move this rule to the front so that it gets picked up by
        // the token gathering method
        if (i) { let tmp = cached[i]; cached[i] = cached[0]; cached[0] = tmp }
        return cached[i]
      }
  }

  call(rule, next) {
    return this.callWith(rule, F, next)
  }

  callWith(rule, arg, next) {
    let match
    if (rule.cached) {
      let argVal = arg == F ? null : arg
      let cached = this.getMatch(rule, argVal)
      if (cached) {
        cached.parent = this.currentMatch
        this.pos = cached.end
        return next(this, cached.value)
      }
      match = new Match(rule, argVal, this.pos, this.currentMatch)
    }
    this.stack.push(new Frame(match, arg, next))
    this.callee = rule
    return C
  }

  exec(startRule, onAdvance) {
    let result = C
    this.callee = startRule
    for (;;) {
      if (result === C) {
        result = this.callee.code(this)
      } else if (result === F) {
        let backtrack
        do {
          backtrack = this.backtrackStack.pop()
        } while (backtrack && backtrack.pos < this.pos - MAX_BACKTRACK_DISTANCE)
        if (backtrack) {
          if (!this.skipping &&
              !backtrack.isLookahead && this.pos > this.frontierPos &&
              this.pos > backtrack.pos + MIN_SAVE_DISTANCE) {
            this.frontierStack = this.stack.slice()
            this.frontierBacktrack = this.backtrackStack.concat(backtrack)
            this.frontierPos = this.pos
          }
          this.pos = backtrack.pos
          this.stack.length = backtrack.frameDepth
          result = backtrack.next(this)
        } else if (!this.frontierStack) {
          result = anyTokens(this)
        } else {
          if (onAdvance(this) === false) return
          this.stack = this.frontierStack.slice()
          this.pos = this.frontierPos
          this.skipping = true
          result = this.call(anyToken, m => {
            m.skipping = false
            m.backtrackStack = m.frontierBacktrack.slice()
            m.frontierPos = this.pos
            for (let i = 0; i < m.backtrackStack.length; i++) {
              let bt = m.backtrackStack[i]
              bt.pos = m.pos
              if (bt.recover) {
                m.backtrackStack.splice(++i, 0, new BacktrackFrame(m.pos, false, bt.frameDepth, bt.recover))
                bt.recover = null
              }
            }
            return F
          })
        }
      } else if (this.stack.length == 0) {
        if (onAdvance(this) === false) return
        this.callee = startRule
        result = C
      } else {
        if (onAdvance(this) === false) return
        let frame = this.stack.pop(), match = frame.match
        if (match) {
          match.end = this.pos
          match.value = result
          ;(this.cache[match.start] || (this.cache[match.start] = [])).push(match)
        }
        result = frame.next(this, result)
      }
    }
  }

  getTokens(from, to) {
    let result = [], last = 0
    for (let pos = from; pos < to; pos++) {
      let cached = this.cache[pos]
      if (cached) for (let i = 0; i < cached.length; i++) {
        if (cached[i].rule.tokenType) {
          if (pos > last) result.push(pos, null)
          last = Math.min(to, cached[i].end)
          result.push(last, cached[i].rule.tokenType)
          pos = last - 1
          break
        }
      }
    }
    if (last < to) result.push(to, null)
    return result
  }
}

let m = new ModeMatcher("package foo;\n\nmessage Address {\n  required string foo = 1-\n  optional int32 bar = 2;\n}\n")
m.exec(Program, m => m.pos < m.input.length)
<|endoftext|>


<commit_before>/* eslint-disable global-require, no-underscore-dangle */

import React from 'react'
import {
  createStore,
  applyMiddleware,
  compose,
  combineReducers,
} from 'redux'
import { combineEpics, createEpicMiddleware } from 'redux-observable'
import { Provider } from 'react-redux'
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'
import metricsMiddleware from 'browser-metrics/lib/reduxMetricsMiddleware'
import createStyleManager from 'modules/styles/createDefaultContext'
import analytics from 'modules/analytics'
import dataReducer from 'modules/redux/dataReducer'
import Routes from 'review/Routes'
import accountReducer from 'review/routes/profile/accountReducer'
import accountEpic from 'review/routes/profile/accountEpic'
import buildReducer from 'review/routes/build/buildReducer'
import buildEpic from 'review/routes/build/buildEpic'
import repositoryReducer from 'review/routes/repository/repositoryReducer'
import repositoryEpic from 'review/routes/repository/repositoryEpic'
import repositoryDetailsReducer from 'review/routes/repository/detailsReducer'
import repositoryDetailsEpic from 'review/routes/repository/detailsEpic'
import profileReducer from 'review/routes/profile/profileReducer'
import profileEpic from 'review/routes/profile/profileEpic'
import dashboardReducer from 'review/routes/dashboard/dashboardReducer'
import dashboardEpic from 'review/routes/dashboard/dashboardEpic'

const rootEpic = combineEpics(
  accountEpic,
  buildEpic,
  repositoryEpic,
  repositoryDetailsEpic,
  profileEpic,
  dashboardEpic,
)

let middlewares = [
  metricsMiddleware({
    trackTiming: analytics.trackTiming,
  }),
  createEpicMiddleware(rootEpic),
]

if (
  process.env.NODE_ENV !== 'production' &&
  !window.__REDUX_DEVTOOLS_EXTENSION__
) {
  const createLogger = require('redux-logger').createLogger

  middlewares = [...middlewares, createLogger()]
}

const uiReducer = combineReducers({
  account: accountReducer,
  build: buildReducer,
  repository: repositoryReducer,
  repositoryDetails: repositoryDetailsReducer,
  profile: profileReducer,
  dashboard: dashboardReducer,
})

const rootReducers = combineReducers({
  ui: uiReducer,
  data: dataReducer,
})

const composeEnhancers = (process.env.NODE_ENV !== 'production' &&
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose

const store = composeEnhancers(
  applyMiddleware(...middlewares),
)(createStore)(rootReducers)

function Root() {
  const { styleManager, theme } = createStyleManager()

  return (
    <Provider store={store}>
      <MuiThemeProvider theme={theme} styleManager={styleManager}>
        <Routes />
      </MuiThemeProvider>
    </Provider>
  )
}

export default Root
<commit_msg>fix: typo<commit_after>/* eslint-disable global-require */

import React from 'react'
import {
  createStore,
  applyMiddleware,
  compose,
  combineReducers,
} from 'redux'
import { combineEpics, createEpicMiddleware } from 'redux-observable'
import { Provider } from 'react-redux'
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'
import metricsMiddleware from 'browser-metrics/lib/reduxMetricsMiddleware'
import createDefaultContext from 'modules/styles/createDefaultContext'
import analytics from 'modules/analytics'
import dataReducer from 'modules/redux/dataReducer'
import Routes from 'review/Routes'
import accountReducer from 'review/routes/profile/accountReducer'
import accountEpic from 'review/routes/profile/accountEpic'
import buildReducer from 'review/routes/build/buildReducer'
import buildEpic from 'review/routes/build/buildEpic'
import repositoryReducer from 'review/routes/repository/repositoryReducer'
import repositoryEpic from 'review/routes/repository/repositoryEpic'
import repositoryDetailsReducer from 'review/routes/repository/detailsReducer'
import repositoryDetailsEpic from 'review/routes/repository/detailsEpic'
import profileReducer from 'review/routes/profile/profileReducer'
import profileEpic from 'review/routes/profile/profileEpic'
import dashboardReducer from 'review/routes/dashboard/dashboardReducer'
import dashboardEpic from 'review/routes/dashboard/dashboardEpic'

const rootEpic = combineEpics(
  accountEpic,
  buildEpic,
  repositoryEpic,
  repositoryDetailsEpic,
  profileEpic,
  dashboardEpic,
)

let middlewares = [
  metricsMiddleware({
    trackTiming: analytics.trackTiming,
  }),
  createEpicMiddleware(rootEpic),
]

if (process.env.NODE_ENV === 'development' && !window.devToolsExtension) {
  const loggerMiddleware = require('redux-logger')

  middlewares = [
    ...middlewares,
    loggerMiddleware(),
  ]
}

const uiReducer = combineReducers({
  account: accountReducer,
  build: buildReducer,
  repository: repositoryReducer,
  repositoryDetails: repositoryDetailsReducer,
  profile: profileReducer,
  dashboard: dashboardReducer,
})

const rootReducers = combineReducers({
  ui: uiReducer,
  data: dataReducer,
})

/* eslint-disable no-underscore-dangle */
const composeEnhancers = (process.env.NODE_ENV !== 'production' &&
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose
/* eslint-enable no-underscore-dangle */

const store = composeEnhancers(
  applyMiddleware(...middlewares),
)(createStore)(rootReducers)

function Root() {
  const { styleManager, theme } = createDefaultContext()

  return (
    <Provider store={store}>
      <MuiThemeProvider theme={theme} styleManager={styleManager}>
        <Routes />
      </MuiThemeProvider>
    </Provider>
  )
}

export default Root
<|endoftext|>


<commit_before>import atExtendNoMissingPlaceholder from "./at-extend-no-missing-placeholder";
import atElseClosingBraceNewlineAfter from "./at-else-closing-brace-newline-after";
import atElseClosingBraceSpaceAfter from "./at-else-closing-brace-space-after";
import atElseEmptyLineBefore from "./at-else-empty-line-before";
import atElseIfParenthesesSpaceBefore from "./at-else-if-parentheses-space-before";
import atFunctionNamedArguments from "./at-function-named-arguments";
import atFunctionParenthesesSpaceBefore from "./at-function-parentheses-space-before";
import atFunctionPattern from "./at-function-pattern";
import atIfClosingBraceNewlineAfter from "./at-if-closing-brace-newline-after";
import atIfClosingBraceSpaceAfter from "./at-if-closing-brace-space-after";
import atImportNoPartialLeadingUnderscore from "./at-import-no-partial-leading-underscore";
import atImportPartialExtensionBlacklist from "./at-import-partial-extension-blacklist";
import atImportPartialExtensionWhitelist from "./at-import-partial-extension-whitelist";
import atMixinArgumentlessCallParentheses from "./at-mixin-argumentless-call-parentheses";
import atMixinNamedArguments from "./at-mixin-named-arguments";
import atMixinParenthesesSpaceBefore from "./at-mixin-parentheses-space-before";
import atMixinPattern from "./at-mixin-pattern";
import atRuleNoUnknown from "./at-rule-no-unknown";
import declarationNestedProperties from "./declaration-nested-properties";
import declarationNestedPropertiesNoDividedGroups from "./declaration-nested-properties-no-divided-groups";
import dollarVariableColonNewlineAfter from "./dollar-variable-colon-newline-after";
import dollarVariableColonSpaceAfter from "./dollar-variable-colon-space-after";
import dollarVariableColonSpaceBefore from "./dollar-variable-colon-space-before";
import dollarVariableDefault from "./dollar-variable-default";
import dollarVariableEmptyLineBefore from "./dollar-variable-empty-line-before";
import dollarVariableNoMissingInterpolation from "./dollar-variable-no-missing-interpolation";
import dollarVariablePattern from "./dollar-variable-pattern";
import doubleSlashCommentEmptyLineBefore from "./double-slash-comment-empty-line-before";
import doubleSlashCommentInline from "./double-slash-comment-inline";
import doubleSlashCommentWhitespaceInside from "./double-slash-comment-whitespace-inside";
import mediaFeatureValueDollarVariable from "./media-feature-value-dollar-variable";
import noDollarVariables from "./no-dollar-variables";
import noDuplicateDollarVariables from "./no-duplicate-dollar-variables";
import operatorNoNewlineAfter from "./operator-no-newline-after";
import operatorNoNewlineBefore from "./operator-no-newline-before";
import operatorNoUnspaced from "./operator-no-unspaced";
import partialNoImport from "./partial-no-import";
import percentPlaceholderPattern from "./percent-placeholder-pattern";
import selectorNestCombinators from "./selector-nest-combinators";
import selectorNoRedundantNestingSelector from "./selector-no-redundant-nesting-selector";

export default {
  "at-extend-no-missing-placeholder": atExtendNoMissingPlaceholder,
  "at-else-closing-brace-newline-after": atElseClosingBraceNewlineAfter,
  "at-else-closing-brace-space-after": atElseClosingBraceSpaceAfter,
  "at-else-empty-line-before": atElseEmptyLineBefore,
  "at-else-if-parentheses-space-before": atElseIfParenthesesSpaceBefore,
  "at-function-named-arguments": atFunctionNamedArguments,
  "at-function-parentheses-space-before": atFunctionParenthesesSpaceBefore,
  "at-function-pattern": atFunctionPattern,
  "at-if-closing-brace-newline-after": atIfClosingBraceNewlineAfter,
  "at-if-closing-brace-space-after": atIfClosingBraceSpaceAfter,
  "at-import-no-partial-leading-underscore": atImportNoPartialLeadingUnderscore,
  "at-import-partial-extension-blacklist": atImportPartialExtensionBlacklist,
  "at-import-partial-extension-whitelist": atImportPartialExtensionWhitelist,
  "at-mixin-argumentless-call-parentheses": atMixinArgumentlessCallParentheses,
  "at-mixin-named-arguments": atMixinNamedArguments,
  "at-mixin-parentheses-space-before": atMixinParenthesesSpaceBefore,
  "at-mixin-pattern": atMixinPattern,
  "at-rule-no-unknown": atRuleNoUnknown,
  "declaration-nested-properties": declarationNestedProperties,
  "declaration-nested-properties-no-divided-groups": declarationNestedPropertiesNoDividedGroups,
  "dollar-variable-colon-newline-after": dollarVariableColonNewlineAfter,
  "dollar-variable-colon-space-after": dollarVariableColonSpaceAfter,
  "dollar-variable-colon-space-before": dollarVariableColonSpaceBefore,
  "dollar-variable-default": dollarVariableDefault,
  "dollar-variable-empty-line-before": dollarVariableEmptyLineBefore,
  "dollar-variable-no-missing-interpolation": dollarVariableNoMissingInterpolation,
  "dollar-variable-pattern": dollarVariablePattern,
  "double-slash-comment-empty-line-before": doubleSlashCommentEmptyLineBefore,
  "double-slash-comment-inline": doubleSlashCommentInline,
  "double-slash-comment-whitespace-inside": doubleSlashCommentWhitespaceInside,
  "media-feature-value-dollar-variable": mediaFeatureValueDollarVariable,
  "no-dollar-variables": noDollarVariables,
  "no-duplicate-dollar-variables": noDuplicateDollarVariables,
  "operator-no-newline-after": operatorNoNewlineAfter,
  "operator-no-newline-before": operatorNoNewlineBefore,
  "operator-no-unspaced": operatorNoUnspaced,
  "percent-placeholder-pattern": percentPlaceholderPattern,
  "partial-no-import": partialNoImport,
  "selector-nest-combinators": selectorNestCombinators,
  "selector-no-redundant-nesting-selector": selectorNoRedundantNestingSelector
};
<commit_msg>adding new quote-not-used-with-quoted-string rule<commit_after>import atExtendNoMissingPlaceholder from "./at-extend-no-missing-placeholder";
import atElseClosingBraceNewlineAfter from "./at-else-closing-brace-newline-after";
import atElseClosingBraceSpaceAfter from "./at-else-closing-brace-space-after";
import atElseEmptyLineBefore from "./at-else-empty-line-before";
import atElseIfParenthesesSpaceBefore from "./at-else-if-parentheses-space-before";
import atFunctionNamedArguments from "./at-function-named-arguments";
import atFunctionParenthesesSpaceBefore from "./at-function-parentheses-space-before";
import atFunctionPattern from "./at-function-pattern";
import atIfClosingBraceNewlineAfter from "./at-if-closing-brace-newline-after";
import atIfClosingBraceSpaceAfter from "./at-if-closing-brace-space-after";
import atImportNoPartialLeadingUnderscore from "./at-import-no-partial-leading-underscore";
import atImportPartialExtensionBlacklist from "./at-import-partial-extension-blacklist";
import atImportPartialExtensionWhitelist from "./at-import-partial-extension-whitelist";
import atMixinArgumentlessCallParentheses from "./at-mixin-argumentless-call-parentheses";
import atMixinNamedArguments from "./at-mixin-named-arguments";
import atMixinParenthesesSpaceBefore from "./at-mixin-parentheses-space-before";
import atMixinPattern from "./at-mixin-pattern";
import atRuleNoUnknown from "./at-rule-no-unknown";
import declarationNestedProperties from "./declaration-nested-properties";
import declarationNestedPropertiesNoDividedGroups from "./declaration-nested-properties-no-divided-groups";
import dollarVariableColonNewlineAfter from "./dollar-variable-colon-newline-after";
import dollarVariableColonSpaceAfter from "./dollar-variable-colon-space-after";
import dollarVariableColonSpaceBefore from "./dollar-variable-colon-space-before";
import dollarVariableDefault from "./dollar-variable-default";
import dollarVariableEmptyLineBefore from "./dollar-variable-empty-line-before";
import dollarVariableNoMissingInterpolation from "./dollar-variable-no-missing-interpolation";
import dollarVariablePattern from "./dollar-variable-pattern";
import doubleSlashCommentEmptyLineBefore from "./double-slash-comment-empty-line-before";
import doubleSlashCommentInline from "./double-slash-comment-inline";
import doubleSlashCommentWhitespaceInside from "./double-slash-comment-whitespace-inside";
import mediaFeatureValueDollarVariable from "./media-feature-value-dollar-variable";
import noDollarVariables from "./no-dollar-variables";
import noDuplicateDollarVariables from "./no-duplicate-dollar-variables";
import operatorNoNewlineAfter from "./operator-no-newline-after";
import operatorNoNewlineBefore from "./operator-no-newline-before";
import operatorNoUnspaced from "./operator-no-unspaced";
import partialNoImport from "./partial-no-import";
import percentPlaceholderPattern from "./percent-placeholder-pattern";
import quoteNoQuotedString from "./quoteNoQuotedString";
import selectorNestCombinators from "./selector-nest-combinators";
import selectorNoRedundantNestingSelector from "./selector-no-redundant-nesting-selector";

export default {
  "at-extend-no-missing-placeholder": atExtendNoMissingPlaceholder,
  "at-else-closing-brace-newline-after": atElseClosingBraceNewlineAfter,
  "at-else-closing-brace-space-after": atElseClosingBraceSpaceAfter,
  "at-else-empty-line-before": atElseEmptyLineBefore,
  "at-else-if-parentheses-space-before": atElseIfParenthesesSpaceBefore,
  "at-function-named-arguments": atFunctionNamedArguments,
  "at-function-parentheses-space-before": atFunctionParenthesesSpaceBefore,
  "at-function-pattern": atFunctionPattern,
  "at-if-closing-brace-newline-after": atIfClosingBraceNewlineAfter,
  "at-if-closing-brace-space-after": atIfClosingBraceSpaceAfter,
  "at-import-no-partial-leading-underscore": atImportNoPartialLeadingUnderscore,
  "at-import-partial-extension-blacklist": atImportPartialExtensionBlacklist,
  "at-import-partial-extension-whitelist": atImportPartialExtensionWhitelist,
  "at-mixin-argumentless-call-parentheses": atMixinArgumentlessCallParentheses,
  "at-mixin-named-arguments": atMixinNamedArguments,
  "at-mixin-parentheses-space-before": atMixinParenthesesSpaceBefore,
  "at-mixin-pattern": atMixinPattern,
  "at-rule-no-unknown": atRuleNoUnknown,
  "declaration-nested-properties": declarationNestedProperties,
  "declaration-nested-properties-no-divided-groups": declarationNestedPropertiesNoDividedGroups,
  "dollar-variable-colon-newline-after": dollarVariableColonNewlineAfter,
  "dollar-variable-colon-space-after": dollarVariableColonSpaceAfter,
  "dollar-variable-colon-space-before": dollarVariableColonSpaceBefore,
  "dollar-variable-default": dollarVariableDefault,
  "dollar-variable-empty-line-before": dollarVariableEmptyLineBefore,
  "dollar-variable-no-missing-interpolation": dollarVariableNoMissingInterpolation,
  "dollar-variable-pattern": dollarVariablePattern,
  "double-slash-comment-empty-line-before": doubleSlashCommentEmptyLineBefore,
  "double-slash-comment-inline": doubleSlashCommentInline,
  "double-slash-comment-whitespace-inside": doubleSlashCommentWhitespaceInside,
  "media-feature-value-dollar-variable": mediaFeatureValueDollarVariable,
  "no-dollar-variables": noDollarVariables,
  "no-duplicate-dollar-variables": noDuplicateDollarVariables,
  "operator-no-newline-after": operatorNoNewlineAfter,
  "operator-no-newline-before": operatorNoNewlineBefore,
  "operator-no-unspaced": operatorNoUnspaced,
  "percent-placeholder-pattern": percentPlaceholderPattern,
  "partial-no-import": partialNoImport,
  "quote-not-used-with-quoted-string": quoteNoQuotedString,
  "selector-nest-combinators": selectorNestCombinators,
  "selector-no-redundant-nesting-selector": selectorNoRedundantNestingSelector
};
<|endoftext|>


<commit_before>import React from 'react'
import {join} from 'path'
import {withRouter} from 'next/router'
import {Box, BorderBox, Flex, Relative} from '@primer/components'
import NodeLink from './NodeLink'
import {rootPage} from './utils'

export default function SideNav(props) {
  return (
    <Relative is="nav">
      <BorderBox
        bg="gray.0"
        borderColor="gray.2"
        borderRight={1}
        borderTop={[1, 1, 0, 0]}
        height="100%"
        id="sidenav"
        {...props}
      >
        <Flex flexDirection="column" alignItems="start">
          <Router>

            <RouteMatch path="/css">

            </RouteMatch>
          </Router>
        </Flex>
      </BorderBox>
    </Relative>
  )
}

/**
 * A
 gets a `path` and optional children. If it has children it will
 * render those and prepend each child's `href` prop with the provided `path`.
 * This means that you can do:
 *
 * ```jsx
 * <Section path="/section">
 *   <Link href="foo">Links to /section/foo</Link>
 * </Section>
 * ```
 *
 * If no children are provided, it renders a <NavList> with the provided
 * `path`.
 */
const Section = ({path, children}) => (
  <BorderBox p={4} border={0} borderBottom={1} borderRadius={0} width="100%">
    {children ? (
      React.Children.map(children, child => {
        const href = join(path, child.props.href || '')
        return React.cloneElement(child, {href})
      })
    ) : (
      <NavList path={path} />
    )}
  </BorderBox>
)

/**
 * A <NavList> renders a <SectionLink> for the given `path` and looks up the
 * path in the page tree. If a node is found, it renders a <NavLink> for each
 * of the node's children.
 */
function NavList({path}) {
  const node = rootPage.first(node => node.path === path)
  const children = node ? node.children.sort(nodeSort) : []
  return (
    <>
      <SectionLink href={path} />
      {children.map(child => <NavLink href={child.path} key={child.path} />)}
    </>
  )
}

/**
 * A <SectionLink> is really just a <NodeLink> that's bold when its `href`
 * matches the current path, wrapped in a <Box> for whitespace.
 */
const SectionLink = withRouter(({href, router, ...rest}) => (
  <Box mb={2}>
    <NodeLink
      href={href}
      color="gray.9"
      fontSize={2}
      fontWeight={router.pathname.startsWith(href) ? 'bold' : null}
      {...rest}
    />
  </Box>
))

/**
 * A <NavLink> is a <NodeLink> that turns black when its `href` matches the
 * current path, wrapped in a <Box> for whitespace.
 */
const NavLink = withRouter(({href, router, ...rest}) => {
  return (
    <Box mb={2}>
      <NodeLink href={href} color={router.pathname === href ? 'black' : undefined} fontSize={1} {...rest} />
    </Box>
  )
})

/**
 * This inspired React Router's <Router> component, in that it looks for
 * children with the `path` prop and only renders the _first_ one that matches
 * the beginning of the current path. Children without a `path` prop are always
 * rendered.
 */
const Router = withRouter(({router, children}) => {
  let matched = false
  return React.Children.toArray(children).map(child => {
    if (child.props.path) {
      if (!matched && router.pathname.indexOf(child.props.path) === 0) {
        return (matched = child)
      }
    } else {
      return child
    }
  })
})

/**
 * <RouteMatch> is just a way to conditonally render content without a wrapper
 * element when contained directly in a <Router>:
 *
 * ```jsx
 * <Router>
 *   <RouteMatch path="/some/dir">
 *     this will only show up on pages whose path begins with "/some/dir"
 *   </RouteMatch>
 * </Router>
 * ```
 */
function RouteMatch({children}) {
  return children
}

function sortCompare(a, b, get) {
  const aa = get(a)
  const bb = get(b)
  return typeof aa === 'string' && typeof bb === 'string' ? aa.localeCompare(bb) : undefined
}

function nodeSort(a, b) {
  return sortCompare(a, b, node => node.meta.sort_title || node.meta.title)
}
<commit_msg>fix side nav height<commit_after>import React from 'react'
import {join} from 'path'
import {withRouter} from 'next/router'
import {Box, BorderBox, Flex, Relative} from '@primer/components'
import NodeLink from './NodeLink'
import {rootPage} from './utils'

export default function SideNav(props) {
  return (
    <Relative is="nav">
      <Box id="sidenav" {...props}>
        <Flex flexDirection="column" alignItems="start">
          <Router>

            <RouteMatch path="/css">

            </RouteMatch>
          </Router>
        </Flex>
      </Box>
    </Relative>
  )
}

/**
 * A
 gets a `path` and optional children. If it has children it will
 * render those and prepend each child's `href` prop with the provided `path`.
 * This means that you can do:
 *
 * ```jsx
 * <Section path="/section">
 *   <Link href="foo">Links to /section/foo</Link>
 * </Section>
 * ```
 *
 * If no children are provided, it renders a <NavList> with the provided
 * `path`.
 */
const Section = ({path, children}) => (
  <BorderBox p={4} border={0} borderBottom={1} borderRadius={0} width="100%">
    {children ? (
      React.Children.map(children, child => {
        const href = join(path, child.props.href || '')
        return React.cloneElement(child, {href})
      })
    ) : (
      <NavList path={path} />
    )}
  </BorderBox>
)

/**
 * A <NavList> renders a <SectionLink> for the given `path` and looks up the
 * path in the page tree. If a node is found, it renders a <NavLink> for each
 * of the node's children.
 */
function NavList({path}) {
  const node = rootPage.first(node => node.path === path)
  const children = node ? node.children.sort(nodeSort) : []
  return (
    <>
      <SectionLink href={path} />
      {children.map(child => <NavLink href={child.path} key={child.path} />)}
    </>
  )
}

/**
 * A <SectionLink> is really just a <NodeLink> that's bold when its `href`
 * matches the current path, wrapped in a <Box> for whitespace.
 */
const SectionLink = withRouter(({href, router, ...rest}) => (
  <Box mb={2}>
    <NodeLink
      href={href}
      color="gray.9"
      fontSize={2}
      fontWeight={router.pathname.startsWith(href) ? 'bold' : null}
      {...rest}
    />
  </Box>
))

/**
 * A <NavLink> is a <NodeLink> that turns black when its `href` matches the
 * current path, wrapped in a <Box> for whitespace.
 */
const NavLink = withRouter(({href, router, ...rest}) => {
  return (
    <Box mb={2}>
      <NodeLink href={href} color={router.pathname === href ? 'black' : undefined} fontSize={1} {...rest} />
    </Box>
  )
})

/**
 * This inspired React Router's <Router> component, in that it looks for
 * children with the `path` prop and only renders the _first_ one that matches
 * the beginning of the current path. Children without a `path` prop are always
 * rendered.
 */
const Router = withRouter(({router, children}) => {
  let matched = false
  return React.Children.toArray(children).map(child => {
    if (child.props.path) {
      if (!matched && router.pathname.indexOf(child.props.path) === 0) {
        return (matched = child)
      }
    } else {
      return child
    }
  })
})

/**
 * <RouteMatch> is just a way to conditonally render content without a wrapper
 * element when contained directly in a <Router>:
 *
 * ```jsx
 * <Router>
 *   <RouteMatch path="/some/dir">
 *     this will only show up on pages whose path begins with "/some/dir"
 *   </RouteMatch>
 * </Router>
 * ```
 */
function RouteMatch({children}) {
  return children
}

function sortCompare(a, b, get) {
  const aa = get(a)
  const bb = get(b)
  return typeof aa === 'string' && typeof bb === 'string' ? aa.localeCompare(bb) : undefined
}

function nodeSort(a, b) {
  return sortCompare(a, b, node => node.meta.sort_title || node.meta.title)
}
<|endoftext|>


<commit_before>// ==UserScript==
// @name        GM_testing
// @description Simplifies switching between courses that use different interface language (i.e., base language, the language from which you learn).
// @namespace   https://github.com/zeta12ti/DuolingoCourseSwitcher
// @include https://*.duolingo.com/*
// @grant none
// @version     0.3
// @author      zeta12ti
// ==/UserScript==

// NOTES:
// document.querySelector instead of jquery.

// localStorage['duo.state'] contains a ton of good data
// I need to find out when it's updated

// It *looks* like all course info is stored in duo.state. No need for setting values?

// found this in https://d35aaqx5ub95lt.cloudfront.net/js/app-5dfdd9c3.js
/*
      flag: 'HCWXf _3PU7E _2XSZu',
      '_popover-item': '_1qBnH',
      'course-popover': '_20LC5 FUNrE _3w0_r OSaWc _2HujR _1ZY-H',
      'course-header': '_2PurW',
      course: '_2kNgI _1qBnH',
      'course-active': '_1oVFS _2kNgI _1qBnH',
      'menu-flag': '_3vx2Z _1ct7y _2XSZu',
      level: '_1fA14',
      'course-divider': 'qsrrc',
      'course-add': '_2uBp_ _1qBnH',
*/

// duo is very different from before.
// interesting things:
// duo.l10n.declared[183] is level (niveau)
// duo.l10n.declared[224] includes the number, 335 all caps
// it seems that the actual page uses 183 + ' ' + '{{level}}'
// Even languages with their own numerals just use arabic numerals
// although it seems that rtl languages reverse the order (not sure if that's automatic)
// 122, 142 (Languages), 147 J'apprends, 58 langue d'apprendissage
// 178 add a new course
// (checked in english and french)

var duoState = JSON.parse(localStorage['duo.state'])
var duo = window.duo // no need to use unsecureWindow, since we don't grant anything

// Language names (in english) - for use with duo.l10n.undeclared
// extracted from https://duolingo.com/api/1/courses/list
var languages = JSON.parse('{"ar": "Arabic", "bn": "Bengali", "ca": "Catalan", "cs": "Czech", "cy": "Welsh", "da": "Danish", "de": "German", "el": "Greek", "en": "English", "eo": "Esperanto", "es": "Spanish", "fr": "French", "ga": "Irish", "gn": "Guarani (Jopar\\u00e1)", "he": "Hebrew", "hi": "Hindi", "hu": "Hungarian", "id": "Indonesian", "it": "Italian", "ja": "Japanese", "ko": "Korean", "nl-NL": "Dutch", "no-BO": "Norwegian (Bokm\\u00e5l)", "pa": "Punjabi (Gurmukhi)", "pl": "Polish", "pt": "Portuguese", "ro": "Romanian", "ru": "Russian", "sv": "Swedish", "sw": "Swahili", "ta": "Tamil", "te": "Telugu", "th": "Thai", "tl": "Tagalog", "tlh": "Klingon", "tr": "Turkish", "uk": "Ukrainian", "vi": "Vietnamese", "zh-CN": "Chinese"}')

function getLanguageString(languageCode) {
    if (languageCode in languages) {
        return languages[languageCode]
    } else {
        return "Unknown"
    }
}

// language flags - extracted from https://d35aaqx5ub95lt.cloudfront.net/js/app-5dfdd9c3.js
// keys: remove flag- prefix and keep consistency - zs -> zh-CN, kl -> tlh, (zs -> zh-¿?)
// It appears that the strings are used as part of the class name.
var flags=JSON.parse('{"ar": "_1ARRD _3viv6", "bn": "_2TXAL _3viv6", "ca": "mc4rg _3viv6", "cs": "_1uPQW _3viv6", "cy": "_1jO8h _3viv6", "da": "_1h0xh _3viv6", "de": "oboa9 _3viv6", "dk": "_3AA1F _3viv6", "el": "_2tQo9 _3viv6", "en": "_2cR-E _3viv6", "eo": "pWj0w _3viv6", "es": "u5W-o _3viv6", "fr": "_2KQN3 _3viv6", "ga": "_1vhNM _3viv6", "gn": "_24xu4 _3viv6", "he": "_PDrK _3viv6", "hi": "OgUIe _3viv6", "hu": "_1S3hi _3viv6", "id": "_107sn _3viv6", "it": "_1PruQ _3viv6", "ja": "_2N-Uj _3viv6", "ko": "_2lkzc _3viv6", "nl-NL": "_1fajz _3viv6", "no-BO": "_200jU _3viv6", "pl": "_3uusw _3viv6", "pt": "pmGwL _3viv6", "ro": "_12U6e _3viv6", "ru": "_1eqxJ _3viv6", "sn": "q_PD- _3viv6", "sv": "_2DMfV _3viv6", "sw": "_3T1km _3viv6", "th": "_2oTcA _3viv6", "tl": "_1q_MQ _3viv6", "tlh": "_6mRM _3viv6", "tr": "_1tJJ2 _3viv6", "uk": "_1zZsN _3viv6", "un": "t-XH- _3viv6", "vi": "_1KtzC _3viv6", "zh": "xi6jQ _3viv6", "zh-CN": "_2gNgd _3viv6", "_circle-flag": "_2XSZu", "_flag": "_3viv6", "medium-circle-flag": "_1ct7y _2XSZu", "micro-circle-flag": "_3i5IF _2XSZu", "small-circle-flag": "_3PU7E _2XSZu"}');

function getLanguageFlag(languageCode) {
    if (languageCode in flags) {
        return flags[languageCode]
    } else {
        return flags.un // maybe one of the other flags would be appropriate, but this is what jrikhal uses.
    }
}

// Removes a css node by its css selector
function deleteElement(query) {
    node = document.querySelector(query)
    node.parentNode.removeChild(element)
}

function deleteChildren(query) {
    node = document.querySelector(query)
    while (node.hasChildNodes()) {
        node.removeChild(node.lastChild);
    }
}

// calculates the level given the experience
function xpToLevel ( xp ) {
    var xpLevelCutoffs = duoState.config.xpLevelCutoffs
    var level = 1
    var length = xpLevelCutoffs.length
    for (var i = 0; i < length; i++) {
        if (xp < xpLevelCutoffs[i]) {
            return level
        } else {
            level++
        }
    }
    return level
}

// Gets information about all currently learning courses from duo.state
function getCourseInfo() {
    var courses = duoState.courses
    var coursesOrganized = {}

    for (var course in courses) {
        if (courses.hasOwnProperty(course)) {
            var from = courses[course].fromLanguage
            var learning = courses[course].learningLanguage
            var xp = courses[course].xp
            if (!(from in coursesOrganized)) {
                coursesOrganized[from] = []
            }

            coursesOrganized[from].push({'from': from,
                                         'from_string': getLanguageString(from),
                                         'learning': learning,
                                         'learning_string': getLanguageString(learning),
                                         'xp': xp,
                                         'level': xpToLevel(xp)
                                         })
        }
    }
    return coursesOrganized
}

// Takes an argument of the form given by the output of getCourseInfo
// Sorts from languages by number of courses and learning languages by xp.
function sortCourseInfo(courses) {
    sortedLanguages = keys(courses).sort(function(a, b) { return courses[b].length - courses[a].length })
    return sortedLanguages.map(function(a) { return courses[a].sort(function(b,c) { return c.xp - b.xp } )})
}

// Use duoState to fetch current from and to languages
function getCurrentLanguages() {
    currentLanguages = {}
    currentLanguages.from = duoState.user.fromLanguage
    currentLanguages.to = duoState.user.learningLanguage
    return currentLanguages
}

// Switch source and target languages - credit to jrikhal
function switchCourse(SrcLang,TgtLang){
    //Needed due to inconsistency in Duo's language codes!!!
    if(SrcLang==='nl-NL'){SrcLang='dn';}
    if(SrcLang==='no-BO'){SrcLang='nb';}
    if(SrcLang==='zh-CN'){SrcLang='zs';}
    if(TgtLang==='nl-NL'){TgtLang='dn';}
    if(TgtLang==='no-BO'){TgtLang='nb';}
    if(TgtLang==='zh-CN'){TgtLang='zs';}

    request=new XMLHttpRequest();
    request.open("POST", "https://www.duolingo.com/api/1/me/switch_language", true)
    request.setRequestHeader('Content-type', 'application/x-www-form-urlencoded')
    request.send('learning_language='+TgtLang+'&from_language='+SrcLang)

    waitForSwitchCourseLoad(request)
}

// Reload page once the course is switched - credit to jrikhal
function waitForSwitchCourseLoad(request){
    if(request.readyState<4){
        setTimeout(function(){ waitForSwitchCourseLoad(request) }, 500)
    } else location.reload()
}

// counstructs a menu based on courses (array of arrays), other args are the current languages
function constructMenu(courses, fromLang, toLang) {
    var topMenu = document.querySelector('._20LC5')
    deleteChildren('._20LC5')

    var header = document.createElement("li")
    header.setAttribute('class', '_2PurW')
    header.innerHTML = '
' + duo.l10n.declared[142] + '
'
    topMenu.appendChild(header)

    var courseList = document.createElement('ul')
    courseList.setAttribute('class', '_1XE6M')
    topMenu.appendChild(courseList)

    var courseLength = courses.length
    for (var i=0; i<courseLength; i++) {
        var sourceLang = document.createElement('li')
        var className = '_2kNgI _1qBnH'
        if (courses[i][0].from == fromLang) { className = '_1oVFS' + className }
        sourceLang.setAttribute('class', className)

        var flag = document.createElement('span')
        className = getLanguageFlag(courses[i][0].from) + '_3vx2Z _1ct7y _2XSZu'
        flag.setAttribute('class', className)
        sourceLang.appendChild(flag)

        var languageName = getLanguageString(courses[i][0].from)
        var localizedLanguageName = duo.l10n.undeclared[languageName + '||"Le/L\'" + language name']
        sourceLang.appendChild(document.createTextNode(localizedLanguageName))

        var targetLangMenu = document.createElement('ul')
        targetLangMenu.setAttribute('class', '_20LC5 FUNrE _3w0_r OSaWc _2HujR _1ZY-H')

        var subheader = document.createElement("li")
        subheader.setAttribute('class', '_2PurW')
        subheader.innerHTML = '
' + duo.l10n.declared[147] + '
'
        targetLangMenu.appendChild(subheader)

        var subCourseList = document.createElement('ul')
        subCourseList.setAttribute('class', '_1XE6M')
        targetLangMenu.appendChild(subCourseList)

        var subCourseLength = courses[i].length
        for (var j=0; j<subCourseLength; j++) {
            var targetLang = document.createElement('li')
            className = '_2kNgI _1qBnH'
            if (courses[i][j].learning == toLang) {className = '_1oVFS' + className}
            targetLang.setAttribute('class', className)

            var subFlag = document.createElement('span')
            className = getLanguageFlag(courses[i][j].learning) + '_3vx2Z _1ct7y _2XSZu'
            subFlag.setAttribute('class', className)
            targetLang.appendChild(subFlag)

            var subLanguageName = getLanguageString(courses[i][j].learning)
            subLocalizedLanguageName = duo.l10n.undeclared[subLanguageName + '||I want to learn...']
            targetLang.appendChild(document.createTextNode(subLocalizedLanguageName))

            var levelText = document.createElement('span')
            levelText.setAttribute('class', '_1fA14')
            levelText.innerHTML = duo.l10n.declared[183] + ' ' + courses[i][j].level
            targetLang.appendChild(levelText)
        }

        topMenu.appendChild(sourceLang)
    }

    var divider = document.createElement('li')
    divider.setAttribute('class', 'qsrrc')
    topMenu.appendChild(divider)

    var mobileAddCourse = document.createElement('li')
    mobileAddCourse.setAttribute('class', '_2nd4- _1qBnH')

    var mobileLink = document.createElement('a')
    mobileLink.setAttribute('class', 'jsHOk')
    mobileLink.setAttribute('href', '/courses')
    mobileLink.innerText = '+'
    mobileAddCourse.appendChild(mobileLink)

    mobileAddCourse.appendChild(document.createTextNode(duo.l10n.declared[179]))
    topMenu.appendChild(mobileAddCourse)

    var addNewCourse = document.createElement('li')
    addNewCourse.setAttribute('class', '_2uBp_ _1qBnH')
    addNewCourse.innerText = duo.l10n.declared[178]
    topMenu.appendChild(addNewCourse)

        // add item for from language (flag, language name)
        // current has gray background
        // add on (mouseover) (maybe after adding submenu)
        // add submenu
            // set header (J'apprends)
            // loop over sub courses
                // add item for learning language (flag, language name, level)
                // current has grey background
                // include fromLang, toLang as attributes ?
                // add onClick
    // add Ajouter un nouveau cours

}
<commit_msg>Menu looks right now (yay).<commit_after>// ==UserScript==
// @name        GM_testing
// @description Simplifies switching between courses that use different interface language (i.e., base language, the language from which you learn).
// @namespace   https://github.com/zeta12ti/DuolingoCourseSwitcher
// @include https://*.duolingo.com/*
// @grant none
// @version     0.3
// @author      zeta12ti
// ==/UserScript==

// NOTES:
// document.querySelector instead of jquery.

// localStorage['duo.state'] contains a ton of good data
// I need to find out when it's updated

// It *looks* like all course info is stored in duo.state. No need for setting values?

// found this in https://d35aaqx5ub95lt.cloudfront.net/js/app-5dfdd9c3.js
/*
      flag: 'HCWXf _3PU7E _2XSZu',
      '_popover-item': '_1qBnH',
      'course-popover': '_20LC5 FUNrE _3w0_r OSaWc _2HujR _1ZY-H',
      'course-header': '_2PurW',
      course: '_2kNgI _1qBnH',
      'course-active': '_1oVFS _2kNgI _1qBnH',
      'menu-flag': '_3vx2Z _1ct7y _2XSZu',
      level: '_1fA14',
      'course-divider': 'qsrrc',
      'course-add': '_2uBp_ _1qBnH',
*/

// duo is very different from before.
// interesting things:
// duo.l10n.declared[183] is level (niveau)
// duo.l10n.declared[224] includes the number, 335 all caps
// it seems that the actual page uses 183 + ' ' + '{{level}}'
// Even languages with their own numerals just use arabic numerals
// although it seems that rtl languages reverse the order (not sure if that's automatic)
// 122, 142 (Languages), 147 J'apprends, 58 langue d'apprendissage
// 178 add a new course
// (checked in english and french)

var duoState = JSON.parse(localStorage['duo.state'])
var duo = window.duo // no need to use unsecureWindow, since we don't grant anything

// Language names (in english) - for use with duo.l10n.undeclared
// extracted from https://duolingo.com/api/1/courses/list
var languages = JSON.parse('{"ar": "Arabic", "bn": "Bengali", "ca": "Catalan", "cs": "Czech", "cy": "Welsh", "da": "Danish", "de": "German", "el": "Greek", "en": "English", "eo": "Esperanto", "es": "Spanish", "fr": "French", "ga": "Irish", "gn": "Guarani (Jopar\\u00e1)", "he": "Hebrew", "hi": "Hindi", "hu": "Hungarian", "id": "Indonesian", "it": "Italian", "ja": "Japanese", "ko": "Korean", "nl-NL": "Dutch", "no-BO": "Norwegian (Bokm\\u00e5l)", "pa": "Punjabi (Gurmukhi)", "pl": "Polish", "pt": "Portuguese", "ro": "Romanian", "ru": "Russian", "sv": "Swedish", "sw": "Swahili", "ta": "Tamil", "te": "Telugu", "th": "Thai", "tl": "Tagalog", "tlh": "Klingon", "tr": "Turkish", "uk": "Ukrainian", "vi": "Vietnamese", "zh-CN": "Chinese"}')

function getLanguageString(languageCode) {
    if (languageCode in languages) {
        return languages[languageCode]
    } else {
        return "Unknown"
    }
}

// language flags - extracted from https://d35aaqx5ub95lt.cloudfront.net/js/app-5dfdd9c3.js
// keys: remove flag- prefix and keep consistency - zs -> zh-CN, kl -> tlh, (zs -> zh-¿?)
// It appears that the strings are used as part of the class name.
var flags=JSON.parse('{"ar": "_1ARRD _3viv6", "bn": "_2TXAL _3viv6", "ca": "mc4rg _3viv6", "cs": "_1uPQW _3viv6", "cy": "_1jO8h _3viv6", "da": "_1h0xh _3viv6", "de": "oboa9 _3viv6", "dk": "_3AA1F _3viv6", "el": "_2tQo9 _3viv6", "en": "_2cR-E _3viv6", "eo": "pWj0w _3viv6", "es": "u5W-o _3viv6", "fr": "_2KQN3 _3viv6", "ga": "_1vhNM _3viv6", "gn": "_24xu4 _3viv6", "he": "_PDrK _3viv6", "hi": "OgUIe _3viv6", "hu": "_1S3hi _3viv6", "id": "_107sn _3viv6", "it": "_1PruQ _3viv6", "ja": "_2N-Uj _3viv6", "ko": "_2lkzc _3viv6", "nl-NL": "_1fajz _3viv6", "no-BO": "_200jU _3viv6", "pl": "_3uusw _3viv6", "pt": "pmGwL _3viv6", "ro": "_12U6e _3viv6", "ru": "_1eqxJ _3viv6", "sn": "q_PD- _3viv6", "sv": "_2DMfV _3viv6", "sw": "_3T1km _3viv6", "th": "_2oTcA _3viv6", "tl": "_1q_MQ _3viv6", "tlh": "_6mRM _3viv6", "tr": "_1tJJ2 _3viv6", "uk": "_1zZsN _3viv6", "un": "t-XH- _3viv6", "vi": "_1KtzC _3viv6", "zh": "xi6jQ _3viv6", "zh-CN": "_2gNgd _3viv6", "_circle-flag": "_2XSZu", "_flag": "_3viv6", "medium-circle-flag": "_1ct7y _2XSZu", "micro-circle-flag": "_3i5IF _2XSZu", "small-circle-flag": "_3PU7E _2XSZu"}');

function getLanguageFlag(languageCode) {
    if (languageCode in flags) {
        return flags[languageCode]
    } else {
        return flags.un // maybe one of the other flags would be appropriate, but this is what jrikhal uses.
    }
}

// Removes a css node by its css selector
function deleteElement(query) {
    node = document.querySelector(query)
    node.parentNode.removeChild(element)
}

function deleteChildren(query) {
    node = document.querySelector(query)
    while (node.hasChildNodes()) {
        node.removeChild(node.lastChild);
    }
}

// calculates the level given the experience
function xpToLevel ( xp ) {
    var xpLevelCutoffs = duoState.config.xpLevelCutoffs
    var level = 1
    var length = xpLevelCutoffs.length
    for (var i = 0; i < length; i++) {
        if (xp < xpLevelCutoffs[i]) {
            return level
        } else {
            level++
        }
    }
    return level
}

// Gets information about all currently learning courses from duo.state
function getCourseInfo() {
    var courses = duoState.courses
    var coursesOrganized = {}

    for (var course in courses) {
        if (courses.hasOwnProperty(course)) {
            var from = courses[course].fromLanguage
            var learning = courses[course].learningLanguage
            var xp = courses[course].xp
            if (!(from in coursesOrganized)) {
                coursesOrganized[from] = []
            }

            coursesOrganized[from].push({'from': from,
                                         'from_string': getLanguageString(from),
                                         'learning': learning,
                                         'learning_string': getLanguageString(learning),
                                         'xp': xp,
                                         'level': xpToLevel(xp)
                                         })
        }
    }
    return coursesOrganized
}

// Takes an argument of the form given by the output of getCourseInfo
// Sorts from languages by number of courses and learning languages by xp.
function sortCourseInfo(courses) {
    sortedLanguages = keys(courses).sort(function(a, b) { return courses[b].length - courses[a].length })
    return sortedLanguages.map(function(a) { return courses[a].sort(function(b,c) { return c.xp - b.xp } )})
}

// Use duoState to fetch current from and to languages
function getCurrentLanguages() {
    currentLanguages = {}
    currentLanguages.from = duoState.user.fromLanguage
    currentLanguages.to = duoState.user.learningLanguage
    return currentLanguages
}

// Switch source and target languages - credit to jrikhal
function switchCourse(SrcLang,TgtLang){
    //Needed due to inconsistency in Duo's language codes!!!
    if(SrcLang==='nl-NL'){SrcLang='dn';}
    if(SrcLang==='no-BO'){SrcLang='nb';}
    if(SrcLang==='zh-CN'){SrcLang='zs';}
    if(TgtLang==='nl-NL'){TgtLang='dn';}
    if(TgtLang==='no-BO'){TgtLang='nb';}
    if(TgtLang==='zh-CN'){TgtLang='zs';}

    request=new XMLHttpRequest();
    request.open("POST", "https://www.duolingo.com/api/1/me/switch_language", true)
    request.setRequestHeader('Content-type', 'application/x-www-form-urlencoded')
    request.send('learning_language='+TgtLang+'&from_language='+SrcLang)

    waitForSwitchCourseLoad(request)
}

// Reload page once the course is switched - credit to jrikhal
function waitForSwitchCourseLoad(request){
    if(request.readyState<4){
        setTimeout(function(){ waitForSwitchCourseLoad(request) }, 500)
    } else location.reload()
}

// counstructs a menu based on courses (array of arrays), other args are the current languages
function constructMenu(courses, fromLang, toLang) {
    var topMenu = document.querySelector('._20LC5')
    deleteChildren('._20LC5')

    var header = document.createElement("li")
    header.setAttribute('class', '_2PurW')
    header.innerHTML = '
' + duo.l10n.declared[142] + '
'
    topMenu.appendChild(header)

    var courseList = document.createElement('ul')
    courseList.setAttribute('class', '_1XE6M')
    topMenu.appendChild(courseList)

    var courseLength = courses.length
    for (var i=0; i<courseLength; i++) {
        var sourceLang = document.createElement('li')
        var className = '_2kNgI _1qBnH'
        if (courses[i][0].from == fromLang) { className = '_1oVFS ' + className }
        sourceLang.setAttribute('class', className)

        var flag = document.createElement('span')
        className = getLanguageFlag(courses[i][0].from) + ' _3vx2Z _1ct7y _2XSZu'
        flag.setAttribute('class', className)
        sourceLang.appendChild(flag)

        var languageName = getLanguageString(courses[i][0].from)
        var localizedLanguageName = duo.l10n.undeclared[languageName + '||"Le/L\'" + language name']
        sourceLang.appendChild(document.createTextNode(localizedLanguageName))

        var targetLangMenu = document.createElement('ul')
        targetLangMenu.setAttribute('class', '_20LC5 FUNrE _3w0_r OSaWc _2HujR _1ZY-H')

        var subheader = document.createElement("li")
        subheader.setAttribute('class', '_2PurW')
        subheader.innerHTML = '
' + duo.l10n.declared[147] + '
'
        targetLangMenu.appendChild(subheader)

        var subCourseList = document.createElement('ul')
        subCourseList.setAttribute('class', '_1XE6M')
        targetLangMenu.appendChild(subCourseList)

        var subCourseLength = courses[i].length
        for (var j=0; j<subCourseLength; j++) {
            var targetLang = document.createElement('li')
            className = '_2kNgI _1qBnH'
            if (courses[i][j].learning == toLang) {className = '_1oVFS ' + className}
            targetLang.setAttribute('class', className)

            var subFlag = document.createElement('span')
            className = getLanguageFlag(courses[i][j].learning) + ' _3vx2Z _1ct7y _2XSZu'
            subFlag.setAttribute('class', className)
            targetLang.appendChild(subFlag)

            var subLanguageName = getLanguageString(courses[i][j].learning)
            subLocalizedLanguageName = duo.l10n.undeclared[subLanguageName + '||I want to learn...']
            targetLang.appendChild(document.createTextNode(subLocalizedLanguageName))

            var levelText = document.createElement('span')
            levelText.setAttribute('class', '_1fA14')
            levelText.innerHTML = duo.l10n.declared[183] + ' ' + courses[i][j].level
            targetLang.appendChild(levelText)
        }

        topMenu.appendChild(sourceLang)
    }

    var divider = document.createElement('li')
    divider.setAttribute('class', 'qsrrc')
    topMenu.appendChild(divider)

    var mobileAddCourse = document.createElement('li')
    mobileAddCourse.setAttribute('class', '_2nd4- _1qBnH')

    var mobileLink = document.createElement('a')
    mobileLink.setAttribute('class', 'jsHOk')
    mobileLink.setAttribute('href', '/courses')
    mobileLink.innerText = '+'
    mobileAddCourse.appendChild(mobileLink)

    mobileAddCourse.appendChild(document.createTextNode(duo.l10n.declared[179]))
    topMenu.appendChild(mobileAddCourse)

    var addNewCourse = document.createElement('li')
    addNewCourse.setAttribute('class', '_2uBp_ _1qBnH')
    addNewCourse.innerText = duo.l10n.declared[178]
    topMenu.appendChild(addNewCourse)

        // add item for from language (flag, language name)
        // current has gray background
        // add on (mouseover) (maybe after adding submenu)
        // add submenu
            // set header (J'apprends)
            // loop over sub courses
                // add item for learning language (flag, language name, level)
                // current has grey background
                // include fromLang, toLang as attributes ?
                // add onClick
    // add Ajouter un nouveau cours

}
<|endoftext|>


<commit_before>import { getRect, prepend, removeChild } from '../util/dom'
import { ease } from '../util/ease'
import { extend } from '../util/lang'
import { warn } from '../util/debug'

export function snapMixin(BScroll) {
  BScroll.prototype._initSnap = function () {
    this.currentPage = {}
    const snap = this.options.snap

    if (snap.loop) {
      let children = this.scroller.children
      if (children.length > 1) {
        prepend(children[children.length - 1].cloneNode(true), this.scroller)
        this.scroller.appendChild(children[1].cloneNode(true))
      } else {
        // Loop does not make any sense if there is only one child.
        snap.loop = false
      }
    }

    let el = snap.el
    if (typeof el === 'string') {
      el = this.scroller.querySelectorAll(el)
    }

    this.on('refresh', () => {
      this.pages = []

      if (!this.wrapperWidth || !this.wrapperHeight || !this.scrollerWidth || !this.scrollerHeight) {
        return
      }

      let stepX = snap.stepX || this.wrapperWidth
      let stepY = snap.stepY || this.wrapperHeight

      let x = 0
      let y
      let cx
      let cy
      let i = 0
      let l
      let m = 0
      let n
      let rect
      if (!el) {
        cx = Math.round(stepX / 2)
        cy = Math.round(stepY / 2)

        while (x > -this.scrollerWidth) {
          this.pages[i] = []
          l = 0
          y = 0

          while (y > -this.scrollerHeight) {
            this.pages[i][l] = {
              x: Math.max(x, this.maxScrollX),
              y: Math.max(y, this.maxScrollY),
              width: stepX,
              height: stepY,
              cx: x - cx,
              cy: y - cy
            }

            y -= stepY
            l++
          }

          x -= stepX
          i++
        }
      } else {
        l = el.length
        n = -1

        for (; i < l; i++) {
          rect = getRect(el[i])
          if (i === 0 || rect.left <= getRect(el[i - 1]).left) {
            m = 0
            n++
          }

          if (!this.pages[m]) {
            this.pages[m] = []
          }

          x = Math.max(-rect.left, this.maxScrollX)
          y = Math.max(-rect.top, this.maxScrollY)
          cx = x - Math.round(rect.width / 2)
          cy = y - Math.round(rect.height / 2)

          this.pages[m][n] = {
            x: x,
            y: y,
            width: rect.width,
            height: rect.height,
            cx: cx,
            cy: cy
          }

          if (x > this.maxScrollX) {
            m++
          }
        }
      }

      this._checkSnapLoop()

      let initPageX = snap._loopX ? 1 : 0
      let initPageY = snap._loopY ? 1 : 0
      this._goToPage(this.currentPage.pageX || initPageX, this.currentPage.pageY || initPageY, 0)

      // Update snap threshold if needed.
      const snapThreshold = snap.threshold
      if (snapThreshold % 1 === 0) {
        this.snapThresholdX = snapThreshold
        this.snapThresholdY = snapThreshold
      } else {
        this.snapThresholdX = Math.round(this.pages[this.currentPage.pageX][this.currentPage.pageY].width * snapThreshold)
        this.snapThresholdY = Math.round(this.pages[this.currentPage.pageX][this.currentPage.pageY].height * snapThreshold)
      }
    })

    this.on('scrollEnd', () => {
      if (snap.loop) {
        if (snap._loopX) {
          if (this.currentPage.pageX === 0) {
            this._goToPage(this.pages.length - 2, this.currentPage.pageY, 1)
          }
          if (this.currentPage.pageX === this.pages.length - 1) {
            this._goToPage(1, this.currentPage.pageY, 1)
          }
        } else {
          if (this.currentPage.pageY === 0) {
            this._goToPage(this.currentPage.pageX, this.pages[0].length - 2, 1)
          }
          if (this.currentPage.pageY === this.pages[0].length - 1) {
            this._goToPage(this.currentPage.pageX, 1, 1)
          }
        }
      }
    })

    if (snap.listenFlick !== false) {
      this.on('flick', () => {
        let time = snap.speed || Math.max(
            Math.max(
              Math.min(Math.abs(this.x - this.startX), 1000),
              Math.min(Math.abs(this.y - this.startY), 1000)
            ), 300)

        this._goToPage(
          this.currentPage.pageX + this.directionX,
          this.currentPage.pageY + this.directionY,
          time
        )
      })
    }

    this.on('destroy', () => {
      if (snap.loop) {
        let children = this.scroller.children
        if (children.length > 2) {
          removeChild(this.scroller, children[children.length - 1])
          removeChild(this.scroller, children[0])
        }
      }
    })
  }

  BScroll.prototype._checkSnapLoop = function () {
    const snap = this.options.snap

    if (!snap.loop || !this.pages || !this.pages.length) {
      return
    }

    if (this.pages.length > 1) {
      snap._loopX = true
    }
    if (this.pages[0] && this.pages[0].length > 1) {
      snap._loopY = true
    }
    if (snap._loopX && snap._loopY) {
      warn('Loop does not support two direction at the same time.')
    }
  }

  BScroll.prototype._nearestSnap = function (x, y) {
    if (!this.pages.length) {
      return {x: 0, y: 0, pageX: 0, pageY: 0}
    }

    let i = 0
    // Check if we exceeded the snap threshold
    if (Math.abs(x - this.absStartX) <= this.snapThresholdX &&
      Math.abs(y - this.absStartY) <= this.snapThresholdY) {
      return this.currentPage
    }

    if (x > this.minScrollX) {
      x = this.minScrollX
    } else if (x < this.maxScrollX) {
      x = this.maxScrollX
    }

    if (y > this.minScrollY) {
      y = this.minScrollY
    } else if (y < this.maxScrollY) {
      y = this.maxScrollY
    }

    let l = this.pages.length
    for (; i < l; i++) {
      if (x >= this.pages[i][0].cx) {
        x = this.pages[i][0].x
        break
      }
    }

    l = this.pages[i].length

    let m = 0
    for (; m < l; m++) {
      if (y >= this.pages[0][m].cy) {
        y = this.pages[0][m].y
        break
      }
    }

    if (i === this.currentPage.pageX) {
      i += this.directionX

      if (i < 0) {
        i = 0
      } else if (i >= this.pages.length) {
        i = this.pages.length - 1
      }

      x = this.pages[i][0].x
    }

    if (m === this.currentPage.pageY) {
      m += this.directionY

      if (m < 0) {
        m = 0
      } else if (m >= this.pages[0].length) {
        m = this.pages[0].length - 1
      }

      y = this.pages[0][m].y
    }

    return {
      x,
      y,
      pageX: i,
      pageY: m
    }
  }

  BScroll.prototype._goToPage = function (x, y = 0, time, easing) {
    const snap = this.options.snap

    if (!snap || !this.pages || !this.pages.length) {
      return
    }

    easing = easing || snap.easing || ease.bounce

    if (x >= this.pages.length) {
      x = this.pages.length - 1
    } else if (x < 0) {
      x = 0
    }

    if (!this.pages[x]) {
      return
    }

    if (y >= this.pages[x].length) {
      y = this.pages[x].length - 1
    } else if (y < 0) {
      y = 0
    }

    let posX = this.pages[x][y].x
    let posY = this.pages[x][y].y

    time = time === undefined ? snap.speed || Math.max(
        Math.max(
          Math.min(Math.abs(posX - this.x), 1000),
          Math.min(Math.abs(posY - this.y), 1000)
        ), 300) : time

    this.currentPage = {
      x: posX,
      y: posY,
      pageX: x,
      pageY: y
    }
    this.scrollTo(posX, posY, time, easing)
  }

  BScroll.prototype.goToPage = function (x, y, time, easing) {
    const snap = this.options.snap
    if (!snap || !this.pages || !this.pages.length) {
      return
    }

    if (snap.loop) {
      let len
      if (snap._loopX) {
        len = this.pages.length - 2
        if (x >= len) {
          x = len - 1
        } else if (x < 0) {
          x = 0
        }
        x += 1
      } else {
        len = this.pages[0].length - 2
        if (y >= len) {
          y = len - 1
        } else if (y < 0) {
          y = 0
        }
        y += 1
      }
    }
    this._goToPage(x, y, time, easing)
  }

  BScroll.prototype.next = function (time, easing) {
    const snap = this.options.snap
    if (!snap) {
      return
    }

    let x = this.currentPage.pageX
    let y = this.currentPage.pageY

    x++
    if (x >= this.pages.length && this.hasVerticalScroll) {
      x = 0
      y++
    }

    this._goToPage(x, y, time, easing)
  }

  BScroll.prototype.prev = function (time, easing) {
    const snap = this.options.snap
    if (!snap) {
      return
    }

    let x = this.currentPage.pageX
    let y = this.currentPage.pageY

    x--
    if (x < 0 && this.hasVerticalScroll) {
      x = 0
      y--
    }

    this._goToPage(x, y, time, easing)
  }

  BScroll.prototype.getCurrentPage = function () {
    const snap = this.options.snap
    if (!snap) {
      return null
    }

    if (snap.loop) {
      let currentPage
      if (snap._loopX) {
        currentPage = extend({}, this.currentPage, {
          pageX: this.currentPage.pageX - 1
        })
      } else {
        currentPage = extend({}, this.currentPage, {
          pageY: this.currentPage.pageY - 1
        })
      }
      return currentPage
    }
    return this.currentPage
  }
}
<commit_msg>set zero delay when use gotopage<commit_after>import { getRect, prepend, removeChild } from '../util/dom'
import { ease } from '../util/ease'
import { extend } from '../util/lang'
import { warn } from '../util/debug'

export function snapMixin(BScroll) {
  BScroll.prototype._initSnap = function () {
    this.currentPage = {}
    const snap = this.options.snap

    if (snap.loop) {
      let children = this.scroller.children
      if (children.length > 1) {
        prepend(children[children.length - 1].cloneNode(true), this.scroller)
        this.scroller.appendChild(children[1].cloneNode(true))
      } else {
        // Loop does not make any sense if there is only one child.
        snap.loop = false
      }
    }

    let el = snap.el
    if (typeof el === 'string') {
      el = this.scroller.querySelectorAll(el)
    }

    this.on('refresh', () => {
      this.pages = []

      if (!this.wrapperWidth || !this.wrapperHeight || !this.scrollerWidth || !this.scrollerHeight) {
        return
      }

      let stepX = snap.stepX || this.wrapperWidth
      let stepY = snap.stepY || this.wrapperHeight

      let x = 0
      let y
      let cx
      let cy
      let i = 0
      let l
      let m = 0
      let n
      let rect
      if (!el) {
        cx = Math.round(stepX / 2)
        cy = Math.round(stepY / 2)

        while (x > -this.scrollerWidth) {
          this.pages[i] = []
          l = 0
          y = 0

          while (y > -this.scrollerHeight) {
            this.pages[i][l] = {
              x: Math.max(x, this.maxScrollX),
              y: Math.max(y, this.maxScrollY),
              width: stepX,
              height: stepY,
              cx: x - cx,
              cy: y - cy
            }

            y -= stepY
            l++
          }

          x -= stepX
          i++
        }
      } else {
        l = el.length
        n = -1

        for (; i < l; i++) {
          rect = getRect(el[i])
          if (i === 0 || rect.left <= getRect(el[i - 1]).left) {
            m = 0
            n++
          }

          if (!this.pages[m]) {
            this.pages[m] = []
          }

          x = Math.max(-rect.left, this.maxScrollX)
          y = Math.max(-rect.top, this.maxScrollY)
          cx = x - Math.round(rect.width / 2)
          cy = y - Math.round(rect.height / 2)

          this.pages[m][n] = {
            x: x,
            y: y,
            width: rect.width,
            height: rect.height,
            cx: cx,
            cy: cy
          }

          if (x > this.maxScrollX) {
            m++
          }
        }
      }

      this._checkSnapLoop()

      let initPageX = snap._loopX ? 1 : 0
      let initPageY = snap._loopY ? 1 : 0
      this._goToPage(this.currentPage.pageX || initPageX, this.currentPage.pageY || initPageY, 0)

      // Update snap threshold if needed.
      const snapThreshold = snap.threshold
      if (snapThreshold % 1 === 0) {
        this.snapThresholdX = snapThreshold
        this.snapThresholdY = snapThreshold
      } else {
        this.snapThresholdX = Math.round(this.pages[this.currentPage.pageX][this.currentPage.pageY].width * snapThreshold)
        this.snapThresholdY = Math.round(this.pages[this.currentPage.pageX][this.currentPage.pageY].height * snapThreshold)
      }
    })

    this.on('scrollEnd', () => {
      if (snap.loop) {
        if (snap._loopX) {
          if (this.currentPage.pageX === 0) {
            this._goToPage(this.pages.length - 2, this.currentPage.pageY, 0)
          }
          if (this.currentPage.pageX === this.pages.length - 1) {
            this._goToPage(1, this.currentPage.pageY, 0)
          }
        } else {
          if (this.currentPage.pageY === 0) {
            this._goToPage(this.currentPage.pageX, this.pages[0].length - 2, 0)
          }
          if (this.currentPage.pageY === this.pages[0].length - 1) {
            this._goToPage(this.currentPage.pageX, 1, 0)
          }
        }
      }
    })

    if (snap.listenFlick !== false) {
      this.on('flick', () => {
        let time = snap.speed || Math.max(
            Math.max(
              Math.min(Math.abs(this.x - this.startX), 1000),
              Math.min(Math.abs(this.y - this.startY), 1000)
            ), 300)

        this._goToPage(
          this.currentPage.pageX + this.directionX,
          this.currentPage.pageY + this.directionY,
          time
        )
      })
    }

    this.on('destroy', () => {
      if (snap.loop) {
        let children = this.scroller.children
        if (children.length > 2) {
          removeChild(this.scroller, children[children.length - 1])
          removeChild(this.scroller, children[0])
        }
      }
    })
  }

  BScroll.prototype._checkSnapLoop = function () {
    const snap = this.options.snap

    if (!snap.loop || !this.pages || !this.pages.length) {
      return
    }

    if (this.pages.length > 1) {
      snap._loopX = true
    }
    if (this.pages[0] && this.pages[0].length > 1) {
      snap._loopY = true
    }
    if (snap._loopX && snap._loopY) {
      warn('Loop does not support two direction at the same time.')
    }
  }

  BScroll.prototype._nearestSnap = function (x, y) {
    if (!this.pages.length) {
      return {x: 0, y: 0, pageX: 0, pageY: 0}
    }

    let i = 0
    // Check if we exceeded the snap threshold
    if (Math.abs(x - this.absStartX) <= this.snapThresholdX &&
      Math.abs(y - this.absStartY) <= this.snapThresholdY) {
      return this.currentPage
    }

    if (x > this.minScrollX) {
      x = this.minScrollX
    } else if (x < this.maxScrollX) {
      x = this.maxScrollX
    }

    if (y > this.minScrollY) {
      y = this.minScrollY
    } else if (y < this.maxScrollY) {
      y = this.maxScrollY
    }

    let l = this.pages.length
    for (; i < l; i++) {
      if (x >= this.pages[i][0].cx) {
        x = this.pages[i][0].x
        break
      }
    }

    l = this.pages[i].length

    let m = 0
    for (; m < l; m++) {
      if (y >= this.pages[0][m].cy) {
        y = this.pages[0][m].y
        break
      }
    }

    if (i === this.currentPage.pageX) {
      i += this.directionX

      if (i < 0) {
        i = 0
      } else if (i >= this.pages.length) {
        i = this.pages.length - 1
      }

      x = this.pages[i][0].x
    }

    if (m === this.currentPage.pageY) {
      m += this.directionY

      if (m < 0) {
        m = 0
      } else if (m >= this.pages[0].length) {
        m = this.pages[0].length - 1
      }

      y = this.pages[0][m].y
    }

    return {
      x,
      y,
      pageX: i,
      pageY: m
    }
  }

  BScroll.prototype._goToPage = function (x, y = 0, time, easing) {
    const snap = this.options.snap

    if (!snap || !this.pages || !this.pages.length) {
      return
    }

    easing = easing || snap.easing || ease.bounce

    if (x >= this.pages.length) {
      x = this.pages.length - 1
    } else if (x < 0) {
      x = 0
    }

    if (!this.pages[x]) {
      return
    }

    if (y >= this.pages[x].length) {
      y = this.pages[x].length - 1
    } else if (y < 0) {
      y = 0
    }

    let posX = this.pages[x][y].x
    let posY = this.pages[x][y].y

    time = time === undefined ? snap.speed || Math.max(
        Math.max(
          Math.min(Math.abs(posX - this.x), 1000),
          Math.min(Math.abs(posY - this.y), 1000)
        ), 300) : time

    this.currentPage = {
      x: posX,
      y: posY,
      pageX: x,
      pageY: y
    }
    this.scrollTo(posX, posY, time, easing)
  }

  BScroll.prototype.goToPage = function (x, y, time, easing) {
    const snap = this.options.snap
    if (!snap || !this.pages || !this.pages.length) {
      return
    }

    if (snap.loop) {
      let len
      if (snap._loopX) {
        len = this.pages.length - 2
        if (x >= len) {
          x = len - 1
        } else if (x < 0) {
          x = 0
        }
        x += 1
      } else {
        len = this.pages[0].length - 2
        if (y >= len) {
          y = len - 1
        } else if (y < 0) {
          y = 0
        }
        y += 1
      }
    }
    this._goToPage(x, y, time, easing)
  }

  BScroll.prototype.next = function (time, easing) {
    const snap = this.options.snap
    if (!snap) {
      return
    }

    let x = this.currentPage.pageX
    let y = this.currentPage.pageY

    x++
    if (x >= this.pages.length && this.hasVerticalScroll) {
      x = 0
      y++
    }

    this._goToPage(x, y, time, easing)
  }

  BScroll.prototype.prev = function (time, easing) {
    const snap = this.options.snap
    if (!snap) {
      return
    }

    let x = this.currentPage.pageX
    let y = this.currentPage.pageY

    x--
    if (x < 0 && this.hasVerticalScroll) {
      x = 0
      y--
    }

    this._goToPage(x, y, time, easing)
  }

  BScroll.prototype.getCurrentPage = function () {
    const snap = this.options.snap
    if (!snap) {
      return null
    }

    if (snap.loop) {
      let currentPage
      if (snap._loopX) {
        currentPage = extend({}, this.currentPage, {
          pageX: this.currentPage.pageX - 1
        })
      } else {
        currentPage = extend({}, this.currentPage, {
          pageY: this.currentPage.pageY - 1
        })
      }
      return currentPage
    }
    return this.currentPage
  }
}
<|endoftext|>


<commit_before>alert(1)

<commit_msg>test2<commit_after>alert(1)

<|endoftext|>


<commit_before>Shade.array = function(v)
{
    var t = facet_typeOf(v);
    if (t === 'array') {
        var new_v = v.map(Shade.make);
        var array_size = new_v.length;
        if (array_size === 0) {
            throw "array constant must be non-empty";
        }

        var new_types = new_v.map(function(t) { return t.type; });
        var array_type = Shade.Types.array(new_types[0], array_size);
        if (_.any(new_types, function(t) { return !t.equals(new_types[0]); })) {
            throw "array elements must have identical types";
        }
        // if (_.any(new_v, function(el) { return !el.is_constant(); })) {
        //     throw "constant array elements must be constant as well";
        // }
        return Shade._create_concrete_exp( {
            parents: new_v,
            type: array_type,
            expression_type: "constant",
            evaluate: function() { return this.glsl_name; },
            compile: function (ctx) {
                this.array_initializer_glsl_name = ctx.request_fresh_glsl_name();
                ctx.strings.push(this.type.declare(this.glsl_name), ";\n");
                ctx.strings.push("void", this.array_initializer_glsl_name, "(void) {\n");
                for (var i=0; i<this.parents.length; ++i) {
                    ctx.strings.push("    ", this.glsl_name, "[", i, "] =",
                                     this.parents[i].evaluate(), ";\n");
                }
                ctx.strings.push("}\n");
                ctx.add_initialization(this.array_initializer_glsl_name + "()");
            },
            is_constant: function() { return false; },
            element: function(i) {
                return this.parents[i];
            },
            element_is_constant: function(i) {
                return this.parents[i].is_constant();
            },
            element_constant_value: function(i) {
                return this.parents[i].constant_value();
            }
        });
    } else {
        throw "type error: need array";
    }
};
<commit_msg>comments<commit_after>
// Shade.array denotes an array of Facet values of the same type:
//    Shade.array([2, 3, 4, 5, 6]);

Shade.array = function(v)
{
    var t = facet_typeOf(v);
    if (t === 'array') {
        var new_v = v.map(Shade.make);
        var array_size = new_v.length;
        if (array_size === 0) {
            throw "array constant must be non-empty";
        }

        var new_types = new_v.map(function(t) { return t.type; });
        var array_type = Shade.Types.array(new_types[0], array_size);
        if (_.any(new_types, function(t) { return !t.equals(new_types[0]); })) {
            throw "array elements must have identical types";
        }
        // if (_.any(new_v, function(el) { return !el.is_constant(); })) {
        //     throw "constant array elements must be constant as well";
        // }
        return Shade._create_concrete_exp( {
            parents: new_v,
            type: array_type,
            expression_type: "constant",
            evaluate: function() { return this.glsl_name; },
            compile: function (ctx) {
                this.array_initializer_glsl_name = ctx.request_fresh_glsl_name();
                ctx.strings.push(this.type.declare(this.glsl_name), ";\n");
                ctx.strings.push("void", this.array_initializer_glsl_name, "(void) {\n");
                for (var i=0; i<this.parents.length; ++i) {
                    ctx.strings.push("    ", this.glsl_name, "[", i, "] =",
                                     this.parents[i].evaluate(), ";\n");
                }
                ctx.strings.push("}\n");
                ctx.add_initialization(this.array_initializer_glsl_name + "()");
            },
            is_constant: function() { return false; },
            element: function(i) {
                return this.parents[i];
            },
            element_is_constant: function(i) {
                return this.parents[i].is_constant();
            },
            element_constant_value: function(i) {
                return this.parents[i].constant_value();
            }
        });
    } else {
        throw "type error: need array";
    }
};
<|endoftext|>


<commit_before>function deepMerge(a, b) {
  Object.keys(b).forEach(function (key) {
    if (['fields', 'args'].indexOf(key) !== -1) return;

    if (a[key] && b[key] && typeof a[key] === 'object' && typeof b[key] === 'object') {
      a[key] = deepMerge(a[key], b[key]);
    } else {
      a[key] = b[key];
    }
  });

  if (a.fields && b.fields) {
    a.fields = deepMerge(a.fields, b.fields);
  } else if (a.fields || b.fields) {
    a.fields = a.fields || b.fields;
  }

  return a;
}

function hasFragments(info) {
  return info.fragments && Object.keys(info.fragments).length > 0;
}

function isFragment(info, ast) {
  return hasFragments(info) && info.fragments[ast.name.value] && ast.kind !== 'FragmentDefinition';
}

function simplifyObjectValue(objectValue) {
  return objectValue.fields.reduce((memo, field) => {
    memo[field.name.value] =
      field.value.kind === 'IntValue' ? parseInt( field.value.value, 10 ) :
      field.value.kind === 'FloatValue' ? parseFloat( field.value.value ) :
      field.value.kind === 'ObjectValue' ? simplifyObjectValue( field.value ) :
        field.value.value;

    return memo;
  }, {});
}

function simplifyValue(value, info) {
  if (value.values) {
    return value.values.map(value => simplifyValue(value, info));
  }
  if ('value' in value) {
    return value.value;
  }
  if (value.kind === 'ObjectValue') {
    return simplifyObjectValue(value);
  }
  if (value.name && info.variableValues) {
    return info.variableValues[value.name.value];
  }
}

module.exports = function simplifyAST(ast, info, parent) {
  var selections;
  info = info || {};

  if (ast.selectionSet) selections = ast.selectionSet.selections;
  if (Array.isArray(ast)) {
    let simpleAST = {};
    ast.forEach(ast => {
      simpleAST = deepMerge(
        simpleAST, simplifyAST(ast, info)
      );
    });

    return simpleAST;
  }

  if (isFragment(info, ast)) {
    return simplifyAST(info.fragments[ast.name.value], info);
  }

  if (!selections) return {
    fields: {},
    args: {}
  };

  return selections.reduce(function (simpleAST, selection) {
    if (selection.kind === 'FragmentSpread' || selection.kind === 'InlineFragment') {
      simpleAST = deepMerge(
        simpleAST, simplifyAST(selection, info)
      );
      return simpleAST;
    }

    var name = selection.name.value
      , alias = selection.alias && selection.alias.value
      , key = alias || name;

    simpleAST.fields[key] = simpleAST.fields[key] || {};
    simpleAST.fields[key] = deepMerge(
      simpleAST.fields[key], simplifyAST(selection, info, simpleAST.fields[key])
    );

    if (alias) {
      simpleAST.fields[key].key = name;
    }

    simpleAST.fields[key].args = selection.arguments.reduce(function (args, arg) {
      args[arg.name.value] = simplifyValue(arg.value, info);
      return args;
    }, {});

    if (parent) {
      Object.defineProperty(simpleAST.fields[key], '$parent', { value: parent, enumerable: false });
    }

    return simpleAST;
  }, {
    fields: {},
    args: {}
  });
};
<commit_msg>Fix: Guard against missing names in AST (#302)<commit_after>function deepMerge(a, b) {
  Object.keys(b).forEach(function (key) {
    if (['fields', 'args'].indexOf(key) !== -1) return;

    if (a[key] && b[key] && typeof a[key] === 'object' && typeof b[key] === 'object') {
      a[key] = deepMerge(a[key], b[key]);
    } else {
      a[key] = b[key];
    }
  });

  if (a.fields && b.fields) {
    a.fields = deepMerge(a.fields, b.fields);
  } else if (a.fields || b.fields) {
    a.fields = a.fields || b.fields;
  }

  return a;
}

function hasFragments(info) {
  return info.fragments && Object.keys(info.fragments).length > 0;
}

function isFragment(info, ast) {
  return hasFragments(info) &&
    ast.name &&
    info.fragments[ast.name.value] &&
    ast.kind !== 'FragmentDefinition';
}

function simplifyObjectValue(objectValue) {
  return objectValue.fields.reduce((memo, field) => {
    memo[field.name.value] =
      field.value.kind === 'IntValue' ? parseInt( field.value.value, 10 ) :
      field.value.kind === 'FloatValue' ? parseFloat( field.value.value ) :
      field.value.kind === 'ObjectValue' ? simplifyObjectValue( field.value ) :
        field.value.value;

    return memo;
  }, {});
}

function simplifyValue(value, info) {
  if (value.values) {
    return value.values.map(value => simplifyValue(value, info));
  }
  if ('value' in value) {
    return value.value;
  }
  if (value.kind === 'ObjectValue') {
    return simplifyObjectValue(value);
  }
  if (value.name && info.variableValues) {
    return info.variableValues[value.name.value];
  }
}

module.exports = function simplifyAST(ast, info, parent) {
  var selections;
  info = info || {};

  if (ast.selectionSet) selections = ast.selectionSet.selections;
  if (Array.isArray(ast)) {
    let simpleAST = {};
    ast.forEach(ast => {
      simpleAST = deepMerge(
        simpleAST, simplifyAST(ast, info)
      );
    });

    return simpleAST;
  }

  if (isFragment(info, ast)) {
    return simplifyAST(info.fragments[ast.name.value], info);
  }

  if (!selections) return {
    fields: {},
    args: {}
  };

  return selections.reduce(function (simpleAST, selection) {
    if (selection.kind === 'FragmentSpread' || selection.kind === 'InlineFragment') {
      simpleAST = deepMerge(
        simpleAST, simplifyAST(selection, info)
      );
      return simpleAST;
    }

    var name = selection.name.value
      , alias = selection.alias && selection.alias.value
      , key = alias || name;

    simpleAST.fields[key] = simpleAST.fields[key] || {};
    simpleAST.fields[key] = deepMerge(
      simpleAST.fields[key], simplifyAST(selection, info, simpleAST.fields[key])
    );

    if (alias) {
      simpleAST.fields[key].key = name;
    }

    simpleAST.fields[key].args = selection.arguments.reduce(function (args, arg) {
      args[arg.name.value] = simplifyValue(arg.value, info);
      return args;
    }, {});

    if (parent) {
      Object.defineProperty(simpleAST.fields[key], '$parent', { value: parent, enumerable: false });
    }

    return simpleAST;
  }, {
    fields: {},
    args: {}
  });
};
<|endoftext|>