Skip to content

Latest commit

 

History

History
452 lines (394 loc) · 20.6 KB

welcome.litcoffee

File metadata and controls

452 lines (394 loc) · 20.6 KB

Welcome to the welcome file.

This script runs on the welcome page, which welcomes new users, to make it more welcoming. If you haven't already, I welcome you to view the more important main script.

Also, if you haven't noticed yet, I'm trying my best to use the word welcome as many times as I can just to welcome you.

First off, the buttons big, green, welcoming buttons on the bottom of the welcome page are assigned event listeners so that they can make the page show more welcoming information.

for nextButton in document.getElementsByClassName "next"
  nextButton.addEventListener "click", advance = ->
    # The box holding the individual pages that ge scrolled
    # when pressing the "next" button is assigned to a varialbe.
    container = document.body
    # show the next page
    view = +container.getAttribute("data-view")
    window.scrollTo(0,0) # Scoll to top of the page
    history.pushState {page: view+1}, "", "?page=#{view}" # Make the broswer register a page shift
    (npage = document.querySelector("section:nth-child(#{view+2})")).style.display = "inline-block"
    npage.style.transform = npage.style.webkitTransform = npage.style.MozTransform = "translateX(#{view*100}%)"
    # increase the data-view attribute by 1. The rest is handled by the css.
    container.setAttribute "data-view", view+1
    setTimeout ->
      #After animating is done, don't display the first page
      npage.style.transform = npage.style.webkitTransform = npage.style.MozTransform = "translateX(#{view+1}00%)"
      document.querySelector("section:nth-child(#{view+1})").style.display = "none"
    , 50
    return

Additionally, the active class needs to be added when text fields are selected (for the login box) [copied from main script].

for input in document.querySelectorAll "input[type=text], input[type=password], input[type=email], input[type=url], input[type=tel], input[type=number], input[type=search]"
  input.addEventListener "change", (evt) ->
    evt.target.parentNode.querySelector("label").classList.add "active"
  input.addEventListener "focus", (evt) ->
    evt.target.parentNode.querySelector("label").classList.add "active"
  input.addEventListener "blur", (evt) ->
    if evt.target.value.length is 0
      evt.target.parentNode.querySelector("label").classList.remove "active"

An event listener is attached to the window so that when the back button is pressed, a more welcoming page is displayed. Most of the code is the same from next button event listener, except that the page is switched the previous one, not the next one.

window.onpopstate = (event) ->
  container = document.body
  view = if event.state? then event.state.page else 0
  window.scrollTo(0,0) # Scoll to top of the page
  (npage = document.querySelector("section:nth-child(#{view+1})")).style.display = "inline-block"
  npage.style.transform = npage.style.webkitTransform = npage.style.MozTransform = "translateX(#{view*100}%)"
  # increase the data-view attribute by 1. The rest is handled by the css.
  container.setAttribute "data-view", view
  setTimeout ->
    #After animating is done, don't display the first page
    npage.style.transform = npage.style.webkitTransform = npage.style.MozTransform = "translateX(#{view}00%)"
    document.querySelector("section:nth-child(#{view+2})").style.display = "none"
  , 50
  return

When there is an error with parsing the data for Athena, a welcoming warning needs to be displayed (and the data needs to be saved too).

This code below is copied from the main script with two unwelcome lines commented out.

parseAthenaData = (dat) ->
  if dat is ""
    athenaData = null
    localStorage.removeItem "athenaData"
  else
    try
      d = JSON.parse dat
      athenaData2 = {}
      for course,n in d.body.courses.courses
        courseDetails = d.body.courses.sections[n]
        athenaData2[course.course_title] =
          link: "https://athena.harker.org"+courseDetails.link
          logo: courseDetails.logo.substr(0, courseDetails.logo.indexOf("\" alt=\"")).replace("<div class=\"profile-picture\"><img src=\"", "").replace("tiny", "reg")
          period: courseDetails.section_title
      athenaData = athenaData2
      localStorage["athenaData"] = JSON.stringify athenaData
      document.getElementById("athenaDataError").style.display = "none"
      #document.getElementById("athenaDataRefresh").style.display = "block"
      #display()
    catch e
      document.getElementById("athenaDataError").style.display = "block"
      #document.getElementById("athenaDataRefresh").style.display = "none"
      document.getElementById("athenaDataError").innerHTML = e.message
  return

The text box also needs to execute this function when anything is typed / pasted.

document.getElementById("athenaData").addEventListener "input", (evt) ->
  parseAthenaData evt.target.value

