<commit_before>/*jshint esversion: 6, node: true */
const express = require("express");
const router = module.exports = express.Router();

const generatePdf = require("../lib/generatePdf");
const resUtil = require("../lib/resUtil");
const routingUtil = require("../lib/routingUtil");
const extDataPromise = require("../lib/extData");
const { logger, issueError } = require("../lib/logger");

//register callback to get collections on load
let resolutions, access, resolutionArchive, collections;
require("../lib/database").fullInit.then(c => {
  //get collections in vars for easier use
  resolutions = c.resolutions;
  resolutionArchive = c.resolutionArchive;
  access = c.access;
  collections = c;
});

//GET (view) to /resolution displays front page (token and code input) without promo
router.get("/", function(req, res) {
  res.render("index", { promo: false });
});

//GET (responds with url, no view) render pdf
router.get("/renderpdf/:token", function(req, res) {
  //check for token and save new resolution content
  routingUtil.checkToken(req, res, {
    $set: {
      //add current time to render history log
      lastRender: Date.now(),

      //reset flag, don't do a rerender if not saved again
      unrenderedChanges: false
    }
  }, (token, document) => {
    //don't render if nothing saved yet
    if (! document.stage) {
      issueError(req, res, 400, "nothing saved (stage 0)");
      return;
    }

    //url to pdf
    const pdfUrl = "/rendered/" + token + ".pdf";

    //don't render if there hasn't been a save since the last render
    if (! document.unrenderedChanges) {
      //just send url and stop
      res.send(pdfUrl);
      return;
    }

    //generate pdf
    generatePdf(document, "resolution").then(
      //send url to rendered pdf
      pageAmount => {
        //send url to confirm render
        res.send(pdfUrl);

        //check if the page amount could be determined
        if (pageAmount) {
          //put in database for work queue display
          resolutions.updateOne({ token: token }, {
            $set: { pageAmount: pageAmount }
          }).catch( //not interested in result
            err => logger.error("could not update page amount", { stack: err.stack })
          );
        }
      },

      //print and notify of error
      err => issueError(req, res, 500, "render problem", err)
    );
  });
});

//GET (responds with url, no view) render plaintext, not really render but similar
router.get("/renderplain/:token", function(req, res) {
  //check for token and save new resolution content
  routingUtil.checkToken(req, res, (token, document) => {
    //don't render if nothing saved yet
    if (! document.stage) {
      issueError(req, res, 400, "nothing saved (stage 0)");
      return;
    }

    //respond with plaintext form of resolution
    res.render("plainview", {
      data: resUtil.renderPlaintext(document),
      token: token
    });
  });
});

//landing page is displayed statically and only displays information for the user to read
router.get("/prenew", function(req, res) {
  //render landing page, no new resolution button if accessed from menu
  res.render("newreslanding", { noNew: req.query.nonew === "1" });
});

//GET (no view, processor) redirects to editor page with new registered token
router.get("/new", function(req, res) {
  //make a new unique token, true flag for being a token
  resUtil.makeNewThing(req, res, true).then(token => {
    //get now (consistent)
    const timeNow = Date.now();

    //put new resolution into database
    resolutions.insertOne({
      token: token, //identifier
      created: timeNow, //time of creation
      changed: timeNow, //last time it was changed = saved, stage advances don't count
      stageHistory: [ timeNow ], //index is resolution stage, time when reached that stage
      lastRender: 0, //logs pdf render events
      lastLiveview: 0, //last time a liveview session happened with this resolution
      stage: 0, //current workflow stage (see phase 2 notes)
      liveviewOpen: false, //if a liveview page is viewing this resolution right now (TODO: use)
      attributes: "none", //attribute status, see /setattribs or fucntion, restricts actions
      unrenderedChanges: false, //set to true when saved and reset when rendered
      amendments: [] //stores all amendments made to the resolution
    }).then(() => {
      //redirect to editor page (because URL is right then)
      res.redirect("/resolution/editor/" + token);
    }, () => issueError(req, res, 500, "can't create new"));
  });
});

//POST (no view) save resolution
router.post("/save/:token", function(req, res) {
  //require resolution content to be present and valid
  routingUtil.checkBodyRes(req, res, resolutionSent => {
    //authorize, doesn't do code auth if no code necessary (if session present)
    routingUtil.fullAuth(req, res, token => {
      //plenary id to set
      let plenaryId;

      //require extData to be present
      extDataPromise.then(extData => {
        //get the plenary type for the forum of this resolution
        plenaryId = extData.forums[resolutionSent.resolution.address.forum].plenary;
      })

      //save document
      .then(() => resolutions.updateOne(
        { token: token },
        {
          $set: {
            content: resolutionSent, //update resolution content
            changed: Date.now(), //update changedate
            unrenderedChanges: true, //must be rendered
            plenaryId: plenaryId
          },
          $max: {
            stage: 1 //first saved stage at least
          },
          $min: {
            "stageHistory.1": Date.now() //set first save time (don't change if this is older)
          }
        }
      )).then(() => {
        res.send("ok");
      }, err => issueError(req, res, 500, "can't save resolution", err));
    }, {
      //match mode save respects saving attrib restrictions
      matchMode: "save"
    });
  });
});

//return the render param object for the editor view
function getEditorViewParams(doLoad, resDoc, token, codeDoc) {
  //send rendered editor page with the token set
  return {
    token: token,
    meta: resUtil.getMetaInfo(resDoc),
    doLoad: doLoad,
    code: codeDoc.hasOwnProperty("code") ? codeDoc.code : null,
    accessLevel: codeDoc.level
  };
}

//POST/GET (editor view) redirected to here to send editor with set token
//(also only displays meta info if code necessary but not given or invalid)
routingUtil.getAndPost(router, "/editor/:token", function(req, res) {
  //check for token and code (check with DE perm is no code given)
  routingUtil.fullAuth(req, res,
    (token, resDoc, codeDoc) =>
      //send rendered editor page with token set
      res.render("editor", getEditorViewParams(true, resDoc, token, codeDoc)),
    {
      permissionMissmatch: (token, resDoc, codeDoc) =>
        //send edtor page but with "no access" notice and unlock form
        res.render("editor", getEditorViewParams(false, resDoc, token, codeDoc)),

      //editor match mode
      matchMode: "editor"
    }
  );
});

//both admin actions setattribs and delete need the same admin full auth
router.use(["/setattribs/:token", "/delete/:token"], (req, res, next) =>
  //do full auth
  routingUtil.fullAuth(req, res, token => {
    //attach token to req
    req.resToken = token;

    //proceed to individual routes
    next();
  }, {
    //just make no DB query on auth fail
    permissionMissmatch: token => {
      //just go back to editor
      res.redirect("/resolution/editor/" + token);
    },

    //use attribute setting permission set (resticted to MA level)
    matchMode: "admin"
  })
);

//POST (no view) update attributes and redirect back to editor page
router.post("/setattribs/:token", function(req, res) {
  //get token from req as it was attached to the middleware for setattribs and delete
  const token = req.resToken;

  //check for present post body property and must be a ok value
  if (req.body && req.body.attrib &&
      ["none", "noadvance", "readonly", "static"].includes(req.body.attrib)) {
    //set to new value in database
    resolutions.updateOne(
      { token: token },
      {
        $set: {
          //set attrib string as found in post body
          attributes: req.body.attrib
        }
      }
    ).then(() => {
      //redirect back to editor page
      res.redirect("/resolution/editor/" + token);
    }, err => issueError(req, res, 500, "can't set attribute", err));
  } else {
    issueError(req, res, 400, "missing or incorrect fields");
  }
});

//POST (no view) delete a resolution
router.post("/delete/:token", function(req, res) {
  //get token from req (see above)
  const token = req.token;

  //remove resolution with that token by moving to the archive
  resolutions.findOneAndDelete( { token: token } ).then(resDoc => {
    //insert into archive collection, unpack returned object with .value!
    resolutionArchive.insertOne(resDoc.value).then(() => {
      //acknowledge
      res.send("ok. deleted " + token);
    }, err => issueError(req, res, 500, "can't insert into archive", err));
  }, err => issueError(req, res, 500, "can't delete", err));
});

//POST, GET for liveview page (GET may be ok when session with auth is present)
routingUtil.getAndPost(router, "/liveview/:token", function(req, res) {
  //check for token and code and correct stage (liveview permission mode)
  routingUtil.fullAuth(req, res,
    (token, resDoc, codeDoc) =>
      //send rendered editor page with token set
      res.render("liveview", {
        token: token,
        code: codeDoc.code,
        accessLevel: codeDoc.level,
        stage: resDoc.stage
      }),
    {
      permissionMissmatch: (token, resDoc, codeDoc) =>
        //send no permission page
        res.render("weakperm", {
          type: "liveview",
          token: resDoc.token,
          stage: resDoc.stage,
          accessLevel: codeDoc.level
        }),
      matchMode: "liveview"
    }
  );
});

//GET and POST (no view) to send resolution data
routingUtil.getAndPost(router, "/load/:token", function(req, res) {
  //authorize, absence of code is detected in fullAuth
  routingUtil.fullAuth(req, res, (token, resolutionDoc) => {
    //send resolution to client, remove database wrapper
    res.send(resolutionDoc.content);
  });
});

//pads with leading zeros (returns a string)
function padNumber(number, amount) {
  //convert to string
  number = "" + number;

  //return 0 repeated until the right length
  return amount < number.length ? number : "0".repeat(amount - number.length) + number;
}

//POST (no view) to advance resolution, redirect to editor without code after completion
routingUtil.getAndPost(router, "/advance/:token", function(req, res) {
  //authorize, absence of code is detected in fullAuth
  routingUtil.fullAuth(req, res, (token, resDoc) => {
    //query object to be possibly extended by a vote result setter
    const query = {
      $inc: { stage: 1 }, //advance to next stage index
      $push: { stageHistory: Date.now() } //add timestamp to history
    };

    //if vote values are sent
    if (req.body && "inFavor" in req.body) {
      //determine vote type with stage we are advancing from
      const voteType = resDoc.stage === 6 ? "committee" : "plenary";

      //make voting reults object
      const voteResults = {
        // "|| 0" convert to number or 0
        inFavor: parseInt(req.body.inFavor, 10) || 0,
        against: parseInt(req.body.against, 10) || 0,
        abstention: parseInt(req.body.abstention, 10) || 0,
        importantQuestion: req.body.importantQuestion ? true : false,
        voteType
      };

      /*check and add prop whether or not this resolution passed.
      must be majority for pass and 2/3 majority (= two times more than...)
      for important question to pass*/
      voteResults.passed = voteResults.inFavor > voteResults.against *
        (voteResults.importantQuestion ? 2 : 1);

      //add whole thing to query
      query.$set = {
        //set result depending on which vote this is
        [ "voteResults." + voteType]: voteResults
      };
    } else if (resDoc.stage === 6 || resDoc.stage === 10) {
      //needed vote results but got none
      issueError(req, res, 400, "got no vote results although they were required");
      return;
    }

    //add resolution id if going from stage 3 (FC) to 4 (print), regular advance otherwise
    (resDoc.stage === 3 ?
      //get and increment resolution id for this year
      //needs findOneAndUpdate because we need the value of the modified doc
      collections.resolutionId.findOneAndUpdate({ year: new Date().getFullYear() }, {
        //count up one
        $inc: { counter: 1 }
      })

      //advance resolution to next stage and set id
      .then(yearDoc => {
        //add $set property if not set already
        if (! query.$set) {
          query.$set = {};
        }

        //add id and year to update query
        query.$set.resolutionId = padNumber(yearDoc.value.counter, 3);
        query.$set.idYear = yearDoc.value.year;

        //as this changes the pdf generated from this resolution, flag must be set
        //(the id is added to the pdf in a corner)
        query.$set.unrenderedChanges = true;

        //execute query and return resulting promise
        return resolutions.updateOne({ token: token }, query);
      }) :

      //execute and return query without adding resolution id
      resolutions.updateOne({ token: token }, query)
    )

    //redirect to editor without code, prevent form resubmission
    .then(
      () => {
        //don't redirect if correct param set
        if (req.query.noui) {
          res.send("ok");
        } else {
          //regular redirect
          res.redirect("/resolution/editor/" + token);
        }
      },
      err => issueError(req, res, 500, "advance db error", err));
  }, {
    //error/warning page on fail
    permissionMissmatch: (token, resolutionDoc, codeDoc) => {
      //issue error if no ui, error page if user is viewing
      if (req.query.noui) {
        issueError(req, res, 400, "insufficient permission to advance");
      } else {
        //display error page
        res.render("weakperm", {
          type: "advance",
          token: resolutionDoc.token,
          stage: resolutionDoc.stage,
          accessLevel: codeDoc.level,
          attributes: resolutionDoc.attributes
        });
      }
    },
    //use advance match mode because advancement has specific permission requirements
    matchMode: "advance"
  });
});