To avoid some unwelcoming errors, some constants are defined.

loginURL = ""
loginHeaders = {}
viewData = {} # The data to send when switching PCR views

The following code is copied from the main script and slightly modified since there is no login dialog.

send = (url, respType, headers, data, progress=false) ->
  return new Promise (resolve, reject) ->
    req = new XMLHttpRequest()
    req.open (if data? then "POST" else "GET"), url, true

    progressElement = document.getElementById "progress"
    progressInner = progressElement.querySelector "div"
    if progress
      progressElement.offsetWidth # Wait for it to render
      progressElement.classList.add "active"
      if progressInner.classList.contains "determinate"
        progressInner.classList.remove "determinate"
        progressInner.classList.add "indeterminate"

    load = localStorage["load"] or 170000

    req.onload = (evt) ->
      localStorage["load"] = evt.loaded
      progressElement.classList.remove "active" if progress
      if req.status is 200
        resolve req
      else
        reject Error req.statusText
      return

    req.onerror = ->
      progressElement.classList.remove "active" if progress
      reject Error "Network Error"
      return
    if progress
      req.onprogress = (evt) ->
        if progressInner.classList.contains "indeterminate"
          progressInner.classList.remove "indeterminate"
          progressInner.classList.add "determinate"
        progressInner.style.width = 100 * evt.loaded / (if evt.lengthComputable then evt.total else load) + "%"

    if respType?
      req.responseType = respType;
    if headers?
      for headername, header of headers
        req.setRequestHeader headername, header
    if data?
      req.send data
    else
      req.send()
    return

getCookie = (cname) ->
  name = cname + "="
  ca = document.cookie.split(";")
  i = 0
  while i < ca.length
    c = ca[i]
    while c.charAt(0) is " "
      c = c.substring(1)
    if c.indexOf(name) isnt -1
      return c.substring(name.length, c.length)
    i++
  "" # Blank if cookie not found

setCookie = (cname, cvalue, exdays) ->
  d = new Date
  d.setTime d.getTime() + exdays * 24 * 60 * 60 * 1000
  expires = "expires=" + d.toUTCString()
  document.cookie = cname + "=" + cvalue + "; " + expires
  return

snackbar = (message, action, f) ->
  snack = element "div", "snackbar"
  snackInner = element "div", "snackInner", message
  snack.appendChild snackInner
  if action? and f?
    actionE = element "a", [], action
    actionE.addEventListener "click", ->
        snack.classList.remove "active"
        f()
    snackInner.appendChild actionE

  add = ->
    document.body.appendChild snack
    snack.offsetHeight
    snack.classList.add "active"
    setTimeout ->
      snack.classList.remove "active"
      setTimeout ->
        snack.remove()
      , 900
    , 5000

  existing = document.querySelector(".snackbar")
  if existing?
    existing.classList.remove "active"
    setTimeout add, 300
  else
    add()
  return

element = (tag, cls, html, id) ->
  e = document.createElement tag
  if typeof cls is "string"
    e.classList.add cls
  else
    for c in cls
      e.classList.add c
  if html?
    e.innerHTML = html
  if id?
    e.setAttribute "id", id
  e