//GET (no view, validation response) checks if sent token or code is valid (for form display)
router.get("/checkinput/:thing", function(req, res) {
  //respond with token/code ok or not
  if (req.params.thing[0] === "@") {
    routingUtil.checkToken(req, res, () => {
      res.send("ok token");
    });
  } else if (req.params.thing[0] === "!") {
    routingUtil.checkCode(req, res, () => {
      res.send("ok code");
    });
  } else {
    //bad, strange thing sent
    issueError(400, "sent thing unreadable, code/token?");
  }
});
<commit_msg>fix deletion<commit_after>/*jshint esversion: 6, node: true */
const express = require("express");
const router = module.exports = express.Router();

const generatePdf = require("../lib/generatePdf");
const resUtil = require("../lib/resUtil");
const routingUtil = require("../lib/routingUtil");
const extDataPromise = require("../lib/extData");
const { logger, issueError } = require("../lib/logger");

//register callback to get collections on load
let resolutions, access, resolutionArchive, collections;
require("../lib/database").fullInit.then(c => {
  //get collections in vars for easier use
  resolutions = c.resolutions;
  resolutionArchive = c.resolutionArchive;
  access = c.access;
  collections = c;
});

//GET (view) to /resolution displays front page (token and code input) without promo
router.get("/", function(req, res) {
  res.render("index", { promo: false });
});

//GET (responds with url, no view) render pdf
router.get("/renderpdf/:token", function(req, res) {
  //check for token and save new resolution content
  routingUtil.checkToken(req, res, {
    $set: {
      //add current time to render history log
      lastRender: Date.now(),

      //reset flag, don't do a rerender if not saved again
      unrenderedChanges: false
    }
  }, (token, document) => {
    //don't render if nothing saved yet
    if (! document.stage) {
      issueError(req, res, 400, "nothing saved (stage 0)");
      return;
    }

    //url to pdf
    const pdfUrl = "/rendered/" + token + ".pdf";

    //don't render if there hasn't been a save since the last render
    if (! document.unrenderedChanges) {
      //just send url and stop
      res.send(pdfUrl);
      return;
    }

    //generate pdf
    generatePdf(document, "resolution").then(
      //send url to rendered pdf
      pageAmount => {
        //send url to confirm render
        res.send(pdfUrl);

        //check if the page amount could be determined
        if (pageAmount) {
          //put in database for work queue display
          resolutions.updateOne({ token: token }, {
            $set: { pageAmount: pageAmount }
          }).catch( //not interested in result
            err => logger.error("could not update page amount", { stack: err.stack })
          );
        }
      },

      //print and notify of error
      err => issueError(req, res, 500, "render problem", err)
    );
  });
});

//GET (responds with url, no view) render plaintext, not really render but similar
router.get("/renderplain/:token", function(req, res) {
  //check for token and save new resolution content
  routingUtil.checkToken(req, res, (token, document) => {
    //don't render if nothing saved yet
    if (! document.stage) {
      issueError(req, res, 400, "nothing saved (stage 0)");
      return;
    }

    //respond with plaintext form of resolution
    res.render("plainview", {
      data: resUtil.renderPlaintext(document),
      token: token
    });
  });
});

//landing page is displayed statically and only displays information for the user to read
router.get("/prenew", function(req, res) {
  //render landing page, no new resolution button if accessed from menu
  res.render("newreslanding", { noNew: req.query.nonew === "1" });
});

//GET (no view, processor) redirects to editor page with new registered token
router.get("/new", function(req, res) {
  //make a new unique token, true flag for being a token
  resUtil.makeNewThing(req, res, true).then(token => {
    //get now (consistent)
    const timeNow = Date.now();

    //put new resolution into database
    resolutions.insertOne({
      token: token, //identifier
      created: timeNow, //time of creation
      changed: timeNow, //last time it was changed = saved, stage advances don't count
      stageHistory: [ timeNow ], //index is resolution stage, time when reached that stage
      lastRender: 0, //logs pdf render events
      lastLiveview: 0, //last time a liveview session happened with this resolution
      stage: 0, //current workflow stage (see phase 2 notes)
      liveviewOpen: false, //if a liveview page is viewing this resolution right now (TODO: use)
      attributes: "none", //attribute status, see /setattribs or fucntion, restricts actions
      unrenderedChanges: false, //set to true when saved and reset when rendered
      amendments: [] //stores all amendments made to the resolution
    }).then(() => {
      //redirect to editor page (because URL is right then)
      res.redirect("/resolution/editor/" + token);
    }, () => issueError(req, res, 500, "can't create new"));
  });
});

//POST (no view) save resolution
router.post("/save/:token", function(req, res) {
  //require resolution content to be present and valid
  routingUtil.checkBodyRes(req, res, resolutionSent => {
    //authorize, doesn't do code auth if no code necessary (if session present)
    routingUtil.fullAuth(req, res, token => {
      //plenary id to set
      let plenaryId;

      //require extData to be present
      extDataPromise.then(extData => {
        //get the plenary type for the forum of this resolution
        plenaryId = extData.forums[resolutionSent.resolution.address.forum].plenary;
      })

      //save document
      .then(() => resolutions.updateOne(
        { token: token },
        {
          $set: {
            content: resolutionSent, //update resolution content
            changed: Date.now(), //update changedate
            unrenderedChanges: true, //must be rendered
            plenaryId: plenaryId
          },
          $max: {
            stage: 1 //first saved stage at least
          },
          $min: {
            "stageHistory.1": Date.now() //set first save time (don't change if this is older)
          }
        }
      )).then(() => {
        res.send("ok");
      }, err => issueError(req, res, 500, "can't save resolution", err));
    }, {
      //match mode save respects saving attrib restrictions
      matchMode: "save"
    });
  });
});

//return the render param object for the editor view
function getEditorViewParams(doLoad, resDoc, token, codeDoc) {
  //send rendered editor page with the token set
  return {
    token: token,
    meta: resUtil.getMetaInfo(resDoc),
    doLoad: doLoad,
    code: codeDoc.hasOwnProperty("code") ? codeDoc.code : null,
    accessLevel: codeDoc.level
  };
}

//POST/GET (editor view) redirected to here to send editor with set token
//(also only displays meta info if code necessary but not given or invalid)
routingUtil.getAndPost(router, "/editor/:token", function(req, res) {
  //check for token and code (check with DE perm is no code given)
  routingUtil.fullAuth(req, res,
    (token, resDoc, codeDoc) =>
      //send rendered editor page with token set
      res.render("editor", getEditorViewParams(true, resDoc, token, codeDoc)),
    {
      permissionMissmatch: (token, resDoc, codeDoc) =>
        //send edtor page but with "no access" notice and unlock form
        res.render("editor", getEditorViewParams(false, resDoc, token, codeDoc)),

      //editor match mode
      matchMode: "editor"
    }
  );
});

//both admin actions setattribs and delete need the same admin full auth
router.use(["/setattribs/:token", "/delete/:token"], (req, res, next) =>
  //do full auth
  routingUtil.fullAuth(req, res, token => {
    //attach token to req
    req.resToken = token;

    //proceed to individual routes
    next();
  }, {
    //just make no DB query on auth fail
    permissionMissmatch: token => {
      //just go back to editor
      res.redirect("/resolution/editor/" + token);
    },

    //use attribute setting permission set (resticted to MA level)
    matchMode: "admin"
  })
);

//POST (no view) update attributes and redirect back to editor page
router.post("/setattribs/:token", function(req, res) {
  //get token from req as it was attached to the middleware for setattribs and delete
  const token = req.resToken;

  //check for present post body property and must be a ok value
  if (req.body && req.body.attrib &&
      ["none", "noadvance", "readonly", "static"].includes(req.body.attrib)) {
    //set to new value in database
    resolutions.updateOne(
      { token: token },
      {
        $set: {
          //set attrib string as found in post body
          attributes: req.body.attrib
        }
      }
    ).then(() => {
      //redirect back to editor page
      res.redirect("/resolution/editor/" + token);
    }, err => issueError(req, res, 500, "can't set attribute", err));
  } else {
    issueError(req, res, 400, "missing or incorrect fields");
  }
});

//POST (no view) delete a resolution
router.post("/delete/:token", function(req, res) {
  //get token from req (see above)
  const token = req.token;

  //remove resolution with that token by moving to the archive
  resolutions.findOne( { token: token } )
    .then(resDoc => Promise.all([
      resolutionArchive.insertOne(resDoc), resolutions.deleteOne({ token })
    ]))
    .then(() => {
      //acknowledge
      res.send("ok. deleted " + token);
  }, err => issueError(req, res, 500, "can't query for delete", err));
});

//POST, GET for liveview page (GET may be ok when session with auth is present)
routingUtil.getAndPost(router, "/liveview/:token", function(req, res) {
  //check for token and code and correct stage (liveview permission mode)
  routingUtil.fullAuth(req, res,
    (token, resDoc, codeDoc) =>
      //send rendered editor page with token set
      res.render("liveview", {
        token: token,
        code: codeDoc.code,
        accessLevel: codeDoc.level,
        stage: resDoc.stage
      }),
    {
      permissionMissmatch: (token, resDoc, codeDoc) =>
        //send no permission page
        res.render("weakperm", {
          type: "liveview",
          token: resDoc.token,
          stage: resDoc.stage,
          accessLevel: codeDoc.level
        }),
      matchMode: "liveview"
    }
  );
});

//GET and POST (no view) to send resolution data
routingUtil.getAndPost(router, "/load/:token", function(req, res) {
  //authorize, absence of code is detected in fullAuth
  routingUtil.fullAuth(req, res, (token, resolutionDoc) => {
    //send resolution to client, remove database wrapper
    res.send(resolutionDoc.content);
  });
});

//pads with leading zeros (returns a string)
function padNumber(number, amount) {
  //convert to string
  number = "" + number;

  //return 0 repeated until the right length
  return amount < number.length ? number : "0".repeat(amount - number.length) + number;
}

//POST (no view) to advance resolution, redirect to editor without code after completion
routingUtil.getAndPost(router, "/advance/:token", function(req, res) {
  //authorize, absence of code is detected in fullAuth
  routingUtil.fullAuth(req, res, (token, resDoc) => {
    //query object to be possibly extended by a vote result setter
    const query = {
      $inc: { stage: 1 }, //advance to next stage index
      $push: { stageHistory: Date.now() } //add timestamp to history
    };

    //if vote values are sent
    if (req.body && "inFavor" in req.body) {
      //determine vote type with stage we are advancing from
      const voteType = resDoc.stage === 6 ? "committee" : "plenary";

      //make voting reults object
      const voteResults = {
        // "|| 0" convert to number or 0
        inFavor: parseInt(req.body.inFavor, 10) || 0,
        against: parseInt(req.body.against, 10) || 0,
        abstention: parseInt(req.body.abstention, 10) || 0,
        importantQuestion: req.body.importantQuestion ? true : false,
        voteType
      };

      /*check and add prop whether or not this resolution passed.
      must be majority for pass and 2/3 majority (= two times more than...)
      for important question to pass*/
      voteResults.passed = voteResults.inFavor > voteResults.against *
        (voteResults.importantQuestion ? 2 : 1);

      //add whole thing to query
      query.$set = {
        //set result depending on which vote this is
        [ "voteResults." + voteType]: voteResults
      };
    } else if (resDoc.stage === 6 || resDoc.stage === 10) {
      //needed vote results but got none
      issueError(req, res, 400, "got no vote results although they were required");
      return;
    }

    //add resolution id if going from stage 3 (FC) to 4 (print), regular advance otherwise
    (resDoc.stage === 3 ?
      //get and increment resolution id for this year
      //needs findOneAndUpdate because we need the value of the modified doc
      collections.resolutionId.findOneAndUpdate({ year: new Date().getFullYear() }, {
        //count up one
        $inc: { counter: 1 }
      })

      //advance resolution to next stage and set id
      .then(yearDoc => {
        //add $set property if not set already
        if (! query.$set) {
          query.$set = {};
        }

        //add id and year to update query
        query.$set.resolutionId = padNumber(yearDoc.value.counter, 3);
        query.$set.idYear = yearDoc.value.year;

        //as this changes the pdf generated from this resolution, flag must be set
        //(the id is added to the pdf in a corner)
        query.$set.unrenderedChanges = true;

        //execute query and return resulting promise
        return resolutions.updateOne({ token: token }, query);
      }) :

      //execute and return query without adding resolution id
      resolutions.updateOne({ token: token }, query)
    )

    //redirect to editor without code, prevent form resubmission
    .then(
      () => {
        //don't redirect if correct param set
        if (req.query.noui) {
          res.send("ok");
        } else {
          //regular redirect
          res.redirect("/resolution/editor/" + token);
        }
      },
      err => issueError(req, res, 500, "advance db error", err));
  }, {
    //error/warning page on fail
    permissionMissmatch: (token, resolutionDoc, codeDoc) => {
      //issue error if no ui, error page if user is viewing
      if (req.query.noui) {
        issueError(req, res, 400, "insufficient permission to advance");
      } else {
        //display error page
        res.render("weakperm", {
          type: "advance",
          token: resolutionDoc.token,
          stage: resolutionDoc.stage,
          accessLevel: codeDoc.level,
          attributes: resolutionDoc.attributes
        });
      }
    },
    //use advance match mode because advancement has specific permission requirements
    matchMode: "advance"
  });
});

//GET (no view, validation response) checks if sent token or code is valid (for form display)
router.get("/checkinput/:thing", function(req, res) {
  //respond with token/code ok or not
  if (req.params.thing[0] === "@") {
    routingUtil.checkToken(req, res, () => {
      res.send("ok token");
    });
  } else if (req.params.thing[0] === "!") {
    routingUtil.checkCode(req, res, () => {
      res.send("ok code");
    });
  } else {
    //bad, strange thing sent
    issueError(400, "sent thing unreadable, code/token?");
  }
});
<|endoftext|>


<commit_before>const _ = require("lodash");
const express = require("express");
const router = express.Router();
const connect = require("../../utils/database.js");

// Route: {root}/api/users/...

// GET routes
// GET self

// Everything else should be restricted
// GET all users
router.get("/", function(req, res){
	connect.then(function(db){
		return db.collection("_users_auth").find().toArray();
	}).then(function(data){
		// Remove internal ID and password hash, maybe split the db later
		_.each(data, function(el, i){
			delete el._id;
			delete el.hash;
		});
		res.json(data);
	});
});

// GET specific user
router.get("/:username", function(req, res){
	connect.then(function(db){
		return db.collection("_users_auth").findOne({username: req.params.username});
	}).then(function(data){
		// Remove internal ID and password hash, maybe split the db later
		delete data._id;
		delete data.hash;
		res.json(data);
	});
});

// POST routes
// POST to create a new user
router.post("/", function(req, res){
	res.json({
		message: "Implementation pending"
	});
});

// POST to a user (edit existing user)
router.post("/:username", function(){
	res.json({
		message: "Implementation pending"
	});
});

// DELETE routes
// DELETE specific user
router.post("/:username", function(){
	res.json({
		message: "Implementation pending"
	});
});

module.exports = router;<commit_msg>Basic users routes implementation<commit_after>const _ = require("lodash");
const express = require("express");
const router = express.Router();
const connect = require("../../utils/database.js");
const auth = require("../../utils/auth.js");

// Route: {root}/api/users/...

// GET routes
// GET self

// Everything else should be restricted
// GET all users
router.get("/", function(req, res){
	connect.then(function(db){
		return db.collection("_users_auth").find().toArray();
	}).then(function(data){
		// Remove internal ID and password hash, maybe split the db later
		_.each(data, function(el, i){
			delete el._id;
			delete el.hash;
		});
		res.json(data);
	});
});

// GET specific user
router.get("/:username", function(req, res){
	connect.then(function(db){
		return db.collection("_users_auth").findOne({username: req.params.username});
	}).then(function(data){
		// Remove internal ID and password hash, maybe split the db later
		delete data._id;
		delete data.hash;
		res.json(data);
	});
});

// POST routes
// POST to create a new user
router.post("/", function(req, res, next){
	var data = req.body;
	auth.signup(data.username, data.password, function(err, result){
		if(err) next(err);

		res.json(result);
	});
});

// POST to a user (edit existing user)
router.post("/:username", function(req, res, next){
	auth.authenticate(req.params.username, req.body.currentPassword, function(err, user){
		if(err) next(err);

		auth.changePassword(req.params.username, req.body.newPassword, function(err, user){
			res.json({
				message: `User ${req.params.username}'s password changed`
			});
		});
	});
});

// DELETE routes
// DELETE specific user
router.delete("/:username", function(req, res){
	connect.then(function(db){
		return db.collection("_users_auth").deleteOne({"username": req.params.username});
	}).then(function(){
		res.json({
			message: `User ${req.params.username} deleted`
		});
	});
});

module.exports = router;<|endoftext|>


<commit_before>var async = require('async');

module.exports = [
    // Post authorization.
    function (payload, run, done) {
        if (!payload._response) { return done(new Error('No response to process.')); }

        var item = payload._item,
            response = payload._response;

        async.waterfall([
            // Post verify
            function (next) {
                run.authorizer.postVerify(item, response, run, next);
            },
            // Call initialize if necessary
            function (verified, next) {
                // If verification succeeds or the request is already a replay,
                // move on to the next request
                if (verified || payload._replay) { return next(null); }

                payload._replay = true;
                run.authorizer.init(item, run, function (err) {
                    return next(err);
                });
            }
        ], done);
    }
];
<commit_msg>do not create an error if no response is available<commit_after>var async = require('async');

module.exports = [
    // Post authorization.
    function (payload, run, done) {
        // if no response is provided, there's nothing to do, and probably means that the request errored out
        // let the actual request command handle whatever needs to be done.
        if (!payload._response) { return done(); }

        var item = payload._item,
            response = payload._response;

        async.waterfall([
            // Post verify
            function (next) {
                run.authorizer.postVerify(item, response, run, next);
            },
            // Call initialize if necessary
            function (verified, next) {
                // If verification succeeds or the request is already a replay,
                // move on to the next request
                if (verified || payload._replay) { return next(null); }

                payload._replay = true;
                run.authorizer.init(item, run, function (err) {
                    return next(err);
                });
            }
        ], done);
    }
];
<|endoftext|>


<commit_before>/*
 * Return the script under test as a full URL
 */
function script_under_test() {
    return acre.request.base_url + acre.request.script.name.replace("test_","");
}

/*
 * make sure that a URL got fetched correctly by looking into the domain of a tracking cookie they set
 * which is probably the least likely part of their web page that is likely to change over time.
 * Note that the test is designed to pass if the URL fetching returns with a status code that is not 200.
 * This is not bad because we're testing acre's urlfetching machinery, not the availability of these websites
 */
function has_correct_domain(res,cookie_name,domain) {
    ok(((res.status == 200 && res.cookies[cookie_name].domain.indexOf(domain) > -1) || response.status != 200),  domain);
}

/*
 * make sure that a URL got fetched correctly by looking into the body of the response for the given content fragment
 * Note that the test is designed to pass if the URL fetching returns with a status code that is not 200.
 * This is not bad because we're testing acre's urlfetching machinery, not the availability of these websites
 */
function has_content(res,fragment) {
    ok(((res.status == 200 && res.body.indexOf(fragment) > -1) || response.status != 200),  fragment);
}

/*
 * returns a sorted and comma separated list of cookie names given an response object
 */
function cookie_names(response) {
    var names = [];
    for (var c in response.cookies) {
        names.push(c);
    }
    names.sort();
    return names.join(",");
}

/*
 * parse metaweb costs
 */
function metaweb_costs(response) {
    var costs = {};
    var cost_str = response.headers['x-metaweb-cost'];
    if (typeof cost_str != 'undefined') {
        var costs_frags = cost_str.split(',');
        for (var i = 0; i < costs_frags.length; i++) {
            var frag = costs_frags[i];
            var pieces = frag.split('=');
            costs[trim(pieces[0])] = trim(pieces[1]);
        }
    }
    return costs;
}

/*
 * Remove leading and trailing whitespace from the given string
 */
function trim(str) {
    return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
}

/*
 * Return false if the browser sent a request with a 'cache-control: no-cache' header (normally triggered by hitting shift-reload)
 */
function is_caching() {
    return !(acre.request.headers['cache-control'] == 'no-cache');
}<commit_msg>Correct variable name mismatch in testng utils<commit_after>/*
 * Return the script under test as a full URL
 */
function script_under_test() {
    return acre.request.base_url + acre.request.script.name.replace("test_","");
}

/*
 * make sure that a URL got fetched correctly by looking into the domain of a tracking cookie they set
 * which is probably the least likely part of their web page that is likely to change over time.
 * Note that the test is designed to pass if the URL fetching returns with a status code that is not 200.
 * This is not bad because we're testing acre's urlfetching machinery, not the availability of these websites
 */