dologin = (val, submitEvt=false) ->
  document.getElementById("login").classList.remove "active"
  # setTimeout ->
  #   document.getElementById("loginBackground").style.display = "none"
  # , 350
  postArray = [] # Array of data to post
  localStorage["username"] = if val? and not submitEvt then val[0] else document.getElementById("username").value
  # updateAvatar()
  for h of loginHeaders # Loop through the input elements contained in the login page. As mentioned before, they will be sent to PCR to log in.
    if h.toLowerCase().indexOf("user") isnt -1
      loginHeaders[h] = if val? and not submitEvt then val[0] else document.getElementById("username").value
    if h.toLowerCase().indexOf("pass") isnt -1
      loginHeaders[h] = if val? and not submitEvt then val[1] else document.getElementById("password").value
    postArray.push encodeURIComponent(h) + "=" + encodeURIComponent(loginHeaders[h])

  # Now send the login request to PCR
  if location.protocol is "chrome-extension:"
    console.time "Logging in"
    send loginURL, "document", { "Content-type": "application/x-www-form-urlencoded" }, postArray.join("&"), true
      .then (resp) ->
        console.timeEnd "Logging in"
        if resp.responseURL.indexOf("Login") isnt -1
          # If PCR still wants us to log in, then the username or password enterred were incorrect.
          document.getElementById("loginIncorrect").style.display = "block"
          document.getElementById("password").value = ""

          #document.getElementById("login").classList.add "active"
          #document.getElementById("loginBackground").style.display = "block"
        else
          # Otherwise, we are logged in
          if document.getElementById("remember").checked #Is the "remember me" checkbox checked?
            setCookie "userPass", window.btoa(document.getElementById("username").value + ":" + document.getElementById("password").value), 14 # Set a cookie with the username and password so we can log in automatically in the future without having to prompt for a username and password again
          # loadingBar.style.display = "none"
          t = Date.now()
          localStorage["lastUpdate"] = t
          #document.getElementById("lastUpdate").innerHTML = formatUpdate t
          try
            parse resp.response # Parse the data PCR has replied with
            display() # Added
          catch e
            console.log e
            alert "Error parsing assignments. Is PCR on list or month view?"
          return
      , (error) ->
        console.log "Could not log in to PCR. Either your network connection was lost during your visit or PCR is just not working. Here's the error:", error
  else
    console.log postArray
    send "/api/login?remember=#{document.getElementById("remember").checked}", "json", { "Content-type": "application/x-www-form-urlencoded" }, postArray.join("&"), true
      .then (resp) ->
        console.debug "Logging in:",resp.response.time
        if resp.response.login
          # If PCR still wants us to log in, then the username or password enterred were incorrect.
          document.getElementById("loginIncorrect").style.display = "block"
          document.getElementById("password").value = ""

          # document.getElementById("login").classList.add "active"
          # document.getElementById("loginBackground").style.display = "block"
        else
          t = Date.now()
          localStorage["lastUpdate"] = t
          #document.getElementById("lastUpdate").innerHTML = formatUpdate t

          window.data = resp.response.data
          display()
          localStorage["data"] = JSON.stringify(data)
        return
      , (error) ->
        console.log "Could not log in to PCR. Either your network connection was lost during your visit or PCR is just not working. Here's the error:", error
  return

attachmentify = (element) ->
  attachments = []

  # Get all links
  as = element.getElementsByTagName('a')

  a = 0
  while a < as.length
    if as[a].id.indexOf('Attachment') isnt -1
      attachments.push [
        as[a].innerHTML
        as[a].search + as[a].hash
      ]
      as[a].remove()
      a-- # subtract because all elements have shifted down
    a++
  attachments