function has_correct_domain(res,cookie_name,domain) {
    ok(((res.status == 200 && res.cookies[cookie_name].domain.indexOf(domain) > -1) || res.status != 200),  domain);
}

/*
 * make sure that a URL got fetched correctly by looking into the body of the response for the given content fragment
 * Note that the test is designed to pass if the URL fetching returns with a status code that is not 200.
 * This is not bad because we're testing acre's urlfetching machinery, not the availability of these websites
 */
function has_content(res,fragment) {
    ok(((res.status == 200 && res.body.indexOf(fragment) > -1) || res.status != 200),  fragment);
}

/*
 * returns a sorted and comma separated list of cookie names given an response object
 */
function cookie_names(response) {
    var names = [];
    for (var c in response.cookies) {
        names.push(c);
    }
    names.sort();
    return names.join(",");
}

/*
 * parse metaweb costs
 */
function metaweb_costs(response) {
    var costs = {};
    var cost_str = response.headers['x-metaweb-cost'];
    if (typeof cost_str != 'undefined') {
        var costs_frags = cost_str.split(',');
        for (var i = 0; i < costs_frags.length; i++) {
            var frag = costs_frags[i];
            var pieces = frag.split('=');
            costs[trim(pieces[0])] = trim(pieces[1]);
        }
    }
    return costs;
}

/*
 * Remove leading and trailing whitespace from the given string
 */
function trim(str) {
    return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
}

/*
 * Return false if the browser sent a request with a 'cache-control: no-cache' header (normally triggered by hitting shift-reload)
 */
function is_caching() {
    return !(acre.request.headers['cache-control'] == 'no-cache');
}<|endoftext|>


<commit_before>var express = require('express')
  , mongoose = require('mongoose')
  , MongoStore = require('connect-mongo')(express)
  , env = process.env.NODE_ENV || 'development';

module.exports = function(app, config) {
  app.use(express.favicon());

  app.use(express.static(config.rootPath + '/public'));

  if (env == 'development') {
    app.use(express.logger('dev'));
  }

  app.use(express.json());
  app.use(express.urlencoded());

  app.use(express.cookieParser());

  app.use(express.session({
    secret: config.sessionSecret,
    key: 'sid',
    cookie: {
      path: '/',
      httpOnly: 'true',
      maxAge: null
    },
    store: new MongoStore({ mongoose_connection: mongoose.connection })
  }));

  app.use(app.router);

  app.set('views', config.rootPath + '/app/views');
  app.set('view engine', 'jade');
}<commit_msg>add 404 status<commit_after>var express = require('express')
  , mongoose = require('mongoose')
  , MongoStore = require('connect-mongo')(express)
  , env = process.env.NODE_ENV || 'development';

module.exports = function(app, config) {
  app.use(express.favicon());

  app.use(express.static(config.rootPath + '/public'));

  if (env == 'development') {
    app.use(express.logger('dev'));
  }

  app.use(express.json());
  app.use(express.urlencoded());

  app.use(express.cookieParser());

  app.use(express.session({
    secret: config.sessionSecret,
    key: 'sid',
    cookie: {
      path: '/',
      httpOnly: 'true',
      maxAge: null
    },
    store: new MongoStore({ mongoose_connection: mongoose.connection })
  }));

  app.use(app.router);

  app.use(function(req, res) {
    res.send(404);
  })

  app.set('views', config.rootPath + '/app/views');
  app.set('view engine', 'jade');
}<|endoftext|>


<commit_before>import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import SplitPane from 'react-split-pane';
import MarkdownEditor from '../components/MarkdownEditor';
import MarkdownRendered from '../components/MarkdownRendered';
import { updateText } from '../actions/text';
import { resizePane } from '../actions/ui';
import Sidebar from '../components/Sidebar';

const App = ({ raw, rendered, paneSize, handleUpdate, handleResize }) => (

    <Sidebar />

      <SplitPane className="h-100 v-100 static" split="vertical" minSize={20} defaultSize={paneSize} onChange={handleResize}>
        <MarkdownEditor text={raw} handleUpdate={handleUpdate} />
        <MarkdownRendered content={rendered} />
      </SplitPane>

);

App.propTypes = {
  raw: PropTypes.string,
  rendered: PropTypes.string,
  paneSize: PropTypes.number,
  handleUpdate: PropTypes.func,
  handleResize: PropTypes.func
};
const mapStateToProps = state => ({
  raw: state.text.get('raw'),
  rendered: state.text.get('rendered'),
  paneSize: state.ui.get('paneSize')
});
const mapDispatchToProps = dispatch => ({
  handleUpdate: bindActionCreators(updateText, dispatch),
  handleResize: bindActionCreators(resizePane, dispatch)
});
export default connect(mapStateToProps, mapDispatchToProps)(App);
<commit_msg>three panes that all resize- buggy on load<commit_after>import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import SplitPane from 'react-split-pane';
import MarkdownEditor from '../components/MarkdownEditor';
import MarkdownRendered from '../components/MarkdownRendered';
import { updateText } from '../actions/text';
import { resizePane } from '../actions/ui';
import Sidebar from '../components/Sidebar';

const App = ({ raw, rendered, paneSize, handleUpdate, handleResize }) => (

    <SplitPane className="h-100 v-100 static" split="vertical" minSize={20} defaultSize={paneSize} onChange={handleResize}>
      <Sidebar />

        <SplitPane className="h-100 v-100 static" split="vertical" minSize={20} defaultSize={paneSize} onChange={handleResize}>
          <MarkdownEditor text={raw} handleUpdate={handleUpdate} />
          <MarkdownRendered content={rendered} />
        </SplitPane>

    </SplitPane>

);

App.propTypes = {
  raw: PropTypes.string,
  rendered: PropTypes.string,
  paneSize: PropTypes.number,
  handleUpdate: PropTypes.func,
  handleResize: PropTypes.func
};
const mapStateToProps = state => ({
  raw: state.text.get('raw'),
  rendered: state.text.get('rendered'),
  paneSize: state.ui.get('paneSize')
});
const mapDispatchToProps = dispatch => ({
  handleUpdate: bindActionCreators(updateText, dispatch),
  handleResize: bindActionCreators(resizePane, dispatch)
});
export default connect(mapStateToProps, mapDispatchToProps)(App);
<|endoftext|>


<commit_before>var child_process = require('child_process'),
    fs = require('fs'),
    temp = require('temp'),
    _ = require('underscore'),
    seq = require('seq'),
    urlTools = require('assetgraph/lib/util/urlTools'),
    passError = require('assetgraph/lib/util/passError'),
    AssetGraph = require('assetgraph'),
    postProcessPropertyName = AssetGraph.assets.Css.vendorPrefix + '-image-postprocess';

require('bufferjs');

function pipeThroughChildProcessAndBuffer(command, switches, src, cb) {
    var childProcess = child_process.spawn(command, switches),
        buffers = [];
    childProcess.stdout.on('data', function (buffer) {
        buffers.push(buffer);
    }).on('end', function () {
        cb(null, Buffer.concat(buffers));
    }).on('error', cb);
    childProcess.stdin.write(src);
}

var createFilter = {
    pngquant: function (argStr) {
        var numColors = 256,
            switches = [];
        if (argStr) {
            argStr.split(/\s+/).forEach(function (arg) {
                if (/^\d+$/.test(arg)) {
                    numColors = parseInt(arg, 10);
                } else if (/^-(?:nofs|nofloyd|ordered)$/.test(arg)) {
                    switches.push(arg);
                } else {
                    throw new Error("transforms.postProcessBackgroundImages: Invalid pngquant args: " + args);
                }
            });
        }
        switches.unshift(numColors);
        return function (src, cb) {
            pipeThroughChildProcessAndBuffer('pngquant', switches, src, cb);
        };
    },
    optipng: function (argStr) {
        var commandFragments = ['optipng'];
        if (/^\s*-o\d\s*$/.test(argStr)) {
            commandFragments.push(argStr);
        }
        return function (src, cb) {
            var tmpFileName = temp.path({suffix: '.png'});
            fs.writeFile(tmpFileName, src, null, passError(cb, function () {
                child_process.exec(commandFragments.join(" ") + " " + tmpFileName, passError(cb, function () {
                    fs.readFile(tmpFileName, null, function (err, processedSrc) {
                        fs.unlink(tmpFileName);
                        if (err) {
                            return cb(err);
                        }
                        cb(null, processedSrc);
                    });
                }));
            }));
        };
    },
    pngcrush: function (argStr) {
        var commandFragments = ['pngcrush', '-nofilecheck'];
        argStr.split(/\s+/).forEach(function (arg) {
            if (/^[\w\d\-\.]+$/.test(arg)) {
                commandFragments.push(arg);
            }
        });
        return function (src, cb) {
            var tmpFileName1 = temp.path({suffix: '.png'}),
                tmpFileName2 = temp.path({suffix: '.png'});
            fs.writeFile(tmpFileName1, src, null, passError(cb, function () {
                child_process.exec(commandFragments.join(" ") + " " + tmpFileName1 + " " + tmpFileName2, passError(cb, function () {
                    fs.readFile(tmpFileName2, null, function (err, processedSrc) {
                        fs.unlink(tmpFileName1);
                        fs.unlink(tmpFileName2);
                        if (err) {
                            return cb(err);
                        }
                        cb(null, processedSrc);
                    });
                }));
            }));
        };
    }
};

function applyFilters(buffer, filters, cb) {
    if (filters.length === 0) {
        return process.nextTick(function () {
            cb(null, buffer);
        });
    }

    var nextFilterNum = 0;
    function proceed () {
        if (nextFilterNum < filters.length) {
            var nextFilter = filters[nextFilterNum];
            nextFilterNum += 1;
            nextFilter(buffer, passError(cb, function (resultBuffer) {
                buffer = resultBuffer;
                proceed();
            }));
        } else {
            cb(null, buffer);
        }
    }
    proceed();
}