urlify = (text) ->
  text.replace /// (
    https?:\/\/                  # http:// or https://
    [-A-Z0-9+&@#\/%?=~_|!:,.;]*  # Any number of url-OK characters
    [-A-Z0-9+&@#\/%=~_|]+        # At least one url-OK character except ?, !, :, ,, ., and ;
  )///ig, (str, str2, offset) -> # Function to replace matches
    if /href\s*=\s*./.test(text.substring(offset - 10, offset)) then str else '<a href="' + str + '">' + str + '</a>'

findId = (element, tag, id) ->
  for e in element.getElementsByTagName(tag)
    if e.id.indexOf(id) isnt -1
      return e
  return

parse = (doc) ->
  console.time "Handling data" # To time how long it takes to parse the assignments
  handledDataShort = [] # Array used to make sure we don"t parse the same assignment twice.
  window.data = {classes: [], assignments: [], monthView: doc.querySelector(".rsHeaderMonth").parentNode.classList.contains("rsSelected")} # Reset the array in which all of your assignments are stored in.

  for e in doc.getElementsByTagName("input")
    viewData[e.name] = e.value or ""

  # Now, the classes you take are parsed (these are the checkboxes you see up top when looking at PCR).

  classes = findId(doc, "table", "cbClasses").getElementsByTagName("label")
  for c in classes
    window.data.classes.push c.innerHTML

  assignments = doc.getElementsByClassName("rsApt rsAptSimple")

  # Now parse the assignments

  for ca in assignments
    assignment = {}

    # The starting date and ending date of the assignment are parsed first
    range = findId(ca, "span", "StartingOn").innerHTML.split(" - ")
    assignment.start = Math.floor (Date.parse range[0])/1000/3600/24
    assignment.end = if range[1]? then Math.floor (Date.parse range[1])/1000/3600/24 else assignment.start

    # Then, the name of the assignment is parsed
    t = findId(ca, "span", "lblTitle")
    title = t.innerHTML

    # The actual body of the assignment and its attachments are parsed next
    b = t.parentNode.parentNode
    divs = b.getElementsByTagName("div")
    for d in [0...2]
      divs[0].remove()
    ap = attachmentify(b) # Separates attachments from the body
    assignment.attachments = ap
    assignment.body = urlify(b.innerHTML).replace(/^(?:\s*<br\s*\/?>)*/, "").replace(/(?:\s*<br\s*\/?>)*\s*$/, "").trim() # The replaces remove leading and trailing newlines

    # Finally, we separate the class name and type (homework, classwork, or projects) from the title of the assignment
    assignment.type = title.match(/\(([^\(\)]*)\)$/)[1].toLowerCase().replace("& quizzes", "").replace("tests", "test")
    assignment.baseType = (ca.title.substring 0, ca.title.indexOf "\n").toLowerCase().replace("& quizzes", "")
    for c, pos in window.data.classes
      if title.indexOf(c) isnt -1
        assignment.class = pos
        title = title.replace(c,"")
        break
    assignment.title = title.substring(title.indexOf(": ") + 2).replace(/\([^\(\)]*\)$/,"").trim()

    # To make sure there are no repeats, the title of the assignment (only letters) and its start & end date are combined to give it a unique identifier.
    assignment.id = assignment.title.replace(/[^\w]*/g, "") + (assignment.start + assignment.end)
    if handledDataShort.indexOf(assignment.id) is -1 # Make sure we haven't already parsed the assignment
      handledDataShort.push assignment.id
      window.data.assignments.push assignment

  console.timeEnd "Handling data"

  # Now allow the view to be switched
  document.body.classList.add "loaded"

  #display() # Display the data
  localStorage["data"] = JSON.stringify(data) # Store for offline use
  return

A slightly modified fetch function is then called

do ->
  if location.protocol is "chrome-extension:"
    console.time "Fetching assignments"
    send "https://webappsca.pcrsoft.com/Clue/SC-Assignments-End-Date-Range/7536", "document"
      .then (resp) ->
        console.timeEnd "Fetching assignments"
        if resp.responseURL.indexOf("Login") isnt -1
          # We have to log in now
          loginURL = resp.responseURL
          for e in resp.response.getElementsByTagName("input")
            loginHeaders[e.name] = e.value or ""
          console.log "Need to log in"
          ### up = getCookie("userPass") # Attempts to get the cookie *userPass*, which is set if the "Remember me" checkbox is checked when logging in through CheckPCR
          if up is ""
            document.getElementById("loginBackground").style.display = "block"
            document.getElementById("login").classList.add "active"
          else
            dologin window.atob(up).split(":") # Because we were remembered, we can log in immediately without waiting for the user to log in through the login form
          ###
          # Add login button event listeners and enable the login button
          document.getElementById("login").classList.add "ready"
          document.getElementById("login").addEventListener "submit", (evt) ->
            evt.preventDefault()
            dologin(null, true)
        else
          # Logged in now
          console.log "Fetching assignments successful"
          t = Date.now()
          localStorage["lastUpdate"] = t
          # document.getElementById("lastUpdate").innerHTML = formatUpdate t
          try
            parse resp.response
          catch e
            console.log e
            alert "Error parsing assignments. Is PCR on list or month view?"
          document.getElementById("loginNext").style.display = ""
          document.getElementById("login").classList.add "done"
        return
      , (error) ->
        console.log "Could not fetch assignments; You are probably offline. Here's the error:", error
        snackbar "You must be online to set up Check PCR. Please refresh when the internet works."
        return
  else
    send "/api/start", "json"
      .then (resp) ->
        console.debug "Fetching assignments:",resp.response.time
        if resp.response.login
          loginHeaders = resp.response.loginHeaders
          # document.getElementById("loginBackground").style.display = "block"
          # document.getElementById("login").classList.add "active"
          # Add login button event listeners and enable the login button
          document.getElementById("login").classList.add "ready"
          document.getElementById("login").addEventListener "submit", (evt) ->
            evt.preventDefault()
            dologin(null, true)
        else
          console.log "Fetching assignments successful"
          t = Date.now()
          localStorage["lastUpdate"] = t
          # document.getElementById("lastUpdate").innerHTML = formatUpdate t

          window.data = resp.response.data
          localStorage["data"] = JSON.stringify(data)
          document.getElementById("loginNext").style.display = ""
          document.getElementById("login").classList.add "done"
        return
      , (error) ->
        console.log "Could not fetch assignments; You are probably offline. Here's the error:", error
        snackbar "Could not fetch your assignments", "Retry", fetch
  return

The display function is set to advance the setup proccess so there aren't any unwelcoming errors.

display = advance