module.exports = function (queryObj) {
    return function postProcessCssImages(assetGraph, cb) {
        var imageInfos = [];
        assetGraph.findRelations(_.extend({type: 'CssImage'}, queryObj)).forEach(function (relation) {
            var postProcessValue = relation.cssRule.style[postProcessPropertyName];
            if (postProcessValue) {
                if (/^_/.test(relation.propertyName)) {
                    console.warn("transforms.postProcessBackgroundImages warning: Skipping " + relation.propertyName);
                } else {
                    var imageInfo = {
                        filters: [],
                        asset: relation.to,
                        incomingRelations: [relation]
                    };
                    postProcessValue.match(/\w+(?:\([^\)]*\))?/g).forEach(function (commandStr) {
                        if (commandStr.toLowerCase() === 'ie6') {
                            imageInfo.ie6 = true;
                        } else {
                            var commandMatch = commandStr.match(/^(pngquant|pngcrush|optipng)(?:\(([^\)]*)\))?/);
                            if (commandMatch) {
                                imageInfo.filters.push(createFilter[commandMatch[1]](commandMatch[2]));
                            }
                        }
                    });
                    imageInfos.push(imageInfo);
                }
            }
        });
        if (imageInfos.length === 0) {
            return process.nextTick(cb);
        }
        seq(imageInfos)
            .parEach(function (imageInfo, i) {
                applyFilters(imageInfo.asset.rawSrc, imageInfo.filters, this.into("" + i));
            })
            .parEach(function (imageInfo, i) {
                var processedAsset = new AssetGraph.assets.Png({
                    rawSrc: this.vars[i]
                });
                processedAsset.url = urlTools.resolveUrl(assetGraph.root, processedAsset.id + processedAsset.defaultExtension);
                assetGraph.addAsset(processedAsset);
                imageInfo.incomingRelations.forEach(function (incomingRelation) {
                    var style = incomingRelation.cssRule.style;
                    style.removeProperty(postProcessPropertyName);
                    if (imageInfo.ie6) {
                        // Designates that the processed image should only be used in IE6
                        // Keep the original relation and use the underscore hack for getting
                        // IE6 to fetch the processed version:
                        if (('_' + incomingRelation.propertyName) in style) {
                            throw new Error("transforms.postProcessBackgroundImages: Underscore hack already in use in Css rule");
                        }
                        style.setProperty('_' + incomingRelation.propertyName, 'url(...)', style.getPropertyPriority(incomingRelation.propertyName));
                        var relation = new AssetGraph.relations.CssImage({
                            propertyName: '_' + incomingRelation.propertyName,
                            cssRule: incomingRelation.cssRule,
                            from: incomingRelation.from,
                            to: processedAsset
                        });
                        assetGraph.addRelation(relation, 'after', incomingRelation);
                        relation.refreshHref();
                    } else {
                        // All browsers should see the processed version, replace the old relation:
                        var relation = new AssetGraph.relations.CssImage({
                            propertyName: incomingRelation.propertyName,
                            cssRule: incomingRelation.cssRule,
                            from: incomingRelation.from,
                            to: processedAsset
                        });
                        assetGraph.addRelation(relation, 'after', incomingRelation);
                        relation.refreshHref();
                        assetGraph.removeRelation(incomingRelation);
                    }
                    incomingRelation.from.markDirty();
                });
                // Remove original asset if it has become orphaned:
                if (!assetGraph.findRelations({to: imageInfo.asset}).length) {
                    assetGraph.removeAsset(imageInfo.asset);
                }
                this();
            })
            .seq(function () {
                cb();
            })
            ['catch'](cb);
    };
};
<commit_msg>transforms.postProcessCssImages: Output an intelligible error message when failing to run pngquant, pngcrush, or optipng. Closes #2.<commit_after>var child_process = require('child_process'),
    fs = require('fs'),
    temp = require('temp'),
    _ = require('underscore'),
    seq = require('seq'),
    urlTools = require('assetgraph/lib/util/urlTools'),
    passError = require('assetgraph/lib/util/passError'),
    AssetGraph = require('assetgraph'),
    postProcessPropertyName = AssetGraph.assets.Css.vendorPrefix + '-image-postprocess';

require('bufferjs');

function pipeThroughChildProcessAndBuffer(command, switches, src, cb) {
    var childProcess = child_process.spawn(command, switches),
        buffers = [];
    childProcess.stdout.on('data', function (buffer) {
        buffers.push(buffer);
    }).on('end', function () {
        if (buffers.length) {
            cb(null, Buffer.concat(buffers));
        } else {
            cb(new Error('transforms.postProcessCssImages: Error executing "' + [command].concat(switches).join(" ") + '", please make sure ' + command + ' is installed'));
        }
    }).on('error', function (err) {
        cb(new Error('transforms.postProcessCssImages: Error executing "' + [command].concat(switches).join(" ") + '": ' + err.message + ', please make sure ' + command + ' is installed'));
    });
    childProcess.stdin.write(src);
}

var createFilter = {
    pngquant: function (argStr) {
        var numColors = 256,
            switches = [];
        if (argStr) {
            argStr.split(/\s+/).forEach(function (arg) {
                if (/^\d+$/.test(arg)) {
                    numColors = parseInt(arg, 10);
                } else if (/^-(?:nofs|nofloyd|ordered)$/.test(arg)) {
                    switches.push(arg);
                } else {
                    throw new Error("transforms.postProcessCssImages: Invalid pngquant args: " + args);
                }
            });
        }
        switches.unshift(numColors);
        return function (src, cb) {
            pipeThroughChildProcessAndBuffer('pngquant', switches, src, cb);
        };
    },
    optipng: function (argStr) {
        var commandFragments = ['optipng'];
        if (/^\s*-o\d\s*$/.test(argStr)) {
            commandFragments.push(argStr);
        }
        return function (src, cb) {
            var tmpFileName = temp.path({suffix: '.png'});
            fs.writeFile(tmpFileName, src, null, passError(cb, function () {
                child_process.exec(commandFragments.join(" ") + " " + tmpFileName, passError(cb, function () {
                    fs.readFile(tmpFileName, null, function (err, processedSrc) {
                        fs.unlink(tmpFileName);
                        if (err) {
                            return cb(err);
                        }
                        cb(null, processedSrc);
                    });
                }));
            }));
        };
    },
    pngcrush: function (argStr) {
        var commandFragments = ['pngcrush', '-nofilecheck'];
        argStr.split(/\s+/).forEach(function (arg) {
            if (/^[\w\d\-\.]+$/.test(arg)) {
                commandFragments.push(arg);
            }
        });
        return function (src, cb) {
            var tmpFileName1 = temp.path({suffix: '.png'}),
                tmpFileName2 = temp.path({suffix: '.png'});
            fs.writeFile(tmpFileName1, src, null, passError(cb, function () {
                child_process.exec(commandFragments.join(" ") + " " + tmpFileName1 + " " + tmpFileName2, passError(cb, function () {
                    fs.readFile(tmpFileName2, null, function (err, processedSrc) {
                        fs.unlink(tmpFileName1);
                        fs.unlink(tmpFileName2);
                        if (err) {
                            return cb(err);
                        }
                        cb(null, processedSrc);
                    });
                }));
            }));
        };
    }
};

function applyFilters(buffer, filters, cb) {
    if (filters.length === 0) {
        return process.nextTick(function () {
            cb(null, buffer);
        });
    }

    var nextFilterNum = 0;
    function proceed () {
        if (nextFilterNum < filters.length) {
            var nextFilter = filters[nextFilterNum];
            nextFilterNum += 1;
            nextFilter(buffer, passError(cb, function (resultBuffer) {
                buffer = resultBuffer;
                proceed();
            }));
        } else {
            cb(null, buffer);
        }
    }
    proceed();
}

module.exports = function (queryObj) {
    return function postProcessCssImages(assetGraph, cb) {
        var imageInfos = [];
        assetGraph.findRelations(_.extend({type: 'CssImage'}, queryObj)).forEach(function (relation) {
            var postProcessValue = relation.cssRule.style[postProcessPropertyName];
            if (postProcessValue) {
                if (/^_/.test(relation.propertyName)) {
                    console.warn("transforms.postProcessCssImages warning: Skipping " + relation.propertyName);
                } else {
                    var imageInfo = {
                        filters: [],
                        asset: relation.to,
                        incomingRelations: [relation]
                    };
                    postProcessValue.match(/\w+(?:\([^\)]*\))?/g).forEach(function (commandStr) {
                        if (commandStr.toLowerCase() === 'ie6') {
                            imageInfo.ie6 = true;
                        } else {
                            var commandMatch = commandStr.match(/^(pngquant|pngcrush|optipng)(?:\(([^\)]*)\))?/);
                            if (commandMatch) {
                                imageInfo.filters.push(createFilter[commandMatch[1]](commandMatch[2]));
                            }
                        }
                    });
                    imageInfos.push(imageInfo);
                }
            }
        });
        if (imageInfos.length === 0) {
            return process.nextTick(cb);
        }
        seq(imageInfos)
            .parEach(function (imageInfo, i) {
                applyFilters(imageInfo.asset.rawSrc, imageInfo.filters, this.into("" + i));
            })
            .parEach(function (imageInfo, i) {
                var processedAsset = new AssetGraph.assets.Png({
                    rawSrc: this.vars[i]
                });
                processedAsset.url = urlTools.resolveUrl(assetGraph.root, processedAsset.id + processedAsset.defaultExtension);
                assetGraph.addAsset(processedAsset);
                imageInfo.incomingRelations.forEach(function (incomingRelation) {
                    var style = incomingRelation.cssRule.style;
                    style.removeProperty(postProcessPropertyName);
                    if (imageInfo.ie6) {
                        // Designates that the processed image should only be used in IE6
                        // Keep the original relation and use the underscore hack for getting
                        // IE6 to fetch the processed version:
                        if (('_' + incomingRelation.propertyName) in style) {
                            throw new Error("transforms.postProcessCssImages: Underscore hack already in use in Css rule");
                        }
                        style.setProperty('_' + incomingRelation.propertyName, 'url(...)', style.getPropertyPriority(incomingRelation.propertyName));
                        var relation = new AssetGraph.relations.CssImage({
                            propertyName: '_' + incomingRelation.propertyName,
                            cssRule: incomingRelation.cssRule,
                            from: incomingRelation.from,
                            to: processedAsset
                        });
                        assetGraph.addRelation(relation, 'after', incomingRelation);
                        relation.refreshHref();
                    } else {
                        // All browsers should see the processed version, replace the old relation:
                        var relation = new AssetGraph.relations.CssImage({
                            propertyName: incomingRelation.propertyName,
                            cssRule: incomingRelation.cssRule,
                            from: incomingRelation.from,
                            to: processedAsset
                        });
                        assetGraph.addRelation(relation, 'after', incomingRelation);
                        relation.refreshHref();
                        assetGraph.removeRelation(incomingRelation);
                    }
                    incomingRelation.from.markDirty();
                });
                // Remove original asset if it has become orphaned:
                if (!assetGraph.findRelations({to: imageInfo.asset}).length) {
                    assetGraph.removeAsset(imageInfo.asset);
                }
                this();
            })
            .seq(function () {
                cb();
            })
            ['catch'](cb);
    };
};
<|endoftext|>


<commit_before>dojo.provide("dijit._Widget");

dojo.require("dijit._base");

dojo.declare("dijit._Widget", null, {
	//	summary:
	//		The foundation of dijit widgets.
	//
	//	id: String
	//		a unique, opaque ID string that can be assigned by users or by the
	//		system. If the developer passes an ID which is known not to be
	//		unique, the specified ID is ignored and the system-generated ID is
	//		used instead.
	id: "",

	// lang: String
	//	Language to display this widget in (like en-us).
	//	Defaults to brower's specified preferred language (typically the language of the OS)
	lang: "",

	// dir: String
	//  Bi-directional support, as defined by the HTML DIR attribute. Either left-to-right "ltr" or right-to-left "rtl".
	dir: "",

	// class: String
	// HTML class attribute
	"class": "",

	// style: String
	// HTML style attribute
	style: "",

	// title: String
	// HTML title attribute
	title: "",

	// srcNodeRef: DomNode
	//		pointer to original dom node
	srcNodeRef: null,

	// domNode: DomNode
	//		this is our visible representation of the widget! Other DOM
	//		Nodes may by assigned to other properties, usually through the
	//		template system's dojoAttachPonit syntax, but the domNode
	//		property is the canonical "top level" node in widget UI.
	domNode: null,

	// attributeMap: Object
	//		A map of attributes and attachpoints -- typically standard HTML attributes -- to set
	//		on the widget's dom, at the "domNode" attach point, by default.
	//		Other node references can be specified as properties of 'this'
	attributeMap: {id:"", dir:"", lang:"", "class":"", style:"", title:""},  // TODO: add on* handlers?

	//////////// INITIALIZATION METHODS ///////////////////////////////////////
//TODOC: params and srcNodeRef need docs.  Is srcNodeRef optional?
//TODOC: summary needed for postscript
	postscript: function(/*Object?*/params, /*DomNode|String*/srcNodeRef){
		this.create(params, srcNodeRef);
	},

	create: function(/*Object?*/params, /*DomNode|String*/srcNodeRef){
		// summary:
		//		To understand the process by which widgets are instantiated, it
		//		is critical to understand what other methods create calls and
		//		which of them you'll want to override. Of course, adventurous
		//		developers could override create entirely, but this should
		//		only be done as a last resort.
		//
		//		Below is a list of the methods that are called, in the order
		//		they are fired, along with notes about what they do and if/when
		//		you should over-ride them in your widget:
		//
		//		|	* postMixInProperties
		//		|		a stub function that you can over-ride to modify
		//		|		variables that may have been naively assigned by
		//		|		mixInProperties
		//		|	# widget is added to manager object here
		//		|	* buildRendering
		//		|		Subclasses use this method to handle all UI initialization
		//		|		Sets this.domNode.  Templated widgets do this automatically
		//		|		and otherwise it just uses the source dom node.
		//		|	* postCreate
		//		|		a stub function that you can over-ride to modify take
		//		|		actions once the widget has been placed in the UI

		// store pointer to original dom tree
		this.srcNodeRef = dojo.byId(srcNodeRef);

		// For garbage collection.  An array of handles returned by Widget.connect()
		// Each handle returned from Widget.connect() is an array of handles from dojo.connect()
		this._connects=[];

		// _attaches: String[]
		// 		names of all our dojoAttachPoint variables
		this._attaches=[];

		//mixin our passed parameters
		if(this.srcNodeRef && (typeof this.srcNodeRef.id == "string")){ this.id = this.srcNodeRef.id; }
		if(params){
			this.params = params;
			dojo.mixin(this,params);
		}
		this.postMixInProperties();

		// generate an id for the widget if one wasn't specified
		// (be sure to do this before buildRendering() because that function might
		// expect the id to be there.
		if(!this.id){
			this.id=dijit.getUniqueId(this.declaredClass.replace(/\./g,"_"));
		}
		dijit.registry.add(this);

		this.buildRendering();

		// Copy attributes listed in attributeMap into the [newly created] DOM for the widget.
		// The placement of these attributes is according to the property mapping in attributeMap.
		// Note special handling for 'style' and 'class' attributes which are lists and can
		// have elements from both old and new structures, and some attributes like "type"
		// cannot be processed this way as they are not mutable.
		if(this.domNode){
			for(var attr in this.attributeMap){
				var value = this[attr];
				if(typeof value != "object" && ((value !== "" && value !== false) || (params && params[attr]))){
					this.setAttribute(attr, value);
				}
			}
		}

		if(this.domNode){
			this.domNode.setAttribute("widgetId", this.id);
		}
		this.postCreate();

		// If srcNodeRef has been processed and removed from the DOM (e.g. TemplatedWidget) then delete it to allow GC.
		if(this.srcNodeRef && !this.srcNodeRef.parentNode){
			delete this.srcNodeRef;
		}
	},

	postMixInProperties: function(){
		// summary
		//	Called after the parameters to the widget have been read-in,
		//	but before the widget template is instantiated.
		//	Especially useful to set properties that are referenced in the widget template.
	},

	buildRendering: function(){
		// summary:
		//		Construct the UI for this widget, setting this.domNode.
		//		Most widgets will mixin TemplatedWidget, which overrides this method.
		this.domNode = this.srcNodeRef || dojo.doc.createElement('div');
	},

	postCreate: function(){
		// summary:
		//		Called after a widget's dom has been setup
	},

	startup: function(){
		// summary:
		//		Called after a widget's children, and other widgets on the page, have been created.
		//		Provides an opportunity to manipulate any children before they are displayed.
		//		This is useful for composite widgets that need to control or layout sub-widgets.
		//		Many layout widgets can use this as a wiring phase.
		this._started = true;
	},

	//////////// DESTROY FUNCTIONS ////////////////////////////////

	destroyRecursive: function(/*Boolean*/ finalize){
		// summary:
		// 		Destroy this widget and it's descendants. This is the generic
		// 		"destructor" function that all widget users should call to
		// 		cleanly discard with a widget. Once a widget is destroyed, it's
		// 		removed from the manager object.
		// finalize: Boolean
		//		is this function being called part of global environment
		//		tear-down?

		this.destroyDescendants();
		this.destroy();
	},

	destroy: function(/*Boolean*/ finalize){
		// summary:
		// 		Destroy this widget, but not its descendants
		// finalize: Boolean
		//		is this function being called part of global environment
		//		tear-down?

		this.uninitialize();
		dojo.forEach(this._connects, function(array){
			dojo.forEach(array, dojo.disconnect);
		});

		// destroy widgets created as part of template, etc.
		dojo.forEach(this._supportingWidgets || [], function(w){ w.destroy(); });

		this.destroyRendering(finalize);
		dijit.registry.remove(this.id);
	},

	destroyRendering: function(/*Boolean*/ finalize){
		// summary:
		//		Destroys the DOM nodes associated with this widget
		// finalize: Boolean
		//		is this function being called part of global environment
		//		tear-down?

		if(this.bgIframe){
			this.bgIframe.destroy();
			delete this.bgIframe;
		}

		if(this.domNode){
			dojo._destroyElement(this.domNode);
			delete this.domNode;
		}

		if(this.srcNodeRef){
			dojo._destroyElement(this.srcNodeRef);
			delete this.srcNodeRef;
		}
	},

	destroyDescendants: function(){
		// summary:
		//		Recursively destroy the children of this widget and their
		//		descendants.

		// TODO: should I destroy in the reverse order, to go bottom up?
		dojo.forEach(this.getDescendants(), function(widget){ widget.destroy(); });
	},

	uninitialize: function(){
		// summary:
		//		stub function. Override to implement custom widget tear-down
		//		behavior.
		return false;
	},

	////////////////// MISCELLANEOUS METHODS ///////////////////

	onFocus: function(){
		// summary:
		//		stub function. Override or connect to this method to receive
		//		notifications for when the widget moves into focus.
	},

	onBlur: function(){
		// summary:
		//		stub function. Override or connect to this method to receive
		//		notifications for when the widget moves out of focus.
	},

	_onFocus: function(e){
		this.onFocus();
	},

	_onBlur: function(){
		this.onBlur();
	},

	setAttribute: function(/*String*/ attr, /*anything*/ value){
		// summary
		//		Set native HTML attributes reflected in the widget,
		//		such as readOnly, disabled, and maxLength in TextBox widgets.
		// description
		//		In general, a widget's "value" is controlled via setValue()/getValue(),
		//		rather than this method.  The exception is for widgets where the
		//		end user can't adjust the value, such as Button and CheckBox;
		//		in the unusual case that you want to change the value attribute of
		//		those widgets, use setAttribute().
		var mapNode = this[this.attributeMap[attr]||'domNode'];
		this[attr] = value;
		switch(attr){
			case "class":
				dojo.addClass(mapNode, value);
				break;
			case "style":
				if(mapNode.style.cssText){
					mapNode.style.cssText += "; " + value;// FIXME: Opera
				}else{
					mapNode.style.cssText = value;
				}
				break;
			default:
				if(/^on[A-Z]/.test(attr)){ // eg. onSubmit needs to be onsubmit
					attr = attr.toLowerCase();
				}
				if(typeof value == "function"){ // functions execute in the context of the widget
					value = dojo.hitch(this, value);
				}
				dojo.attr(mapNode, attr, value);
		}
	},

	toString: function(){
		// summary:
		//		returns a string that represents the widget. When a widget is
		//		cast to a string, this method will be used to generate the
		//		output. Currently, it does not implement any sort of reversable
		//		serialization.
		return '[Widget ' + this.declaredClass + ', ' + (this.id || 'NO ID') + ']'; // String
	},

	getDescendants: function(){
		// summary:
		//	Returns all the widgets that contained by this, i.e., all widgets underneath this.containerNode.
		if(this.containerNode){
			var list= dojo.query('[widgetId]', this.containerNode);
			return list.map(dijit.byNode);		// Array
		}else{
			return [];
		}
	},

//TODOC
	nodesWithKeyClick: ["input", "button"],

	connect: function(
			/*Object|null*/ obj,
			/*String*/ event,
			/*String|Function*/ method){
		//	summary:
		//		Connects specified obj/event to specified method of this object
		//		and registers for disconnect() on widget destroy.
		//		Special event: "ondijitclick" triggers on a click or enter-down or space-up
		//		Similar to dojo.connect() but takes three arguments rather than four.
		var handles =[];
		if(event == "ondijitclick"){
			// add key based click activation for unsupported nodes.
			if(!this.nodesWithKeyClick[obj.nodeName]){
				handles.push(dojo.connect(obj, "onkeydown", this,
					function(e){
						if(e.keyCode == dojo.keys.ENTER){
							return (dojo.isString(method))?
								this[method](e) : method.call(this, e);
						}else if(e.keyCode == dojo.keys.SPACE){
							// stop space down as it causes IE to scroll
							// the browser window
							dojo.stopEvent(e);
						}
			 		}));
				handles.push(dojo.connect(obj, "onkeyup", this,
					function(e){
						if(e.keyCode == dojo.keys.SPACE){
							return dojo.isString(method) ?
								this[method](e) : method.call(this, e);
						}
			 		}));
			}
			event = "onclick";
		}
		handles.push(dojo.connect(obj, event, this, method));

		// return handles for FormElement and ComboBox
		this._connects.push(handles);
		return handles;
	},

	disconnect: function(/*Object*/ handles){
		// summary:
		//		Disconnects handle created by this.connect.
		//		Also removes handle from this widget's list of connects
		for(var i=0; i<this._connects.length; i++){
			if(this._connects[i]==handles){
				dojo.forEach(handles, dojo.disconnect);
				this._connects.splice(i, 1);
				return;
			}
		}
	},

	isLeftToRight: function(){
		// summary:
		//		Checks the DOM to for the text direction for bi-directional support
		// description:
		//		This method cannot be used during widget construction because the widget
		//		must first be connected to the DOM tree.  Parent nodes are searched for the
		//		'dir' attribute until one is found, otherwise left to right mode is assumed.
		//		See HTML spec, DIR attribute for more information.

		if(!("_ltr" in this)){
			this._ltr = dojo.getComputedStyle(this.domNode).direction != "rtl";
		}
		return this._ltr; //Boolean
	},

	isFocusable: function(){
		// summary:
		//		Return true if this widget can currently be focused
		//		and false if not
		return this.focus && (dojo.style(this.domNode, "display") != "none");
	}
});
<commit_msg>Provide build flag so Widget.js doesn't pull in all of dijit._base. The is an intermim solution for making lightweight builds (especially for custom widgets) that don't use all the code in dijit/base. Refs #6101.  !strict<commit_after>dojo.provide("dijit._Widget");

//>>excludeStart("dijitBaseExclude", kwArgs.customDijitBase == "true");
dojo.require( "dijit._base" );
//>>excludeEnd("dijitBaseExclude");

dojo.declare("dijit._Widget", null, {
	//	summary:
	//		The foundation of dijit widgets.
	//
	//	id: String
	//		a unique, opaque ID string that can be assigned by users or by the
	//		system. If the developer passes an ID which is known not to be
	//		unique, the specified ID is ignored and the system-generated ID is
	//		used instead.
	id: "",

	// lang: String
	//	Language to display this widget in (like en-us).
	//	Defaults to brower's specified preferred language (typically the language of the OS)
	lang: "",

	// dir: String
	//  Bi-directional support, as defined by the HTML DIR attribute. Either left-to-right "ltr" or right-to-left "rtl".
	dir: "",

	// class: String
	// HTML class attribute
	"class": "",

	// style: String
	// HTML style attribute
	style: "",

	// title: String
	// HTML title attribute
	title: "",

	// srcNodeRef: DomNode
	//		pointer to original dom node
	srcNodeRef: null,

	// domNode: DomNode
	//		this is our visible representation of the widget! Other DOM
	//		Nodes may by assigned to other properties, usually through the
	//		template system's dojoAttachPonit syntax, but the domNode
	//		property is the canonical "top level" node in widget UI.
	domNode: null,

	// attributeMap: Object
	//		A map of attributes and attachpoints -- typically standard HTML attributes -- to set
	//		on the widget's dom, at the "domNode" attach point, by default.
	//		Other node references can be specified as properties of 'this'
	attributeMap: {id:"", dir:"", lang:"", "class":"", style:"", title:""},  // TODO: add on* handlers?

	//////////// INITIALIZATION METHODS ///////////////////////////////////////
//TODOC: params and srcNodeRef need docs.  Is srcNodeRef optional?
//TODOC: summary needed for postscript
	postscript: function(/*Object?*/params, /*DomNode|String*/srcNodeRef){
		this.create(params, srcNodeRef);
	},

	create: function(/*Object?*/params, /*DomNode|String*/srcNodeRef){
		// summary:
		//		To understand the process by which widgets are instantiated, it
		//		is critical to understand what other methods create calls and
		//		which of them you'll want to override. Of course, adventurous
		//		developers could override create entirely, but this should
		//		only be done as a last resort.
		//
		//		Below is a list of the methods that are called, in the order
		//		they are fired, along with notes about what they do and if/when
		//		you should over-ride them in your widget:
		//
		//		|	* postMixInProperties
		//		|		a stub function that you can over-ride to modify
		//		|		variables that may have been naively assigned by
		//		|		mixInProperties
		//		|	# widget is added to manager object here
		//		|	* buildRendering
		//		|		Subclasses use this method to handle all UI initialization
		//		|		Sets this.domNode.  Templated widgets do this automatically
		//		|		and otherwise it just uses the source dom node.
		//		|	* postCreate
		//		|		a stub function that you can over-ride to modify take
		//		|		actions once the widget has been placed in the UI

		// store pointer to original dom tree
		this.srcNodeRef = dojo.byId(srcNodeRef);

		// For garbage collection.  An array of handles returned by Widget.connect()
		// Each handle returned from Widget.connect() is an array of handles from dojo.connect()
		this._connects=[];

		// _attaches: String[]
		// 		names of all our dojoAttachPoint variables
		this._attaches=[];

		//mixin our passed parameters
		if(this.srcNodeRef && (typeof this.srcNodeRef.id == "string")){ this.id = this.srcNodeRef.id; }
		if(params){
			this.params = params;
			dojo.mixin(this,params);
		}
		this.postMixInProperties();

		// generate an id for the widget if one wasn't specified
		// (be sure to do this before buildRendering() because that function might
		// expect the id to be there.
		if(!this.id){
			this.id=dijit.getUniqueId(this.declaredClass.replace(/\./g,"_"));
		}
		dijit.registry.add(this);

		this.buildRendering();

		// Copy attributes listed in attributeMap into the [newly created] DOM for the widget.
		// The placement of these attributes is according to the property mapping in attributeMap.
		// Note special handling for 'style' and 'class' attributes which are lists and can
		// have elements from both old and new structures, and some attributes like "type"
		// cannot be processed this way as they are not mutable.
		if(this.domNode){
			for(var attr in this.attributeMap){
				var value = this[attr];
				if(typeof value != "object" && ((value !== "" && value !== false) || (params && params[attr]))){
					this.setAttribute(attr, value);
				}
			}
		}

		if(this.domNode){
			this.domNode.setAttribute("widgetId", this.id);
		}
		this.postCreate();

		// If srcNodeRef has been processed and removed from the DOM (e.g. TemplatedWidget) then delete it to allow GC.
		if(this.srcNodeRef && !this.srcNodeRef.parentNode){
			delete this.srcNodeRef;
		}
	},

	postMixInProperties: function(){
		// summary
		//	Called after the parameters to the widget have been read-in,
		//	but before the widget template is instantiated.
		//	Especially useful to set properties that are referenced in the widget template.
	},

	buildRendering: function(){
		// summary:
		//		Construct the UI for this widget, setting this.domNode.
		//		Most widgets will mixin TemplatedWidget, which overrides this method.
		this.domNode = this.srcNodeRef || dojo.doc.createElement('div');
	},

	postCreate: function(){
		// summary:
		//		Called after a widget's dom has been setup
	},

	startup: function(){
		// summary:
		//		Called after a widget's children, and other widgets on the page, have been created.
		//		Provides an opportunity to manipulate any children before they are displayed.
		//		This is useful for composite widgets that need to control or layout sub-widgets.
		//		Many layout widgets can use this as a wiring phase.
		this._started = true;
	},

	//////////// DESTROY FUNCTIONS ////////////////////////////////

	destroyRecursive: function(/*Boolean*/ finalize){
		// summary:
		// 		Destroy this widget and it's descendants. This is the generic
		// 		"destructor" function that all widget users should call to
		// 		cleanly discard with a widget. Once a widget is destroyed, it's
		// 		removed from the manager object.
		// finalize: Boolean
		//		is this function being called part of global environment
		//		tear-down?

		this.destroyDescendants();
		this.destroy();
	},

	destroy: function(/*Boolean*/ finalize){
		// summary:
		// 		Destroy this widget, but not its descendants
		// finalize: Boolean
		//		is this function being called part of global environment
		//		tear-down?

		this.uninitialize();
		dojo.forEach(this._connects, function(array){
			dojo.forEach(array, dojo.disconnect);
		});

		// destroy widgets created as part of template, etc.
		dojo.forEach(this._supportingWidgets || [], function(w){ w.destroy(); });

		this.destroyRendering(finalize);
		dijit.registry.remove(this.id);
	},

	destroyRendering: function(/*Boolean*/ finalize){
		// summary:
		//		Destroys the DOM nodes associated with this widget
		// finalize: Boolean
		//		is this function being called part of global environment
		//		tear-down?

		if(this.bgIframe){
			this.bgIframe.destroy();
			delete this.bgIframe;
		}

		if(this.domNode){
			dojo._destroyElement(this.domNode);
			delete this.domNode;
		}

		if(this.srcNodeRef){
			dojo._destroyElement(this.srcNodeRef);
			delete this.srcNodeRef;
		}
	},

	destroyDescendants: function(){
		// summary:
		//		Recursively destroy the children of this widget and their
		//		descendants.

		// TODO: should I destroy in the reverse order, to go bottom up?
		dojo.forEach(this.getDescendants(), function(widget){ widget.destroy(); });
	},

	uninitialize: function(){
		// summary:
		//		stub function. Override to implement custom widget tear-down
		//		behavior.
		return false;
	},

	////////////////// MISCELLANEOUS METHODS ///////////////////

	onFocus: function(){
		// summary:
		//		stub function. Override or connect to this method to receive
		//		notifications for when the widget moves into focus.
	},

	onBlur: function(){
		// summary:
		//		stub function. Override or connect to this method to receive
		//		notifications for when the widget moves out of focus.
	},

	_onFocus: function(e){
		this.onFocus();
	},

	_onBlur: function(){
		this.onBlur();
	},

	setAttribute: function(/*String*/ attr, /*anything*/ value){
		// summary
		//		Set native HTML attributes reflected in the widget,
		//		such as readOnly, disabled, and maxLength in TextBox widgets.
		// description
		//		In general, a widget's "value" is controlled via setValue()/getValue(),
		//		rather than this method.  The exception is for widgets where the
		//		end user can't adjust the value, such as Button and CheckBox;
		//		in the unusual case that you want to change the value attribute of
		//		those widgets, use setAttribute().
		var mapNode = this[this.attributeMap[attr]||'domNode'];
		this[attr] = value;
		switch(attr){
			case "class":
				dojo.addClass(mapNode, value);
				break;
			case "style":
				if(mapNode.style.cssText){
					mapNode.style.cssText += "; " + value;// FIXME: Opera
				}else{
					mapNode.style.cssText = value;
				}
				break;
			default:
				if(/^on[A-Z]/.test(attr)){ // eg. onSubmit needs to be onsubmit
					attr = attr.toLowerCase();
				}
				if(typeof value == "function"){ // functions execute in the context of the widget
					value = dojo.hitch(this, value);
				}
				dojo.attr(mapNode, attr, value);
		}
	},

	toString: function(){
		// summary:
		//		returns a string that represents the widget. When a widget is
		//		cast to a string, this method will be used to generate the
		//		output. Currently, it does not implement any sort of reversable
		//		serialization.
		return '[Widget ' + this.declaredClass + ', ' + (this.id || 'NO ID') + ']'; // String
	},

	getDescendants: function(){
		// summary:
		//	Returns all the widgets that contained by this, i.e., all widgets underneath this.containerNode.
		if(this.containerNode){
			var list= dojo.query('[widgetId]', this.containerNode);
			return list.map(dijit.byNode);		// Array
		}else{
			return [];
		}
	},

//TODOC
	nodesWithKeyClick: ["input", "button"],

	connect: function(
			/*Object|null*/ obj,
			/*String*/ event,
			/*String|Function*/ method){
		//	summary:
		//		Connects specified obj/event to specified method of this object
		//		and registers for disconnect() on widget destroy.
		//		Special event: "ondijitclick" triggers on a click or enter-down or space-up
		//		Similar to dojo.connect() but takes three arguments rather than four.
		var handles =[];
		if(event == "ondijitclick"){
			// add key based click activation for unsupported nodes.
			if(!this.nodesWithKeyClick[obj.nodeName]){
				handles.push(dojo.connect(obj, "onkeydown", this,
					function(e){
						if(e.keyCode == dojo.keys.ENTER){
							return (dojo.isString(method))?
								this[method](e) : method.call(this, e);
						}else if(e.keyCode == dojo.keys.SPACE){
							// stop space down as it causes IE to scroll
							// the browser window
							dojo.stopEvent(e);
						}
			 		}));
				handles.push(dojo.connect(obj, "onkeyup", this,
					function(e){
						if(e.keyCode == dojo.keys.SPACE){
							return dojo.isString(method) ?
								this[method](e) : method.call(this, e);
						}
			 		}));
			}
			event = "onclick";
		}
		handles.push(dojo.connect(obj, event, this, method));

		// return handles for FormElement and ComboBox
		this._connects.push(handles);
		return handles;
	},

	disconnect: function(/*Object*/ handles){
		// summary:
		//		Disconnects handle created by this.connect.
		//		Also removes handle from this widget's list of connects
		for(var i=0; i<this._connects.length; i++){
			if(this._connects[i]==handles){
				dojo.forEach(handles, dojo.disconnect);
				this._connects.splice(i, 1);
				return;
			}
		}
	},

	isLeftToRight: function(){
		// summary:
		//		Checks the DOM to for the text direction for bi-directional support
		// description:
		//		This method cannot be used during widget construction because the widget
		//		must first be connected to the DOM tree.  Parent nodes are searched for the
		//		'dir' attribute until one is found, otherwise left to right mode is assumed.
		//		See HTML spec, DIR attribute for more information.

		if(!("_ltr" in this)){
			this._ltr = dojo.getComputedStyle(this.domNode).direction != "rtl";
		}
		return this._ltr; //Boolean
	},

	isFocusable: function(){
		// summary:
		//		Return true if this widget can currently be focused
		//		and false if not
		return this.focus && (dojo.style(this.domNode, "display") != "none");
	}
});
<|endoftext|>


<commit_before>import { API } from 'api/index';
import React from 'react';
import cx from 'classnames';

import { MethodLabel } from 'ui/components/method-label';
import { ActiveIndicator } from 'ui/components/active-indicator';
import { CapturedRequestDetails } from 'ui/components/details-panel-types/captured-request-details';
import { CapturedMockedRequestDetails } from 'ui/components/details-panel-types/captured-mocked-request-details';

export class CapturedRequests extends React.Component {

  constructor(props) {
    super(props);

    this.state = {
      hideMockedRequests: false
    };
  }

  _mockRequest(request) {
    API.mockRequest(request);
  }

  _isAlreadyMocked(request) {
    return !!this._getMockedRequest(request);
  }

  _getMockedRequest(request) {
    return API.activeMockedRequests
      .filter((mockedRequest) => mockedRequest.requestHash === request.requestHash)[0];
  }

  _actionButtonFor(request) {
    return this._isAlreadyMocked(request)
      ? <button className="btn btn-edit">Edit</button>
      : <button className="btn btn-mock" onClick={ this._mockRequest.bind(this, request) }>Mock</button>;
  }

  _requestDetailsFor(request) {
    if (this._isAlreadyMocked(request)) {
      const mockedRequest = this._getMockedRequest(request);

      return mockedRequest.states
        .filter((state) => state.id === mockedRequest.selectedStateId)[0]
        .name;
    }

    return request.url;
  }

  _toggleHideMockedRequests(event) {
    this.setState({ hideMockedRequests: event.target.checked });
  }

  _selectRequest(request) {
    this.setState({ selectedRequest: request });
  }

  _requestRowClassName(requestId) {
    const isSelected = this.state.selectedRequest
      ? this.state.selectedRequest.id === requestId
      : false;

    return cx('request-row', { selected: isSelected });
  }

  _requestsList() {
    const requestList = this.state.hideMockedRequests
      ? API.capturedRequests.filter((request) => !this._isAlreadyMocked(request))
      : API.capturedRequests;

    return requestList.map((request) => {
      return (

            <ActiveIndicator active={ this._isAlreadyMocked(request) }
                             onEnable={ () => {} }
                             onDisable={ () => {} }/>
            <MethodLabel method={ request.method }/>
            { this._requestDetailsFor(request) }

          { this._actionButtonFor(request) }

      );
    })
  }

  _requestDetails() {
    if (!this.state.selectedRequest) {
      return null;
    }

    return this._isAlreadyMocked(this.state.selectedRequest)
      ? <CapturedMockedRequestDetails request={ this._getMockedRequest(this.state.selectedRequest) }/>
      : <CapturedRequestDetails request={ this.state.selectedRequest }/>;
  }

  render() {
    return (

All captured requests

          <input type="checkbox"
                 className="margin-bottom-20"
                 onChange={ this._toggleHideMockedRequests.bind(this) }/>
          Hide mocked requests

        { this._requestsList() }
        { this._requestDetails() }

    );
  }
}
<commit_msg>format url in captured requests<commit_after>import { API } from 'api/index';
import React from 'react';
import cx from 'classnames';

import { MethodLabel } from 'ui/components/method-label';
import { Url } from 'ui/components/url';
import { ActiveIndicator } from 'ui/components/active-indicator';
import { CapturedRequestDetails } from 'ui/components/details-panel-types/captured-request-details';
import { CapturedMockedRequestDetails } from 'ui/components/details-panel-types/captured-mocked-request-details';

export class CapturedRequests extends React.Component {

  constructor(props) {
    super(props);

    this.state = {
      hideMockedRequests: false
    };
  }

  _mockRequest(request) {
    API.mockRequest(request);
  }

  _isAlreadyMocked(request) {
    return !!this._getMockedRequest(request);
  }

  _getMockedRequest(request) {
    return API.activeMockedRequests
      .filter((mockedRequest) => mockedRequest.requestHash === request.requestHash)[0];
  }

  _actionButtonFor(request) {
    return this._isAlreadyMocked(request)
      ? <button className="btn btn-edit">Edit</button>
      : <button className="btn btn-mock" onClick={ this._mockRequest.bind(this, request) }>Mock</button>;
  }

  _requestDetailsFor(request) {
    if (this._isAlreadyMocked(request)) {
      const mockedRequest = this._getMockedRequest(request);

      return mockedRequest.states
        .filter((state) => state.id === mockedRequest.selectedStateId)[0]
        .name;
    }

    return <Url url={ request.url }/>;
  }

  _toggleHideMockedRequests(event) {
    this.setState({ hideMockedRequests: event.target.checked });
  }

  _selectRequest(request) {
    this.setState({ selectedRequest: request });
  }

  _requestRowClassName(requestId) {
    const isSelected = this.state.selectedRequest
      ? this.state.selectedRequest.id === requestId
      : false;

    return cx('request-row', { selected: isSelected });
  }

  _activeIndicator(request) {
    if (!this._isAlreadyMocked(request)) {
      return null;
    }

    return (
      <ActiveIndicator active={ this._getMockedRequest(request).active }
                       onEnable={ () => {} }
                       onDisable={ () => {} }/>
    );
  }

  _requestsList() {
    const requestList = this.state.hideMockedRequests
      ? API.capturedRequests.filter((request) => !this._isAlreadyMocked(request))
      : API.capturedRequests;

    return requestList.map((request) => {
      return (

            { this._activeIndicator(request) }
            <MethodLabel method={ request.method }/>
            { this._requestDetailsFor(request) }

          { this._actionButtonFor(request) }

      );
    })
  }

  _requestDetails() {
    if (!this.state.selectedRequest) {
      return null;
    }

    return this._isAlreadyMocked(this.state.selectedRequest)
      ? <CapturedMockedRequestDetails request={ this._getMockedRequest(this.state.selectedRequest) }/>
      : <CapturedRequestDetails request={ this.state.selectedRequest }/>;
  }

  render() {
    return (

All captured requests

          <input type="checkbox"
                 className="margin-bottom-20"
                 onChange={ this._toggleHideMockedRequests.bind(this) }/>
          Hide mocked requests

        { this._requestsList() }
        { this._requestDetails() }

    );
  }
}
<|endoftext|>


<commit_before>L.Control.Liveupdate = L.Control.extend({

    timer: false,

    options: {
        position: 'topleft',
        title: {
            'false': 'Start live updates',
            'true': 'Stop live updates'
        },
        is_updating: true,
        update_track: false,  // callback function
				interval: 10000
    },

    onAdd: function (map) {
        this.container = L.DomUtil.create('div', 'leaflet-control-liveupdate leaflet-bar leaflet-control');

        this.link = L.DomUtil.create('a', 'leaflet-control-liveupdate-button leaflet-bar-part', this.container);
        this.link.href = '#';

        this._map = map;
        this._setUpdating(this.options.is_updating);

        L.DomEvent.on(this.link, 'click', this._click, this);
        return this.container;
    },

    _click: function (e) {
        L.DomEvent.stopPropagation(e);
        L.DomEvent.preventDefault(e);
        this.toggleUpdating();
    },

    _toggleTitle: function() {
        this.link.title = this.options.title[this.isUpdating()];
    },

    isUpdating: function () {
        return this._isUpdating || false;
    },

    _setUpdating: function (updating) {
        this._isUpdating = updating;
        if (updating) {
            L.DomUtil.addClass(this.container, 'leaflet-liveupdate-on');
        } else {
            L.DomUtil.removeClass(this.container, 'leaflet-liveupdate-on');
        }
        this._toggleTitle();
    },

    toggleUpdating: function () {
        if (this.isUpdating ()) {
            this.stopUpdating ();
            a = 'stopped';
        }
        else {
            this.startUpdating ();
            a = 'started';
        }
        this._map.messagebox.show('Live updates ' + a);
    },

    startUpdating: function () {

        var map = this._map;
        var update_track = this.options.update_track;
        var _this = this;

        this._setUpdating(true);
        update_track(this);
        this.timer = setInterval(function() {
            update_track (_this);
        }, this.options.interval);
    },

    stopUpdating: function () {
        this._setUpdating(false);
        clearInterval(this.timer);
        this.timer = false;
    }

});

L.control.liveupdate = function (options) {
    return new L.Control.Liveupdate(options);
};
<commit_msg>Fix callback variable name and check for messagebox before using<commit_after>L.Control.Liveupdate = L.Control.extend({

    timer: false,

    options: {
        position: 'topleft',
        title: {
            'false': 'Start live updates',
            'true': 'Stop live updates'
        },
        is_updating: true,
        update_map: false,  // callback function
        interval: 10000
    },

    onAdd: function (map) {
        this.container = L.DomUtil.create('div', 'leaflet-control-liveupdate leaflet-bar leaflet-control');

        this.link = L.DomUtil.create('a', 'leaflet-control-liveupdate-button leaflet-bar-part', this.container);
        this.link.href = '#';

        this._map = map;
        this._setUpdating(this.options.is_updating);

        L.DomEvent.on(this.link, 'click', this._click, this);
        return this.container;
    },

    _click: function (e) {
        L.DomEvent.stopPropagation(e);
        L.DomEvent.preventDefault(e);
        this.toggleUpdating();
    },

    _toggleTitle: function() {
        this.link.title = this.options.title[this.isUpdating()];
    },

    isUpdating: function () {
        return this._isUpdating || false;
    },

    _setUpdating: function (updating) {
        this._isUpdating = updating;
        if (updating) {
            L.DomUtil.addClass(this.container, 'leaflet-liveupdate-on');
        } else {
            L.DomUtil.removeClass(this.container, 'leaflet-liveupdate-on');
        }
        this._toggleTitle();
    },

    toggleUpdating: function () {
        if (this.isUpdating ()) {
            this.stopUpdating ();
            a = 'stopped';
        }
        else {
            this.startUpdating ();
            a = 'started';
        }
        if (this._map.messagebox) {
            this._map.messagebox.show('Live updates ' + a);
        }
    },

    startUpdating: function () {

        var map = this._map;
        var update_map = this.options.update_map;
        var _this = this;

        this._setUpdating(true);
        update_map(this);
        this.timer = setInterval(function() {
            update_map(_this);
        }, this.options.interval);
    },

    stopUpdating: function () {
        this._setUpdating(false);
        clearInterval(this.timer);
        this.timer = false;
    }

});

L.control.liveupdate = function (options) {
    return new L.Control.Liveupdate(options);
};
<|endoftext|>