1
0
mirror of https://github.com/sismics/docs.git synced 2025-12-13 09:46:17 +00:00

Initial commit

This commit is contained in:
jendib
2013-07-27 18:33:20 +02:00
parent 41cb6dd9ae
commit 9b74bd8194
156 changed files with 72879 additions and 0 deletions

325
docs-web/pom.xml Normal file
View File

@@ -0,0 +1,325 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>com.sismics.docs</groupId>
<artifactId>docs-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../docs-parent</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>docs-web</artifactId>
<packaging>war</packaging>
<name>Docs Web</name>
<dependencies>
<!-- Dependencies to Docs -->
<dependency>
<groupId>com.sismics.docs</groupId>
<artifactId>docs-core</artifactId>
</dependency>
<dependency>
<groupId>com.sismics.docs</groupId>
<artifactId>docs-web-common</artifactId>
</dependency>
<!-- Dependencies to Jersey -->
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-server</artifactId>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-bundle</artifactId>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-json</artifactId>
</dependency>
<dependency>
<groupId>com.sun.jersey.contribs</groupId>
<artifactId>jersey-multipart</artifactId>
</dependency>
<!-- Other external dependencies -->
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
</dependency>
<dependency>
<groupId>org.mortbay.jetty</groupId>
<artifactId>servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
</dependency>
<!-- Test dependencies -->
<dependency>
<groupId>com.sismics.docs</groupId>
<artifactId>docs-web-common</artifactId>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.sun.grizzly</groupId>
<artifactId>grizzly-servlet-webserver</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.sun.jersey.jersey-test-framework</groupId>
<artifactId>jersey-test-framework-grizzly2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.subethamail</groupId>
<artifactId>subethasmtp-wiser</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<configuration>
<systemProperties>
<systemProperty>
<name>webapp.root</name>
<value>${basedir}/src/main/webapp</value>
</systemProperty>
</systemProperties>
<scanIntervalSeconds>0</scanIntervalSeconds>
<webAppConfig>
<contextPath>/docs-web</contextPath>
<extraClasspath>target/classes;../docs-core/target/classes</extraClasspath>
<overrideDescriptor>src/dev/main/webapp/web-override.xml</overrideDescriptor>
</webAppConfig>
</configuration>
</plugin>
<!-- Cargo Plugin -->
<plugin>
<groupId>org.codehaus.cargo</groupId>
<artifactId>cargo-maven2-plugin</artifactId>
<configuration>
<container>
<containerId>jetty8x</containerId>
<type>remote</type>
</container>
<deployer>
<type>remote</type>
</deployer>
<deployables>
<deployable>
<groupId>com.sismics.docs</groupId>
<artifactId>docs-web</artifactId>
<type>war</type>
<properties>
<context>/docs</context>
</properties>
<pingURL>http://localhost:8080/docs/index.html</pingURL>
</deployable>
</deployables>
</configuration>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>com.samaxes.maven</groupId>
<artifactId>
minify-maven-plugin
</artifactId>
<versionRange>[1.5,)</versionRange>
<goals>
<goal>minify</goal>
</goals>
</pluginExecutionFilter>
<action>
<ignore />
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
<profiles>
<!-- Development profile (active by default) -->
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
<property>
<name>env</name>
<value>dev</value>
</property>
</activation>
<build>
<resources>
<resource>
<directory>src/dev/resources</directory>
<filtering>false</filtering>
<excludes>
<exclude>**/config.properties</exclude>
</excludes>
</resource>
<resource>
<directory>src/dev/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/config.properties</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<configuration>
<scanIntervalSeconds>0</scanIntervalSeconds>
<webAppConfig>
<contextPath>/docs-web</contextPath>
<extraClasspath>target/classes;../docs-core/target/classes</extraClasspath>
<overrideDescriptor>src/dev/main/webapp/web-override.xml</overrideDescriptor>
</webAppConfig>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<!-- Production profile -->
<profile>
<id>prod</id>
<activation>
<property>
<name>env</name>
<value>prod</value>
</property>
</activation>
<build>
<resources>
<resource>
<directory>src/prod/resources</directory>
<filtering>false</filtering>
<excludes>
<exclude>**/config.properties</exclude>
</excludes>
</resource>
<resource>
<directory>src/prod/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/config.properties</include>
</includes>
</resource>
</resources>
<plugins>
<!-- Generation of the war -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<warSourceExcludes>
sismicsdocs/**,
</warSourceExcludes>
<webXml>src\main\webapp\WEB-INF\web.xml</webXml>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<!-- Hosted version profile -->
<profile>
<id>hosted</id>
<activation>
<property>
<name>env</name>
<value>hosted</value>
</property>
</activation>
<build>
<resources>
<resource>
<directory>src/hosted/resources</directory>
<filtering>false</filtering>
</resource>
</resources>
</build>
</profile>
</profiles>
</project>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="docs"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<!-- Override init parameter to avoid nasty file locking issue on windows. -->
<servlet>
<servlet-name>default</servlet-name>
<init-param>
<param-name>useFileMappedBuffer</param-name>
<param-value>false</param-value>
</init-param>
</servlet>
</web-app>

View File

@@ -0,0 +1,3 @@
api.current_version=${project.version}
api.min_version=1.0
db.version=1

View File

@@ -0,0 +1,10 @@
hibernate.connection.driver_class=org.hsqldb.jdbcDriver
hibernate.connection.url=jdbc:hsqldb:mem:docs
hibernate.connection.username=sa
hibernate.connection.password=
hibernate.hbm2ddl.auto=none
hibernate.dialect=org.hibernate.dialect.HSQLDialect
hibernate.show_sql=false
hibernate.format_sql=false
hibernate.max_fetch_depth=5
hibernate.cache.use_second_level_cache=false

View File

@@ -0,0 +1,8 @@
log4j.rootCategory=WARN, CONSOLE, MEMORY
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{DATE} %p %l %m %n
log4j.appender.MEMORY=com.sismics.util.log4j.MemoryAppender
log4j.appender.MEMORY.size=1000
log4j.logger.com.sismics=DEBUG

View File

@@ -0,0 +1,14 @@
package com.sismics.docs.rest.constant;
/**
* Base functions.
*
* @author jtremeaux
*/
public enum BaseFunction {
/**
* Allows the user to use the admin fonctions.
*/
ADMIN,
}

View File

@@ -0,0 +1,114 @@
package com.sismics.docs.rest.resource;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Appender;
import org.apache.log4j.Logger;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import com.sismics.docs.core.util.ConfigUtil;
import com.sismics.docs.core.util.jpa.PaginatedList;
import com.sismics.docs.core.util.jpa.PaginatedLists;
import com.sismics.docs.rest.constant.BaseFunction;
import com.sismics.rest.exception.ForbiddenClientException;
import com.sismics.rest.exception.ServerException;
import com.sismics.util.log4j.LogCriteria;
import com.sismics.util.log4j.LogEntry;
import com.sismics.util.log4j.MemoryAppender;
/**
* General app REST resource.
*
* @author jtremeaux
*/
@Path("/app")
public class AppResource extends BaseResource {
/**
* Return the information about the application.
*
* @return Response
* @throws JSONException
*/
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response version() throws JSONException {
ResourceBundle configBundle = ConfigUtil.getConfigBundle();
String currentVersion = configBundle.getString("api.current_version");
String minVersion = configBundle.getString("api.min_version");
JSONObject response = new JSONObject();
response.put("current_version", currentVersion.replace("-SNAPSHOT", ""));
response.put("min_version", minVersion);
response.put("total_memory", Runtime.getRuntime().totalMemory());
response.put("free_memory", Runtime.getRuntime().freeMemory());
return Response.ok().entity(response).build();
}
/**
* Retrieve the application logs.
*
* @param level Filter on logging level
* @param tag Filter on logger name / tag
* @param message Filter on message
* @param limit Page limit
* @param offset Page offset
* @return
* @throws JSONException
*/
@GET
@Path("log")
@Produces(MediaType.APPLICATION_JSON)
public Response log(
@QueryParam("level") String level,
@QueryParam("tag") String tag,
@QueryParam("message") String message,
@QueryParam("limit") Integer limit,
@QueryParam("offset") Integer offset) throws JSONException {
if (!authenticate()) {
throw new ForbiddenClientException();
}
checkBaseFunction(BaseFunction.ADMIN);
// Get the memory appender
Logger logger = Logger.getRootLogger();
Appender appender = logger.getAppender("MEMORY");
if (appender == null || !(appender instanceof MemoryAppender)) {
throw new ServerException("ServerError", "MEMORY appender not configured");
}
MemoryAppender memoryAppender = (MemoryAppender) appender;
// Find the logs
LogCriteria logCriteria = new LogCriteria();
logCriteria.setLevel(StringUtils.stripToNull(level));
logCriteria.setTag(StringUtils.stripToNull(tag));
logCriteria.setMessage(StringUtils.stripToNull(message));
PaginatedList<LogEntry> paginatedList = PaginatedLists.create(limit, offset);
memoryAppender.find(logCriteria, paginatedList);
JSONObject response = new JSONObject();
List<JSONObject> logs = new ArrayList<JSONObject>();
for (LogEntry logEntry : paginatedList.getResultList()) {
JSONObject log = new JSONObject();
log.put("date", logEntry.getTimestamp());
log.put("level", logEntry.getLevel());
log.put("tag", logEntry.getTag());
log.put("message", logEntry.getMessage());
logs.add(log);
}
response.put("total", paginatedList.getResultCount());
response.put("logs", logs);
return Response.ok().entity(response).build();
}
}

View File

@@ -0,0 +1,82 @@
package com.sismics.docs.rest.resource;
import java.security.Principal;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import org.codehaus.jettison.json.JSONException;
import com.sismics.docs.rest.constant.BaseFunction;
import com.sismics.rest.exception.ForbiddenClientException;
import com.sismics.security.IPrincipal;
import com.sismics.security.UserPrincipal;
import com.sismics.util.filter.TokenBasedSecurityFilter;
/**
* Base class of REST resources.
*
* @author jtremeaux
*/
public abstract class BaseResource {
/**
* Injects the HTTP request.
*/
@Context
protected HttpServletRequest request;
/**
* Application key.
*/
@QueryParam("app_key")
protected String appKey;
/**
* Principal of the authenticated user.
*/
protected IPrincipal principal;
/**
* This method is used to check if the user is authenticated.
*
* @return True if the user is authenticated and not anonymous
*/
protected boolean authenticate() {
Principal principal = (Principal) request.getAttribute(TokenBasedSecurityFilter.PRINCIPAL_ATTRIBUTE);
if (principal != null && principal instanceof IPrincipal) {
this.principal = (IPrincipal) principal;
return !this.principal.isAnonymous();
} else {
return false;
}
}
/**
* Checks if the user has a base function. Throw an exception if the check fails.
*
* @param baseFunction Base function to check
* @throws JSONException
*/
protected void checkBaseFunction(BaseFunction baseFunction) throws JSONException {
if (!hasBaseFunction(baseFunction)) {
throw new ForbiddenClientException();
}
}
/**
* Checks if the user has a base function.
*
* @param baseFunction Base function to check
* @return True if the user has the base function
* @throws JSONException
*/
protected boolean hasBaseFunction(BaseFunction baseFunction) throws JSONException {
if (principal == null || !(principal instanceof UserPrincipal)) {
return false;
}
Set<String> baseFunctionSet = ((UserPrincipal) principal).getBaseFunctionSet();
return baseFunctionSet != null && baseFunctionSet.contains(baseFunction.name());
}
}

View File

@@ -0,0 +1,230 @@
package com.sismics.docs.rest.resource;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.NoResultException;
import javax.ws.rs.DELETE;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import com.sismics.docs.core.dao.jpa.DocumentDao;
import com.sismics.docs.core.dao.jpa.criteria.DocumentCriteria;
import com.sismics.docs.core.dao.jpa.dto.DocumentDto;
import com.sismics.docs.core.model.jpa.Document;
import com.sismics.docs.core.util.jpa.PaginatedList;
import com.sismics.docs.core.util.jpa.PaginatedLists;
import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.rest.exception.ClientException;
import com.sismics.rest.exception.ForbiddenClientException;
import com.sismics.rest.util.ValidationUtil;
/**
* Document REST resources.
*
* @author bgamard
*/
@Path("/document")
public class DocumentResource extends BaseResource {
/**
* Returns a document.
*
* @param id Document ID
* @return Response
* @throws JSONException
*/
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response get(
@QueryParam("id") String id) throws JSONException {
if (!authenticate()) {
throw new ForbiddenClientException();
}
DocumentDao documentDao = new DocumentDao();
Document documentDb = null;
try {
documentDb = documentDao.getDocument(id, principal.getId());
} catch (NoResultException e) {
throw new ClientException("DocumentNotFound", MessageFormat.format("Document not found: {0}", id));
}
JSONObject document = new JSONObject();
document.put("id", documentDb.getId());
document.put("title", documentDb.getTitle());
document.put("description", documentDb.getDescription());
document.put("create_date", documentDb.getCreateDate().getTime());
return Response.ok().entity(document).build();
}
/**
* Returns all documents.
*
* @param limit Page limit
* @param offset Page offset
* @return Response
* @throws JSONException
*/
@GET
@Path("list")
@Produces(MediaType.APPLICATION_JSON)
public Response list(
@QueryParam("limit") Integer limit,
@QueryParam("offset") Integer offset,
@QueryParam("sort_column") Integer sortColumn,
@QueryParam("asc") Boolean asc) throws JSONException {
if (!authenticate()) {
throw new ForbiddenClientException();
}
JSONObject response = new JSONObject();
List<JSONObject> documents = new ArrayList<JSONObject>();
DocumentDao documentDao = new DocumentDao();
PaginatedList<DocumentDto> paginatedList = PaginatedLists.create(limit, offset);
SortCriteria sortCriteria = new SortCriteria(sortColumn, asc);
DocumentCriteria documentCriteria = new DocumentCriteria();
documentCriteria.setUserId(principal.getId());
documentDao.findByCriteria(paginatedList, documentCriteria, sortCriteria);
for (DocumentDto documentDto : paginatedList.getResultList()) {
JSONObject document = new JSONObject();
document.put("id", documentDto.getId());
document.put("title", documentDto.getTitle());
document.put("description", documentDto.getDescription());
document.put("create_date", documentDto.getCreateTimestamp());
documents.add(document);
}
response.put("total", paginatedList.getResultCount());
response.put("documents", documents);
return Response.ok().entity(response).build();
}
/**
* Creates a new document.
*
* @param title Title
* @param description Description
* @return Response
* @throws JSONException
*/
@PUT
@Produces(MediaType.APPLICATION_JSON)
public Response add(
@FormParam("title") String title,
@FormParam("description") String description) throws JSONException {
if (!authenticate()) {
throw new ForbiddenClientException();
}
// Validate input data
title = ValidationUtil.validateLength(title, "title", 1, 100, false);
description = ValidationUtil.validateLength(description, "description", 0, 4000, true);
// Create the document
DocumentDao documentDao = new DocumentDao();
Document document = new Document();
document.setUserId(principal.getId());
document.setTitle(title);
document.setDescription(description);
String documentId = documentDao.create(document);
JSONObject response = new JSONObject();
response.put("id", documentId);
return Response.ok().entity(response).build();
}
/**
* Updates the document.
*
* @param title Title
* @param description Description
* @return Response
* @throws JSONException
*/
@POST
@Path("{id: [a-z0-9\\-]+}")
@Produces(MediaType.APPLICATION_JSON)
public Response update(
@PathParam("id") String id,
@FormParam("title") String title,
@FormParam("description") String description) throws JSONException {
if (!authenticate()) {
throw new ForbiddenClientException();
}
// Validate input data
title = ValidationUtil.validateLength(title, "title", 1, 100, false);
description = ValidationUtil.validateLength(description, "description", 0, 4000, true);
// Get the document
DocumentDao documentDao = new DocumentDao();
Document document = null;
try {
document = documentDao.getDocument(id, principal.getId());
} catch (NoResultException e) {
throw new ClientException("DocumentNotFound", MessageFormat.format("Document not found: {0}", id));
}
// Update the document
if (title != null) {
document.setTitle(title);
}
if (description != null) {
document.setDescription(description);
}
// Always return ok
JSONObject response = new JSONObject();
response.put("status", "ok");
return Response.ok().entity(response).build();
}
/**
* Deletes a document.
*
* @param id Document ID
* @return Response
* @throws JSONException
*/
@DELETE
@Path("{id: [a-z0-9\\-]+}")
@Produces(MediaType.APPLICATION_JSON)
public Response delete(
@PathParam("id") String id) throws JSONException {
if (!authenticate()) {
throw new ForbiddenClientException();
}
// Get the document
DocumentDao documentDao = new DocumentDao();
Document document = null;
try {
document = documentDao.getDocument(id, principal.getId());
} catch (NoResultException e) {
throw new ClientException("DocumentNotFound", MessageFormat.format("Document not found: {0}", id));
}
// Delete the document
documentDao.delete(document.getId());
// Always return ok
JSONObject response = new JSONObject();
response.put("status", "ok");
return Response.ok().entity(response).build();
}
}

View File

@@ -0,0 +1,200 @@
package com.sismics.docs.rest.resource;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.NoResultException;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import com.sismics.docs.core.dao.jpa.DocumentDao;
import com.sismics.docs.core.dao.jpa.FileDao;
import com.sismics.docs.core.model.jpa.Document;
import com.sismics.docs.core.model.jpa.File;
import com.sismics.docs.core.util.DirectoryUtil;
import com.sismics.rest.exception.ClientException;
import com.sismics.rest.exception.ForbiddenClientException;
import com.sismics.rest.exception.ServerException;
import com.sismics.rest.util.ValidationUtil;
import com.sismics.util.mime.MimeTypeUtil;
import com.sun.jersey.multipart.FormDataBodyPart;
import com.sun.jersey.multipart.FormDataParam;
/**
* File REST resources.
*
* @author bgamard
*/
@Path("/file")
public class FileResource extends BaseResource {
/**
* Add a file to a document.
*
* @param id Document ID
* @param fileBodyPart File to add
* @return Response
* @throws JSONException
*/
@PUT
@Consumes("multipart/form-data")
@Produces(MediaType.APPLICATION_JSON)
public Response add(
@FormDataParam("id") String documentId,
@FormDataParam("file") FormDataBodyPart fileBodyPart) throws JSONException {
if (!authenticate()) {
throw new ForbiddenClientException();
}
// Validate input data
ValidationUtil.validateRequired(documentId, "id");
ValidationUtil.validateRequired(fileBodyPart, "file");
// Get the document
DocumentDao documentDao = new DocumentDao();
Document document = null;
try {
document = documentDao.getDocument(documentId, principal.getId());
} catch (NoResultException e) {
throw new ClientException("DocumentNotFound", MessageFormat.format("Document not found: {0}", documentId));
}
FileDao fileDao = new FileDao();
InputStream is = fileBodyPart.getValueAs(InputStream.class);
try {
// Create the file
File file = new File();
file.setDocumentId(document.getId());
file.setMimeType(MimeTypeUtil.guessMimeType(is));
String fileId = fileDao.create(file);
// Copy the incoming stream content into the storage directory
Files.copy(is, Paths.get(DirectoryUtil.getStorageDirectory().getPath(), fileId));
// Always return ok
JSONObject response = new JSONObject();
response.put("status", "ok");
response.put("id", fileId);
return Response.ok().entity(response).build();
} catch (Exception e) {
throw new ServerException("FileError", "Error adding a file", e);
}
}
/**
* Returns a file.
*
* @param id Document ID
* @return Response
* @throws JSONException
*/
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response get(
@QueryParam("id") String id) throws JSONException {
if (!authenticate()) {
throw new ForbiddenClientException();
}
FileDao fileDao = new FileDao();
File fileDb = null;
try {
fileDb = fileDao.getFile(id);
} catch (NoResultException e) {
throw new ClientException("FileNotFound", MessageFormat.format("File not found: {0}", id));
}
JSONObject file = new JSONObject();
file.put("id", fileDb.getId());
file.put("mimetype", fileDb.getMimeType());
file.put("document_id", fileDb.getDocumentId());
file.put("create_date", fileDb.getCreateDate().getTime());
return Response.ok().entity(file).build();
}
/**
* Returns files linked to a document.
*
* @param id Document ID
* @return Response
* @throws JSONException
*/
@GET
@Path("list")
@Produces(MediaType.APPLICATION_JSON)
public Response list(
@QueryParam("id") String documentId) throws JSONException {
if (!authenticate()) {
throw new ForbiddenClientException();
}
FileDao fileDao = new FileDao();
List<File> fileList = fileDao.getByDocumentId(documentId);
JSONObject response = new JSONObject();
List<JSONObject> files = new ArrayList<JSONObject>();
for (File fileDb : fileList) {
JSONObject file = new JSONObject();
file.put("id", fileDb.getId());
file.put("mimetype", fileDb.getMimeType());
file.put("document_id", fileDb.getDocumentId());
file.put("create_date", fileDb.getCreateDate().getTime());
files.add(file);
}
response.put("files", files);
return Response.ok().entity(response).build();
}
/**
* Deletes a file.
*
* @param id File ID
* @return Response
* @throws JSONException
*/
@DELETE
@Path("{id: [a-z0-9\\-]+}")
@Produces(MediaType.APPLICATION_JSON)
public Response delete(
@PathParam("id") String id) throws JSONException {
if (!authenticate()) {
throw new ForbiddenClientException();
}
// Get the file
FileDao fileDao = new FileDao();
File file = null;
try {
file = fileDao.getFile(id);
} catch (NoResultException e) {
throw new ClientException("FileNotFound", MessageFormat.format("File not found: {0}", id));
}
// Delete the document
fileDao.delete(file.getId());
// Always return ok
JSONObject response = new JSONObject();
response.put("status", "ok");
return Response.ok().entity(response).build();
}
}

View File

@@ -0,0 +1,46 @@
package com.sismics.docs.rest.resource;
import java.util.ArrayList;
import java.util.List;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import com.sismics.docs.core.dao.jpa.LocaleDao;
import com.sismics.docs.core.model.jpa.Locale;
/**
* Locale REST resources.
*
* @author jtremeaux
*/
@Path("/locale")
public class LocaleResource extends BaseResource {
/**
* Returns the list of all locales.
*
* @return Response
* @throws JSONException
*/
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response list() throws JSONException {
LocaleDao localeDao = new LocaleDao();
List<Locale> localeList = localeDao.findAll();
JSONObject response = new JSONObject();
List<JSONObject> items = new ArrayList<JSONObject>();
for (Locale locale : localeList) {
JSONObject item = new JSONObject();
item.put("id", locale.getId());
items.add(item);
}
response.put("locales", items);
return Response.ok().entity(response).build();
}
}

View File

@@ -0,0 +1,57 @@
package com.sismics.docs.rest.resource;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import com.sun.jersey.core.util.ReaderWriter;
/**
* MessageBodyWriter personalized to write JSON despite the text/plain MIME type.
* Used in particuler in return of a posted form, since IE doesn't knw how to read the application/json MIME type.
*
* @author bgamard
*/
@Provider
@Produces(MediaType.TEXT_PLAIN)
public class TextPlainMessageBodyWriter implements
MessageBodyWriter<JSONObject> {
@Override
public boolean isWriteable(Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType) {
return true;
}
@Override
public long getSize(JSONObject array, Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType) {
return -1;
}
@Override
public void writeTo(JSONObject jsonObject, Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders,
OutputStream entityStream) throws IOException,
WebApplicationException {
try {
OutputStreamWriter writer = new OutputStreamWriter(entityStream, ReaderWriter.getCharset(mediaType));
jsonObject.write(writer);
writer.flush();
} catch (JSONException e) {
throw new WebApplicationException(e);
}
}
}

View File

@@ -0,0 +1,45 @@
package com.sismics.docs.rest.resource;
import java.util.ArrayList;
import java.util.List;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import com.sismics.docs.core.dao.file.theme.ThemeDao;
/**
* Theme REST resources.
*
* @author jtremeaux
*/
@Path("/theme")
public class ThemeResource extends BaseResource {
/**
* Returns the list of all themes.
*
* @return Response
* @throws JSONException
*/
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response list() throws JSONException {
ThemeDao themeDao = new ThemeDao();
List<String> themeList = themeDao.findAll();
JSONObject response = new JSONObject();
List<JSONObject> items = new ArrayList<JSONObject>();
for (String theme : themeList) {
JSONObject item = new JSONObject();
item.put("id", theme);
items.add(item);
}
response.put("themes", items);
return Response.ok().entity(response).build();
}
}

View File

@@ -0,0 +1,535 @@
package com.sismics.docs.rest.resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.servlet.http.Cookie;
import javax.ws.rs.DELETE;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.NewCookie;
import javax.ws.rs.core.Response;
import org.apache.commons.lang.StringUtils;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import com.sismics.docs.core.constant.Constants;
import com.sismics.docs.core.dao.jpa.AuthenticationTokenDao;
import com.sismics.docs.core.dao.jpa.UserDao;
import com.sismics.docs.core.dao.jpa.dto.UserDto;
import com.sismics.docs.core.model.jpa.AuthenticationToken;
import com.sismics.docs.core.model.jpa.User;
import com.sismics.docs.core.util.jpa.PaginatedList;
import com.sismics.docs.core.util.jpa.PaginatedLists;
import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.docs.rest.constant.BaseFunction;
import com.sismics.rest.exception.ClientException;
import com.sismics.rest.exception.ForbiddenClientException;
import com.sismics.rest.exception.ServerException;
import com.sismics.rest.util.ValidationUtil;
import com.sismics.security.UserPrincipal;
import com.sismics.util.LocaleUtil;
import com.sismics.util.filter.TokenBasedSecurityFilter;
/**
* User REST resources.
*
* @author jtremeaux
*/
@Path("/user")
public class UserResource extends BaseResource {
/**
* Creates a new user.
*
* @param username User's username
* @param password Password
* @param email E-Mail
* @param localeId Locale ID
* @return Response
* @throws JSONException
*/
@PUT
@Produces(MediaType.APPLICATION_JSON)
public Response register(
@FormParam("username") String username,
@FormParam("password") String password,
@FormParam("locale") String localeId,
@FormParam("email") String email) throws JSONException {
if (!authenticate()) {
throw new ForbiddenClientException();
}
checkBaseFunction(BaseFunction.ADMIN);
// Validate the input data
username = ValidationUtil.validateLength(username, "username", 3, 50);
ValidationUtil.validateAlphanumeric(username, "username");
password = ValidationUtil.validateLength(password, "password", 8, 50);
email = ValidationUtil.validateLength(email, "email", 3, 50);
ValidationUtil.validateEmail(email, "email");
// Create the user
User user = new User();
user.setRoleId(Constants.DEFAULT_USER_ROLE);
user.setUsername(username);
user.setPassword(password);
user.setEmail(email);
user.setCreateDate(new Date());
if (localeId == null) {
// Set the locale from the HTTP headers
localeId = LocaleUtil.getLocaleIdFromAcceptLanguage(request.getHeader("Accept-Language"));
}
user.setLocaleId(localeId);
// Create the user
UserDao userDao = new UserDao();
try {
userDao.create(user);
} catch (Exception e) {
if ("AlreadyExistingUsername".equals(e.getMessage())) {
throw new ServerException("AlreadyExistingUsername", "Login already used", e);
} else {
throw new ServerException("UnknownError", "Unknown Server Error", e);
}
}
// Always return OK
JSONObject response = new JSONObject();
response.put("status", "ok");
return Response.ok().entity(response).build();
}
/**
* Updates user informations.
*
* @param password Password
* @param email E-Mail
* @param themeId Theme
* @param localeId Locale ID
* @param displayTitleWeb Display only article titles (web application).
* @param displayTitleMobile Display only article titles (mobile application).
* @param displayUnreadWeb Display only unread titles (web application).
* @param displayUnreadMobile Display only unread titles (mobile application).
* @param firstConnection True if the user hasn't acknowledged the first connection wizard yet.
* @return Response
* @throws JSONException
*/
@POST
@Produces(MediaType.APPLICATION_JSON)
public Response update(
@FormParam("password") String password,
@FormParam("email") String email,
@FormParam("theme") String themeId,
@FormParam("locale") String localeId,
@FormParam("first_connection") Boolean firstConnection) throws JSONException {
if (!authenticate()) {
throw new ForbiddenClientException();
}
// Validate the input data
password = ValidationUtil.validateLength(password, "password", 8, 50, true);
email = ValidationUtil.validateLength(email, "email", null, 100, true);
localeId = ValidationUtil.validateLocale(localeId, "locale", true);
themeId = ValidationUtil.validateTheme(themeId, "theme", true);
// Update the user
UserDao userDao = new UserDao();
User user = userDao.getActiveByUsername(principal.getName());
if (email != null) {
user.setEmail(email);
}
if (themeId != null) {
user.setTheme(themeId);
}
if (localeId != null) {
user.setLocaleId(localeId);
}
if (firstConnection != null && hasBaseFunction(BaseFunction.ADMIN)) {
user.setFirstConnection(firstConnection);
}
user = userDao.update(user);
if (StringUtils.isNotBlank(password)) {
user.setPassword(password);
user = userDao.updatePassword(user);
}
// Always return "ok"
JSONObject response = new JSONObject();
response.put("status", "ok");
return Response.ok().entity(response).build();
}
/**
* Updates user informations.
*
* @param username Username
* @param password Password
* @param email E-Mail
* @param themeId Theme
* @param localeId Locale ID
* @param displayTitleWeb Display only article titles (web application).
* @param displayTitleMobile Display only article titles (mobile application).
* @param displayUnreadWeb Display only unread titles (web application).
* @param displayUnreadMobile Display only unread titles (mobile application).
* @return Response
* @throws JSONException
*/
@POST
@Path("{username: [a-zA-Z0-9_]+}")
@Produces(MediaType.APPLICATION_JSON)
public Response update(
@PathParam("username") String username,
@FormParam("password") String password,
@FormParam("email") String email,
@FormParam("theme") String themeId,
@FormParam("locale") String localeId) throws JSONException {
if (!authenticate()) {
throw new ForbiddenClientException();
}
checkBaseFunction(BaseFunction.ADMIN);
// Validate the input data
password = ValidationUtil.validateLength(password, "password", 8, 50, true);
email = ValidationUtil.validateLength(email, "email", null, 100, true);
localeId = ValidationUtil.validateLocale(localeId, "locale", true);
themeId = ValidationUtil.validateTheme(themeId, "theme", true);
// Check if the user exists
UserDao userDao = new UserDao();
User user = userDao.getActiveByUsername(username);
if (user == null) {
throw new ClientException("UserNotFound", "The user doesn't exist");
}
// Update the user
if (email != null) {
user.setEmail(email);
}
if (themeId != null) {
user.setTheme(themeId);
}
if (localeId != null) {
user.setLocaleId(localeId);
}
user = userDao.update(user);
if (StringUtils.isNotBlank(password)) {
// Change the password
user.setPassword(password);
user = userDao.updatePassword(user);
}
// Always return "ok"
JSONObject response = new JSONObject();
response.put("status", "ok");
return Response.ok().entity(response).build();
}
/**
* Checks if a username is available. Search only on active accounts.
*
* @param username Username to check
* @return Response
*/
@GET
@Path("check_username")
@Produces(MediaType.APPLICATION_JSON)
public Response checkUsername(
@QueryParam("username") String username) throws JSONException {
UserDao userDao = new UserDao();
User user = userDao.getActiveByUsername(username);
JSONObject response = new JSONObject();
if (user != null) {
response.put("status", "ko");
response.put("message", "Username already registered");
} else {
response.put("status", "ok");
}
return Response.ok().entity(response).build();
}
/**
* This resource is used to authenticate the user and create a user ession.
* The "session" is only used to identify the user, no other data is stored in the session.
*
* @param username Username
* @param password Password
* @param longLasted Remember the user next time, create a long lasted session.
* @return Response
*/
@POST
@Path("login")
@Produces(MediaType.APPLICATION_JSON)
public Response login(
@FormParam("username") String username,
@FormParam("password") String password,
@FormParam("remember") boolean longLasted) throws JSONException {
// Validate the input data
username = StringUtils.strip(username);
password = StringUtils.strip(password);
// Get the user
UserDao userDao = new UserDao();
String userId = userDao.authenticate(username, password);
if (userId == null) {
throw new ForbiddenClientException();
}
// Create a new session token
AuthenticationTokenDao authenticationTokenDao = new AuthenticationTokenDao();
AuthenticationToken authenticationToken = new AuthenticationToken();
authenticationToken.setUserId(userId);
authenticationToken.setLongLasted(longLasted);
String token = authenticationTokenDao.create(authenticationToken);
// Cleanup old session tokens
authenticationTokenDao.deleteOldSessionToken(userId);
JSONObject response = new JSONObject();
int maxAge = longLasted ? TokenBasedSecurityFilter.TOKEN_LONG_LIFETIME : -1;
NewCookie cookie = new NewCookie(TokenBasedSecurityFilter.COOKIE_NAME, token, "/", null, null, maxAge, false);
return Response.ok().entity(response).cookie(cookie).build();
}
/**
* Logs out the user and deletes the active session.
*
* @return Response
*/
@POST
@Path("logout")
@Produces(MediaType.APPLICATION_JSON)
public Response logout() throws JSONException {
if (!authenticate()) {
throw new ForbiddenClientException();
}
// Get the value of the session token
String authToken = null;
if (request.getCookies() != null) {
for (Cookie cookie : request.getCookies()) {
if (TokenBasedSecurityFilter.COOKIE_NAME.equals(cookie.getName())) {
authToken = cookie.getValue();
}
}
}
AuthenticationTokenDao authenticationTokenDao = new AuthenticationTokenDao();
AuthenticationToken authenticationToken = null;
if (authToken != null) {
authenticationToken = authenticationTokenDao.get(authToken);
}
// No token : nothing to do
if (authenticationToken == null) {
throw new ForbiddenClientException();
}
// Deletes the server token
try {
authenticationTokenDao.delete(authToken);
} catch (Exception e) {
throw new ServerException("AuthenticationTokenError", "Error deleting authentication token: " + authToken, e);
}
// Deletes the client token in the HTTP response
JSONObject response = new JSONObject();
NewCookie cookie = new NewCookie(TokenBasedSecurityFilter.COOKIE_NAME, null);
return Response.ok().entity(response).cookie(cookie).build();
}
/**
* Delete a user.
*
* @return Response
*/
@DELETE
@Produces(MediaType.APPLICATION_JSON)
public Response delete() throws JSONException {
if (!authenticate()) {
throw new ForbiddenClientException();
}
// Ensure that the admin user is not deleted
// TODO Ensure it exists at least one user left
// Delete the user
UserDao userDao = new UserDao();
userDao.delete(principal.getName());
// Always return ok
JSONObject response = new JSONObject();
response.put("status", "ok");
return Response.ok().entity(response).build();
}
/**
* Deletes a user.
*
* @param username Username
* @return Response
* @throws JSONException
*/
@DELETE
@Path("{username: [a-zA-Z0-9_]+}")
@Produces(MediaType.APPLICATION_JSON)
public Response delete(@PathParam("username") String username) throws JSONException {
if (!authenticate()) {
throw new ForbiddenClientException();
}
checkBaseFunction(BaseFunction.ADMIN);
// Check if the user exists
UserDao userDao = new UserDao();
User user = userDao.getActiveByUsername(username);
if (user == null) {
throw new ClientException("UserNotFound", "The user doesn't exist");
}
// Ensure that the admin user is not deleted
// TODO Ensure it exists at least one user left
// Delete the user
userDao.delete(user.getUsername());
// Always return ok
JSONObject response = new JSONObject();
response.put("status", "ok");
return Response.ok().entity(response).build();
}
/**
* Returns the information about the connected user.
*
* @return Response
* @throws JSONException
*/
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response info() throws JSONException {
JSONObject response = new JSONObject();
if (!authenticate()) {
response.put("anonymous", true);
String localeId = LocaleUtil.getLocaleIdFromAcceptLanguage(request.getHeader("Accept-Language"));
response.put("locale", localeId);
// Check if admin has the default password
UserDao userDao = new UserDao();
User adminUser = userDao.getById("admin");
if (adminUser != null && adminUser.getDeleteDate() == null) {
response.put("is_default_password", Constants.DEFAULT_ADMIN_PASSWORD.equals(adminUser.getPassword()));
}
} else {
response.put("anonymous", false);
UserDao userDao = new UserDao();
User user = userDao.getById(principal.getId());
response.put("username", user.getUsername());
response.put("email", user.getEmail());
response.put("theme", user.getTheme());
response.put("locale", user.getLocaleId());
response.put("first_connection", user.isFirstConnection());
JSONArray baseFunctions = new JSONArray(((UserPrincipal) principal).getBaseFunctionSet());
response.put("base_functions", baseFunctions);
response.put("is_default_password", hasBaseFunction(BaseFunction.ADMIN) && Constants.DEFAULT_ADMIN_PASSWORD.equals(user.getPassword()));
}
return Response.ok().entity(response).build();
}
/**
* Returns the information about a user.
*
* @param username Username
* @return Response
* @throws JSONException
*/
@GET
@Path("{username: [a-zA-Z0-9_]+}")
@Produces(MediaType.APPLICATION_JSON)
public Response view(@PathParam("username") String username) throws JSONException {
if (!authenticate()) {
throw new ForbiddenClientException();
}
checkBaseFunction(BaseFunction.ADMIN);
JSONObject response = new JSONObject();
UserDao userDao = new UserDao();
User user = userDao.getActiveByUsername(username);
if (user == null) {
throw new ClientException("UserNotFound", "The user doesn't exist");
}
response.put("username", user.getUsername());
response.put("email", user.getEmail());
response.put("theme", user.getTheme());
response.put("locale", user.getLocaleId());
return Response.ok().entity(response).build();
}
/**
* Returns all active users.
*
* @param limit Page limit
* @param offset Page offset
* @param sortColumn Sort index
* @param asc If true, ascending sorting, else descending
* @return Response
* @throws JSONException
*/
@GET
@Path("list")
@Produces(MediaType.APPLICATION_JSON)
public Response list(
@QueryParam("limit") Integer limit,
@QueryParam("offset") Integer offset,
@QueryParam("sort_column") Integer sortColumn,
@QueryParam("asc") Boolean asc) throws JSONException {
if (!authenticate()) {
throw new ForbiddenClientException();
}
checkBaseFunction(BaseFunction.ADMIN);
JSONObject response = new JSONObject();
List<JSONObject> users = new ArrayList<JSONObject>();
PaginatedList<UserDto> paginatedList = PaginatedLists.create(limit, offset);
SortCriteria sortCriteria = new SortCriteria(sortColumn, asc);
UserDao userDao = new UserDao();
userDao.findAll(paginatedList, sortCriteria);
for (UserDto userDto : paginatedList.getResultList()) {
JSONObject user = new JSONObject();
user.put("id", userDto.getId());
user.put("username", userDto.getUsername());
user.put("email", userDto.getEmail());
user.put("create_date", userDto.getCreateTimestamp());
users.add(user);
}
response.put("total", paginatedList.getResultCount());
response.put("users", users);
return Response.ok().entity(response).build();
}
}

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="docs"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<display-name>Docs</display-name>
<!-- This filter is used to secure URLs -->
<filter>
<filter-name>requestContextFilter</filter-name>
<filter-class>com.sismics.util.filter.RequestContextFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>requestContextFilter</filter-name>
<url-pattern>/api/*</url-pattern>
<url-pattern>*.jsp</url-pattern>
</filter-mapping>
<!-- This filter is used to secure URLs -->
<filter>
<filter-name>tokenBasedSecurityFilter</filter-name>
<filter-class>com.sismics.util.filter.TokenBasedSecurityFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>tokenBasedSecurityFilter</filter-name>
<url-pattern>/api/*</url-pattern>
</filter-mapping>
<!-- Welcome files -->
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
<!-- Jersey -->
<servlet>
<servlet-name>Jersey REST Service</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>com.sismics.docs.rest.resource</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Jersey REST Service</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
</web-app>

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,42 @@
<!doctype html>
<html ng-app="docs">
<head>
<title>Sismics Docs</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" href="style/bootstrap.css" type="text/css" />
<link rel="stylesheet/less" href="style/main.less" type="text/css" />
<script>
less = {
env: 'development', // or "production"
async: false, // load imports async
fileAsync: false, // load imports async when in a page under a file protocol
poll: 1000, // when in watch mode, time in ms between polls
dumpLineNumbers: 'all', // or "mediaQuery" or "comments"
};
</script>
<script src="lib/jquery.js" type="text/javascript"></script>
<script src="lib/less.js" type="text/javascript"></script>
<script src="lib/underscore.js" type="text/javascript"></script>
<script src="lib/angular/angular.js" type="text/javascript"></script>
<script src="lib/angular.ui-router.js" type="text/javascript"></script>
<script src="lib/angular.ui-bootstrap.js" type="text/javascript"></script>
<script src="lib/angular.restangular.js" type="text/javascript"></script>
<script src="js/app.js" type="text/javascript"></script>
<script src="js/controller/Main.js" type="text/javascript"></script>
<script src="js/controller/Document.js" type="text/javascript"></script>
<script src="js/controller/DocumentEdit.js" type="text/javascript"></script>
<script src="js/controller/DocumentView.js" type="text/javascript"></script>
<script src="js/controller/Login.js" type="text/javascript"></script>
<script src="js/service/User.js" type="text/javascript"></script>
</head>
<body>
<div class="navbar">
<div class="navbar-inner">
<a class="brand" href="#">Sismics Docs</a>
</div>
</div>
<main ui-view="page">
</main>
</body>
</html>

View File

@@ -0,0 +1,117 @@
'use strict';
/**
* Trackino application.
*/
var App = angular.module('docs', ['ui.state', 'ui.bootstrap', 'restangular'])
/**
* Configuring modules.
*/
.config(function($stateProvider, $httpProvider, $routeProvider, RestangularProvider) {
// Configuring UI Router
$stateProvider
.state('main', {
url: '',
views: {
'page': {
templateUrl: 'partial/main.html',
controller: 'Main'
}
}
})
.state('document', {
url: '/document',
views: {
'page': {
templateUrl: 'partial/document.html',
controller: 'Document'
}
}
})
.state('document.add', {
url: '/add',
views: {
'document': {
templateUrl: 'partial/document.edit.html',
controller: 'DocumentEdit'
}
}
})
.state('document.edit', {
url: '/edit/:id',
views: {
'document': {
templateUrl: 'partial/document.edit.html',
controller: 'DocumentEdit'
}
}
})
.state('document.view', {
url: '/view/:id',
views: {
'document': {
templateUrl: 'partial/document.view.html',
controller: 'DocumentView'
}
}
})
.state('login', {
url: '/login',
views: {
'page': {
templateUrl: 'partial/login.html',
controller: 'Login'
}
}
});
// Configuring Restangular
RestangularProvider.setBaseUrl('api');
// Configuring $http
$httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';
$httpProvider.defaults.transformRequest = [function(data) {
var param = function(obj) {
var query = '';
var name, value, fullSubName, subName, subValue, innerObj, i;
for(name in obj) {
value = obj[name];
if(value instanceof Array) {
for(i=0; i<value.length; ++i) {
subValue = value[i];
fullSubName = name + '[' + i + ']';
innerObj = {};
innerObj[fullSubName] = subValue;
query += param(innerObj) + '&';
}
} else if(value instanceof Object) {
for(subName in value) {
subValue = value[subName];
fullSubName = name + '[' + subName + ']';
innerObj = {};
innerObj[fullSubName] = subValue;
query += param(innerObj) + '&';
}
}
else if(value !== undefined && value !== null) {
query += encodeURIComponent(name) + '=' + encodeURIComponent(value) + '&';
}
}
return query.length ? query.substr(0, query.length - 1) : query;
};
return angular.isObject(data) && String(data) !== '[object File]' ? param(data) : data;
}];
})
/**
* Application initialization.
*/
.run(function($rootScope, $state, $stateParams) {
$rootScope.$state = $state;
$rootScope.$stateParams = $stateParams;
});

View File

@@ -0,0 +1,34 @@
'use strict';
/**
* Document controller.
*/
App.controller('Document', function($scope, $state, Restangular) {
/**
* Load documents.
*/
$scope.loadDocuments = function() {
Restangular.one('document')
.getList('list', {
offset: 0,
limit: 30
})
.then(function(data) {
$scope.documents = data.documents;
});
};
/**
* Go to add document form.
*/
$scope.addDocument = function() {
$state.transitionTo('document.add');
};
$scope.viewDocument = function(id) {
$state.transitionTo('document.view', { id: id });
};
// Initial documents loading
$scope.loadDocuments();
});

View File

@@ -0,0 +1,37 @@
'use strict';
/**
* Document edition controller.
*/
App.controller('DocumentEdit', function($scope, $state, $stateParams, Restangular) {
/**
* Returns true if in edit mode (false in add mode).
*/
$scope.isEdit = function() {
return $stateParams.id;
};
/**
* Edit a document.
*/
$scope.edit = function() {
if ($scope.isEdit()) {
// TODO
} else {
Restangular
.one('document')
.put($scope.document)
.then(function() {
$scope.document = {};
$scope.loadDocuments();
});
}
};
/**
* Cancel edition.
*/
$scope.cancel = function() {
$state.transitionTo('document');
};
});

View File

@@ -0,0 +1,7 @@
'use strict';
/**
* Document view controller.
*/
App.controller('DocumentView', function($scope, Restangular) {
});

View File

@@ -0,0 +1,18 @@
'use strict';
/**
* Login controller.
*/
App.controller('Login', function($scope, $state, $dialog, User) {
$scope.login = function() {
User.login($scope.user).then(function() {
$state.transitionTo('browse');
}, function() {
var title = 'Login failed';
var msg = 'Username or password invalid';
var btns = [{result:'ok', label: 'OK', cssClass: 'btn-primary'}];
$dialog.messageBox(title, msg, btns).open();
});
};
});

View File

@@ -0,0 +1,14 @@
'use strict';
/**
* Main controller.
*/
App.controller('Main', function($scope, $state, User) {
User.userInfo(true).then(function(data) {
if (data.anonymous) {
$state.transitionTo('login');
} else {
$state.transitionTo('document');
}
});
});

View File

@@ -0,0 +1,28 @@
'use strict';
/**
* User service.
*/
App.factory('User', function(Restangular) {
var userInfo = null;
return {
/**
* Returns user info.
* @param force If true, force reloading data
*/
userInfo: function(force) {
if (userInfo == null || force) {
userInfo = Restangular.one('user').get();
}
return userInfo;
},
/**
* Login an user.
*/
login: function(user) {
return Restangular.one('user').post('login', user);
}
}
});

View File

@@ -0,0 +1,782 @@
'use strict';
(function() {
var module = angular.module('restangular', []);
module.provider('Restangular', function() {
// Configuration
var Configurer = {};
Configurer.init = function(object, config) {
/**
* Those are HTTP safe methods for which there is no need to pass any data with the request.
*/
var safeMethods= ["get", "head", "options", "trace"];
config.isSafe = function(operation) {
return _.contains(safeMethods, operation.toLowerCase());
};
/**
* This is the BaseURL to be used with Restangular
*/
config.baseUrl = _.isUndefined(config.baseUrl) ? "" : config.baseUrl;
object.setBaseUrl = function(newBaseUrl) {
config.baseUrl = _.last(newBaseUrl) === "/"
? _.initial(newBaseUrl).join("")
: newBaseUrl;
};
/**
* Sets the extra fields to keep from the parents
*/
config.extraFields = config.extraFields || [];
object.setExtraFields = function(newExtraFields) {
config.extraFields = newExtraFields;
};
/**
* Some default $http parameter to be used in EVERY call
**/
config.defaultHttpFields = config.defaultHttpFields || {};
object.setDefaultHttpFields = function(values) {
config.defaultHttpFields = values;
};
config.withHttpDefaults = function(obj) {
return _.defaults(obj, config.defaultHttpFields);
};
config.defaultRequestParams = config.defaultRequestParams || {};
object.setDefaultRequestParams = function(values) {
config.defaultRequestParams = values;
};
config.defaultHeaders = config.defaultHeaders || {};
object.setDefaultHeaders = function(headers) {
config.defaultHeaders = headers;
};
/**
* Method overriders will set which methods are sent via POST with an X-HTTP-Method-Override
**/
config.methodOverriders = config.methodOverriders || [];
object.setMethodOverriders = function(values) {
var overriders = _.extend([], values);
if (config.isOverridenMethod('delete', overriders)) {
overriders.push("remove");
}
config.methodOverriders = overriders;
};
config.isOverridenMethod = function(method, values) {
var search = values || config.methodOverriders;
return !_.isUndefined(_.find(search, function(one) {
return one.toLowerCase() === method.toLowerCase();
}));
};
/**
* Sets the URL creator type. For now, only Path is created. In the future we'll have queryParams
**/
config.urlCreator = config.urlCreator || "path";
object.setUrlCreator = function(name) {
if (!_.has(config.urlCreatorFactory, name)) {
throw new Error("URL Path selected isn't valid");
}
config.urlCreator = name;
};
/**
* You can set the restangular fields here. The 3 required fields for Restangular are:
*
* id: Id of the element
* route: name of the route of this element
* parentResource: the reference to the parent resource
*
* All of this fields except for id, are handled (and created) by Restangular. By default,
* the field values will be id, route and parentResource respectively
*/
config.restangularFields = config.restangularFields || {
id: "id",
route: "route",
parentResource: "parentResource",
restangularCollection: "restangularCollection"
};
object.setRestangularFields = function(resFields) {
config.restangularFields =
_.extend(config.restangularFields, resFields);
};
config.setIdToElem = function(elem, id) {
var properties = config.restangularFields.id.split('.');
var idValue = elem;
_.each(_.initial(properties), function(prop) {
idValue[prop] = {};
idValue = idValue[prop];
});
idValue[_.last(properties)] = id;
};
config.getIdFromElem = function(elem) {
var properties = config.restangularFields.id.split('.');
var idValue = angular.copy(elem);
_.each(properties, function(prop) {
idValue = idValue[prop];
});
return idValue;
};
/**
* Sets the Response parser. This is used in case your response isn't directly the data.
* For example if you have a response like {meta: {'meta'}, data: {name: 'Gonto'}}
* you can extract this data which is the one that needs wrapping
*
* The ResponseExtractor is a function that receives the response and the method executed.
*/
config.responseExtractor = config.responseExtractor || function(response) {
return response;
};
object.setResponseExtractor = function(extractor) {
config.responseExtractor = extractor;
};
object.setResponseInterceptor = object.setResponseExtractor;
/**
* Request interceptor is called before sending an object to the server.
*/
config.fullRequestInterceptor = config.fullRequestInterceptor || function(element, operation,
path, url, headers, params) {
return {
element: element,
headers: headers,
params: params
};
};
object.setRequestInterceptor = function(interceptor) {
config.fullRequestInterceptor = function(elem, operation, path, url, headers, params) {
return {
headers: headers,
params: params,
element: interceptor(elem, operation, path, url)
}
};
};
object.setFullRequestInterceptor = function(interceptor) {
config.fullRequestInterceptor = interceptor;
};
config.errorInterceptor = config.errorInterceptor || function() {};
object.setErrorInterceptor = function(interceptor) {
config.errorInterceptor = interceptor;
};
config.onBeforeElemRestangularized = config.onBeforeElemRestangularized || function(elem) {
return elem;
}
object.setOnBeforeElemRestangularized = function(post) {
config.onBeforeElemRestangularized = post;
};
/**
* This method is called after an element has been "Restangularized".
*
* It receives the element, a boolean indicating if it's an element or a collection
* and the name of the model
*
*/
config.onElemRestangularized = config.onElemRestangularized || function(elem) {
return elem;
};
object.setOnElemRestangularized = function(post) {
config.onElemRestangularized = post;
};
/**
* Depracated. Don't use this!!
*/
object.setListTypeIsArray = function(val) {
};
config.shouldSaveParent = config.shouldSaveParent || function() {
return true;
};
object.setParentless = function(values) {
if (_.isArray(values)) {
config.shouldSaveParent = function(route) {
return !_.contains(values, route);
}
} else if (_.isBoolean(values)) {
config.shouldSaveParent = function() {
return !values;
}
}
};
/**
* This lets you set a suffix to every request.
*
* For example, if your api requires that for JSon requests you do /users/123.json, you can set that
* in here.
*
*
* By default, the suffix is null
*/
config.suffix = _.isUndefined(config.suffix) ? null : config.suffix;
object.setRequestSuffix = function(newSuffix) {
config.suffix = newSuffix;
};
/**
* Add element transformers for certain routes.
*/
config.transformers = config.transformers || {};
object.addElementTransformer = function(type, secondArg, thirdArg) {
var isCollection = null;
var transformer = null;
if (arguments.length === 2) {
transformer = secondArg;
} else {
transformer = thirdArg;
isCollection = secondArg;
}
var typeTransformers = config.transformers[type];
if (!typeTransformers) {
typeTransformers = config.transformers[type] = [];
}
typeTransformers.push(function(coll, elem) {
if (_.isNull(isCollection) || (coll == isCollection)) {
return transformer(elem);
}
return elem;
});
};
object.extendCollection = function(route, fn) {
return object.addElementTransformer(route, true, fn);
};
object.extendModel = function(route, fn) {
return object.addElementTransformer(route, false, fn);
};
config.transformElem = function(elem, isCollection, route, Restangular) {
var typeTransformers = config.transformers[route];
var changedElem = elem;
if (typeTransformers) {
_.each(typeTransformers, function(transformer) {
changedElem = transformer(isCollection, changedElem);
});
}
return config.onElemRestangularized(changedElem,
isCollection, route, Restangular);
};
config.fullResponse = _.isUndefined(config.fullResponse) ? false : config.fullResponse;
object.setFullResponse = function(full) {
config.fullResponse = full;
};
//Internal values and functions
config.urlCreatorFactory = {};
/**
* Base URL Creator. Base prototype for everything related to it
**/
var BaseCreator = function() {
};
BaseCreator.prototype.setConfig = function(config) {
this.config = config;
};
BaseCreator.prototype.parentsArray = function(current) {
var parents = [];
while(current) {
parents.push(current);
current = current[this.config.restangularFields.parentResource];
}
return parents.reverse();
};
function RestangularResource($http, url, configurer) {
var resource = {};
_.each(_.keys(configurer), function(key) {
var value = configurer[key];
// We don't want the ? if no params are there
if (_.isEmpty(value.params)) {
delete value.params;
}
if (config.isSafe(value.method)) {
resource[key] = function() {
return $http(_.extend(value, {
url: url
}));
}
} else {
resource[key] = function(data) {
return $http(_.extend(value, {
url: url,
data: data
}));
}
}
});
return resource;
}
BaseCreator.prototype.resource = function(current, $http, callHeaders, callParams, what) {
var params = _.defaults(callParams || {}, this.config.defaultRequestParams);
var headers = _.defaults(callHeaders || {}, this.config.defaultHeaders);
var url = this.base(current);
url += what ? ("/" + what): '';
url += (this.config.suffix || '');
return RestangularResource($http, url, {
getList: this.config.withHttpDefaults({method: 'GET',
params: params,
headers: headers || {}}),
get: this.config.withHttpDefaults({method: 'GET',
params: params,
headers: headers || {}}),
put: this.config.withHttpDefaults({method: 'PUT',
params: params,
headers: headers || {}}),
post: this.config.withHttpDefaults({method: 'POST',
params: params,
headers: headers || {}}),
remove: this.config.withHttpDefaults({method: 'DELETE',
params: params,
headers: headers || {}}),
head: this.config.withHttpDefaults({method: 'HEAD',
params: params,
headers: headers || {}}),
trace: this.config.withHttpDefaults({method: 'TRACE',
params: params,
headers: headers || {}}),
options: this.config.withHttpDefaults({method: 'OPTIONS',
params: params,
headers: headers || {}}),
patch: this.config.withHttpDefaults({method: 'PATCH',
params: params,
headers: headers || {}})
});
};
/**
* This is the Path URL creator. It uses Path to show Hierarchy in the Rest API.
* This means that if you have an Account that then has a set of Buildings, a URL to a building
* would be /accounts/123/buildings/456
**/
var Path = function() {
};
Path.prototype = new BaseCreator();
Path.prototype.base = function(current) {
var __this = this;
return this.config.baseUrl + _.reduce(this.parentsArray(current), function(acum, elem) {
var currUrl = acum + "/" + elem[__this.config.restangularFields.route];
if (!elem[__this.config.restangularFields.restangularCollection]) {
var elemId = __this.config.getIdFromElem(elem);
if ("" !== elemId && !_.isUndefined(elemId) && !_.isNull(elemId)) {
currUrl += "/" + elemId;
}
}
return currUrl;
}, '');
};
Path.prototype.fetchUrl = function(current, what) {
var baseUrl = this.base(current);
if (what) {
baseUrl += "/" + what;
}
return baseUrl;
};
config.urlCreatorFactory.path = Path;
}
var globalConfiguration = {};
Configurer.init(this, globalConfiguration);
this.$get = ['$http', '$q', function($http, $q) {
function createServiceForConfiguration(config) {
var service = {};
var urlHandler = new config.urlCreatorFactory[config.urlCreator]();
urlHandler.setConfig(config);
function restangularizeBase(parent, elem, route) {
elem[config.restangularFields.route] = route;
elem.getRestangularUrl = _.bind(urlHandler.fetchUrl, urlHandler, elem);
elem.addRestangularMethod = _.bind(addRestangularMethodFunction, elem);
// RequestLess connection
elem.one = _.bind(one, elem, elem);
elem.all = _.bind(all, elem, elem);
if (parent && config.shouldSaveParent(route)) {
var restangularFieldsForParent = _.union(
_.values( _.pick(config.restangularFields, ['id', 'route', 'parentResource']) ),
config.extraFields
);
elem[config.restangularFields.parentResource] = _.pick(parent, restangularFieldsForParent);
} else {
elem[config.restangularFields.parentResource] = null;
}
return elem;
}
function one(parent, route, id) {
var elem = {};
config.setIdToElem(elem, id);
return restangularizeElem(parent, elem , route);
}
function all(parent, route) {
return restangularizeCollection(parent, {} , route, true);
}
// Promises
function restangularizePromise(promise, isCollection) {
promise.call = _.bind(promiseCall, promise);
promise.get = _.bind(promiseGet, promise);
promise[config.restangularFields.restangularCollection] = isCollection;
if (isCollection) {
promise.push = _.bind(promiseCall, promise, "push");
}
return promise;
}
function promiseCall(method) {
var deferred = $q.defer();
var callArgs = arguments;
this.then(function(val) {
var params = Array.prototype.slice.call(callArgs, 1);
var func = val[method];
func.apply(val, params);
deferred.resolve(val);
});
return restangularizePromise(deferred.promise, this[config.restangularFields.restangularCollection]);
}
function promiseGet(what) {
var deferred = $q.defer();
this.then(function(val) {
deferred.resolve(val[what]);
});
return restangularizePromise(deferred.promise, this[config.restangularFields.restangularCollection]);
}
function resolvePromise(deferred, response, data) {
if (config.fullResponse) {
return deferred.resolve(_.extend(response, {
data: data
}));
} else {
deferred.resolve(data);
}
}
// Elements
function stripRestangular(elem) {
return _.omit(elem, _.values(_.omit(config.restangularFields, 'id')));
}
function addCustomOperation(elem) {
elem.customOperation = _.bind(customFunction, elem);
_.each(["put", "post", "get", "delete"], function(oper) {
_.each(["do", "custom"], function(alias) {
var callOperation = oper === 'delete' ? 'remove' : oper;
var name = alias + oper.toUpperCase();
elem[name] = _.bind(customFunction, elem, callOperation);
});
});
elem.customGETLIST = _.bind(fetchFunction, elem);
elem.doGETLIST = elem.customGETLIST;
}
function copyRestangularizedElement(fromElement) {
var copiedElement = angular.copy(fromElement);
return restangularizeElem(copiedElement[config.restangularFields.parentResource],
copiedElement, copiedElement[config.restangularFields.route]);
}
function restangularizeElem(parent, element, route) {
var elem = config.onBeforeElemRestangularized(element, false, route);
var localElem = restangularizeBase(parent, elem, route);
localElem[config.restangularFields.restangularCollection] = false;
localElem.get = _.bind(getFunction, localElem);
localElem.getList = _.bind(fetchFunction, localElem);
localElem.put = _.bind(putFunction, localElem);
localElem.post = _.bind(postFunction, localElem);
localElem.remove = _.bind(deleteFunction, localElem);
localElem.head = _.bind(headFunction, localElem);
localElem.trace = _.bind(traceFunction, localElem);
localElem.options = _.bind(optionsFunction, localElem);
localElem.patch = _.bind(patchFunction, localElem);
addCustomOperation(localElem);
return config.transformElem(localElem, false, route, service);
}
function restangularizeCollection(parent, element, route) {
var elem = config.onBeforeElemRestangularized(element, true, route);
var localElem = restangularizeBase(parent, elem, route);
localElem[config.restangularFields.restangularCollection] = true;
localElem.post = _.bind(postFunction, localElem, null);
localElem.head = _.bind(headFunction, localElem);
localElem.trace = _.bind(traceFunction, localElem);
localElem.putElement = _.bind(putElementFunction, localElem);
localElem.options = _.bind(optionsFunction, localElem);
localElem.patch = _.bind(patchFunction, localElem);
localElem.getList = _.bind(fetchFunction, localElem, null);
addCustomOperation(localElem);
return config.transformElem(localElem, true, route, service);
}
function putElementFunction(idx, params, headers) {
var __this = this;
var elemToPut = this[idx];
var deferred = $q.defer();
elemToPut.put(params, headers).then(function(serverElem) {
var newArray = copyRestangularizedElement(__this);
newArray[idx] = serverElem;
deferred.resolve(newArray);
}, function(response) {
deferred.reject(response);
});
return restangularizePromise(deferred.promise, true)
}
function fetchFunction(what, reqParams, headers) {
var __this = this;
var deferred = $q.defer();
var operation = 'getList';
var url = urlHandler.fetchUrl(this, what);
var whatFetched = what || __this[config.restangularFields.route];
var request = config.fullRequestInterceptor(null, operation,
whatFetched, url, headers || {}, reqParams || {});
urlHandler.resource(this, $http, request.headers, request.params, what).getList().then(function(response) {
var resData = response.data;
var data = config.responseExtractor(resData, operation, whatFetched, url);
var processedData = _.map(data, function(elem) {
if (!__this[config.restangularFields.restangularCollection]) {
return restangularizeElem(__this, elem, what);
} else {
return restangularizeElem(__this[config.restangularFields.parentResource],
elem, __this[config.restangularFields.route]);
}
});
processedData = _.extend(data, processedData);
if (!__this[config.restangularFields.restangularCollection]) {
resolvePromise(deferred, response, restangularizeCollection(__this, processedData, what));
} else {
resolvePromise(deferred, response, restangularizeCollection(null, processedData, __this[config.restangularFields.route]));
}
}, function error(response) {
config.errorInterceptor(response);
deferred.reject(response);
});
return restangularizePromise(deferred.promise, true);
}
function elemFunction(operation, what, params, obj, headers) {
var __this = this;
var deferred = $q.defer();
var resParams = params || {};
var resObj = obj || this;
var route = what || this[config.restangularFields.route];
var fetchUrl = urlHandler.fetchUrl(this, what);
var callObj = obj || stripRestangular(this);
var request = config.fullRequestInterceptor(callObj, operation, route, fetchUrl,
headers || {}, resParams || {});
var okCallback = function(response) {
var resData = response.data;
var elem = config.responseExtractor(resData, operation, route, fetchUrl) || resObj;
if (operation === "post" && !__this[config.restangularFields.restangularCollection]) {
resolvePromise(deferred, response, restangularizeElem(__this, elem, what));
} else {
resolvePromise(deferred, response, restangularizeElem(__this[config.restangularFields.parentResource], elem, __this[config.restangularFields.route]));
}
};
var errorCallback = function(response) {
config.errorInterceptor(response);
deferred.reject(response);
};
// Overring HTTP Method
var callOperation = operation;
var callHeaders = _.extend({}, request.headers);
var isOverrideOperation = config.isOverridenMethod(operation);
if (isOverrideOperation) {
callOperation = 'post';
callHeaders = _.extend(callHeaders, {'X-HTTP-Method-Override': operation});
}
if (config.isSafe(operation)) {
if (isOverrideOperation) {
urlHandler.resource(this, $http, callHeaders, request.params,
what)[callOperation]({}).then(okCallback, errorCallback);
} else {
urlHandler.resource(this, $http, callHeaders, request.params,
what)[callOperation]().then(okCallback, errorCallback);
}
} else {
urlHandler.resource(this, $http, callHeaders, request.params,
what)[callOperation](request.element).then(okCallback, errorCallback);
}
return restangularizePromise(deferred.promise);
}
function getFunction(params, headers) {
return _.bind(elemFunction, this)("get", undefined, params, undefined, headers);
}
function deleteFunction(params, headers) {
return _.bind(elemFunction, this)("remove", undefined, params, undefined, headers);
}
function putFunction(params, headers) {
return _.bind(elemFunction, this)("put", undefined, params, undefined, headers);
}
function postFunction(what, elem, params, headers) {
return _.bind(elemFunction, this)("post", what, params, elem, headers);
}
function headFunction(params, headers) {
return _.bind(elemFunction, this)("head", undefined, params, undefined, headers);
}
function traceFunction(params, headers) {
return _.bind(elemFunction, this)("trace", undefined, params, undefined, headers);
}
function optionsFunction(params, headers) {
return _.bind(elemFunction, this)("options", undefined, params, undefined, headers);
}
function patchFunction(elem, params, headers) {
return _.bind(elemFunction, this)("patch", undefined, params, elem, headers);
}
function customFunction(operation, path, params, headers, elem) {
return _.bind(elemFunction, this)(operation, path, params, elem, headers);
}
function addRestangularMethodFunction(name, operation, path, defaultParams, defaultHeaders, defaultElem) {
var bindedFunction;
if (operation === 'getList') {
bindedFunction = _.bind(fetchFunction, this, path);
} else {
bindedFunction = _.bind(customFunction, this, operation, path);
}
this[name] = function(params, headers, elem) {
var callParams = _.defaults({
params: params,
headers: headers,
elem: elem
}, {
params: defaultParams,
headers: defaultHeaders,
elem: defaultElem
});
return bindedFunction(callParams.params, callParams.headers, callParams.elem);
}
}
function withConfigurationFunction(configurer) {
var newConfig = angular.copy(globalConfiguration);
Configurer.init(newConfig, newConfig);
configurer(newConfig);
return createServiceForConfiguration(newConfig);
}
Configurer.init(service, config);
service.copy = _.bind(copyRestangularizedElement, service);
service.withConfig = _.bind(withConfigurationFunction, service);
service.one = _.bind(one, service, null);
service.all = _.bind(all, service, null);
service.restangularizeElement = _.bind(restangularizeElem, service);
service.restangularizeCollection = _.bind(restangularizeCollection, service);
return service;
}
return createServiceForConfiguration(globalConfiguration);
}];
}
);
})();

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,185 @@
/**
* @license AngularJS v1.1.5
* (c) 2010-2012 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, angular, undefined) {
'use strict';
/**
* @ngdoc overview
* @name ngCookies
*/
angular.module('ngCookies', ['ng']).
/**
* @ngdoc object
* @name ngCookies.$cookies
* @requires $browser
*
* @description
* Provides read/write access to browser's cookies.
*
* Only a simple Object is exposed and by adding or removing properties to/from
* this object, new cookies are created/deleted at the end of current $eval.
*
* @example
<doc:example>
<doc:source>
<script>
function ExampleController($cookies) {
// Retrieving a cookie
var favoriteCookie = $cookies.myFavorite;
// Setting a cookie
$cookies.myFavorite = 'oatmeal';
}
</script>
</doc:source>
</doc:example>
*/
factory('$cookies', ['$rootScope', '$browser', function ($rootScope, $browser) {
var cookies = {},
lastCookies = {},
lastBrowserCookies,
runEval = false,
copy = angular.copy,
isUndefined = angular.isUndefined;
//creates a poller fn that copies all cookies from the $browser to service & inits the service
$browser.addPollFn(function() {
var currentCookies = $browser.cookies();
if (lastBrowserCookies != currentCookies) { //relies on browser.cookies() impl
lastBrowserCookies = currentCookies;
copy(currentCookies, lastCookies);
copy(currentCookies, cookies);
if (runEval) $rootScope.$apply();
}
})();
runEval = true;
//at the end of each eval, push cookies
//TODO: this should happen before the "delayed" watches fire, because if some cookies are not
// strings or browser refuses to store some cookies, we update the model in the push fn.
$rootScope.$watch(push);
return cookies;
/**
* Pushes all the cookies from the service to the browser and verifies if all cookies were stored.
*/
function push() {
var name,
value,
browserCookies,
updated;
//delete any cookies deleted in $cookies
for (name in lastCookies) {
if (isUndefined(cookies[name])) {
$browser.cookies(name, undefined);
}
}
//update all cookies updated in $cookies
for(name in cookies) {
value = cookies[name];
if (!angular.isString(value)) {
if (angular.isDefined(lastCookies[name])) {
cookies[name] = lastCookies[name];
} else {
delete cookies[name];
}
} else if (value !== lastCookies[name]) {
$browser.cookies(name, value);
updated = true;
}
}
//verify what was actually stored
if (updated){
updated = false;
browserCookies = $browser.cookies();
for (name in cookies) {
if (cookies[name] !== browserCookies[name]) {
//delete or reset all cookies that the browser dropped from $cookies
if (isUndefined(browserCookies[name])) {
delete cookies[name];
} else {
cookies[name] = browserCookies[name];
}
updated = true;
}
}
}
}
}]).
/**
* @ngdoc object
* @name ngCookies.$cookieStore
* @requires $cookies
*
* @description
* Provides a key-value (string-object) storage, that is backed by session cookies.
* Objects put or retrieved from this storage are automatically serialized or
* deserialized by angular's toJson/fromJson.
* @example
*/
factory('$cookieStore', ['$cookies', function($cookies) {
return {
/**
* @ngdoc method
* @name ngCookies.$cookieStore#get
* @methodOf ngCookies.$cookieStore
*
* @description
* Returns the value of given cookie key
*
* @param {string} key Id to use for lookup.
* @returns {Object} Deserialized cookie value.
*/
get: function(key) {
var value = $cookies[key];
return value ? angular.fromJson(value) : value;
},
/**
* @ngdoc method
* @name ngCookies.$cookieStore#put
* @methodOf ngCookies.$cookieStore
*
* @description
* Sets a value for given cookie key
*
* @param {string} key Id for the `value`.
* @param {Object} value Value to be stored.
*/
put: function(key, value) {
$cookies[key] = angular.toJson(value);
},
/**
* @ngdoc method
* @name ngCookies.$cookieStore#remove
* @methodOf ngCookies.$cookieStore
*
* @description
* Remove given cookie
*
* @param {string} key Id of the key-value pair to delete.
*/
remove: function(key) {
delete $cookies[key];
}
};
}]);
})(window, window.angular);

View File

@@ -0,0 +1,7 @@
/*
AngularJS v1.1.5
(c) 2010-2012 Google, Inc. http://angularjs.org
License: MIT
*/
(function(m,f,l){'use strict';f.module("ngCookies",["ng"]).factory("$cookies",["$rootScope","$browser",function(d,b){var c={},g={},h,i=!1,j=f.copy,k=f.isUndefined;b.addPollFn(function(){var a=b.cookies();h!=a&&(h=a,j(a,g),j(a,c),i&&d.$apply())})();i=!0;d.$watch(function(){var a,e,d;for(a in g)k(c[a])&&b.cookies(a,l);for(a in c)e=c[a],f.isString(e)?e!==g[a]&&(b.cookies(a,e),d=!0):f.isDefined(g[a])?c[a]=g[a]:delete c[a];if(d)for(a in e=b.cookies(),c)c[a]!==e[a]&&(k(e[a])?delete c[a]:c[a]=e[a])});return c}]).factory("$cookieStore",
["$cookies",function(d){return{get:function(b){return(b=d[b])?f.fromJson(b):b},put:function(b,c){d[b]=f.toJson(c)},remove:function(b){delete d[b]}}}])})(window,window.angular);

View File

@@ -0,0 +1,304 @@
/**
* @license AngularJS v1.1.5
* (c) 2010-2012 Google, Inc. http://angularjs.org
* License: MIT
*/
(
/**
* @ngdoc interface
* @name angular.Module
* @description
*
* Interface for configuring angular {@link angular.module modules}.
*/
function setupModuleLoader(window) {
function ensure(obj, name, factory) {
return obj[name] || (obj[name] = factory());
}
return ensure(ensure(window, 'angular', Object), 'module', function() {
/** @type {Object.<string, angular.Module>} */
var modules = {};
/**
* @ngdoc function
* @name angular.module
* @description
*
* The `angular.module` is a global place for creating and registering Angular modules. All
* modules (angular core or 3rd party) that should be available to an application must be
* registered using this mechanism.
*
*
* # Module
*
* A module is a collocation of services, directives, filters, and configuration information. Module
* is used to configure the {@link AUTO.$injector $injector}.
*
* <pre>
* // Create a new module
* var myModule = angular.module('myModule', []);
*
* // register a new service
* myModule.value('appName', 'MyCoolApp');
*
* // configure existing services inside initialization blocks.
* myModule.config(function($locationProvider) {
'use strict';
* // Configure existing providers
* $locationProvider.hashPrefix('!');
* });
* </pre>
*
* Then you can create an injector and load your modules like this:
*
* <pre>
* var injector = angular.injector(['ng', 'MyModule'])
* </pre>
*
* However it's more likely that you'll just use
* {@link ng.directive:ngApp ngApp} or
* {@link angular.bootstrap} to simplify this process for you.
*
* @param {!string} name The name of the module to create or retrieve.
* @param {Array.<string>=} requires If specified then new module is being created. If unspecified then the
* the module is being retrieved for further configuration.
* @param {Function} configFn Optional configuration function for the module. Same as
* {@link angular.Module#config Module#config()}.
* @returns {module} new module with the {@link angular.Module} api.
*/
return function module(name, requires, configFn) {
if (requires && modules.hasOwnProperty(name)) {
modules[name] = null;
}
return ensure(modules, name, function() {
if (!requires) {
throw Error('No module: ' + name);
}
/** @type {!Array.<Array.<*>>} */
var invokeQueue = [];
/** @type {!Array.<Function>} */
var runBlocks = [];
var config = invokeLater('$injector', 'invoke');
/** @type {angular.Module} */
var moduleInstance = {
// Private state
_invokeQueue: invokeQueue,
_runBlocks: runBlocks,
/**
* @ngdoc property
* @name angular.Module#requires
* @propertyOf angular.Module
* @returns {Array.<string>} List of module names which must be loaded before this module.
* @description
* Holds the list of modules which the injector will load before the current module is loaded.
*/
requires: requires,
/**
* @ngdoc property
* @name angular.Module#name
* @propertyOf angular.Module
* @returns {string} Name of the module.
* @description
*/
name: name,
/**
* @ngdoc method
* @name angular.Module#provider
* @methodOf angular.Module
* @param {string} name service name
* @param {Function} providerType Construction function for creating new instance of the service.
* @description
* See {@link AUTO.$provide#provider $provide.provider()}.
*/
provider: invokeLater('$provide', 'provider'),
/**
* @ngdoc method
* @name angular.Module#factory
* @methodOf angular.Module
* @param {string} name service name
* @param {Function} providerFunction Function for creating new instance of the service.
* @description
* See {@link AUTO.$provide#factory $provide.factory()}.
*/
factory: invokeLater('$provide', 'factory'),
/**
* @ngdoc method
* @name angular.Module#service
* @methodOf angular.Module
* @param {string} name service name
* @param {Function} constructor A constructor function that will be instantiated.
* @description
* See {@link AUTO.$provide#service $provide.service()}.
*/
service: invokeLater('$provide', 'service'),
/**
* @ngdoc method
* @name angular.Module#value
* @methodOf angular.Module
* @param {string} name service name
* @param {*} object Service instance object.
* @description
* See {@link AUTO.$provide#value $provide.value()}.
*/
value: invokeLater('$provide', 'value'),
/**
* @ngdoc method
* @name angular.Module#constant
* @methodOf angular.Module
* @param {string} name constant name
* @param {*} object Constant value.
* @description
* Because the constant are fixed, they get applied before other provide methods.
* See {@link AUTO.$provide#constant $provide.constant()}.
*/
constant: invokeLater('$provide', 'constant', 'unshift'),
/**
* @ngdoc method
* @name angular.Module#animation
* @methodOf angular.Module
* @param {string} name animation name
* @param {Function} animationFactory Factory function for creating new instance of an animation.
* @description
*
* Defines an animation hook that can be later used with {@link ng.directive:ngAnimate ngAnimate}
* alongside {@link ng.directive:ngAnimate#Description common ng directives} as well as custom directives.
* <pre>
* module.animation('animation-name', function($inject1, $inject2) {
* return {
* //this gets called in preparation to setup an animation
* setup : function(element) { ... },
*
* //this gets called once the animation is run
* start : function(element, done, memo) { ... }
* }
* })
* </pre>
*
* See {@link ng.$animationProvider#register $animationProvider.register()} and
* {@link ng.directive:ngAnimate ngAnimate} for more information.
*/
animation: invokeLater('$animationProvider', 'register'),
/**
* @ngdoc method
* @name angular.Module#filter
* @methodOf angular.Module
* @param {string} name Filter name.
* @param {Function} filterFactory Factory function for creating new instance of filter.
* @description
* See {@link ng.$filterProvider#register $filterProvider.register()}.
*/
filter: invokeLater('$filterProvider', 'register'),
/**
* @ngdoc method
* @name angular.Module#controller
* @methodOf angular.Module
* @param {string} name Controller name.
* @param {Function} constructor Controller constructor function.
* @description
* See {@link ng.$controllerProvider#register $controllerProvider.register()}.
*/
controller: invokeLater('$controllerProvider', 'register'),
/**
* @ngdoc method
* @name angular.Module#directive
* @methodOf angular.Module
* @param {string} name directive name
* @param {Function} directiveFactory Factory function for creating new instance of
* directives.
* @description
* See {@link ng.$compileProvider#directive $compileProvider.directive()}.
*/
directive: invokeLater('$compileProvider', 'directive'),
/**
* @ngdoc method
* @name angular.Module#config
* @methodOf angular.Module
* @param {Function} configFn Execute this function on module load. Useful for service
* configuration.
* @description
* Use this method to register work which needs to be performed on module loading.
*/
config: config,
/**
* @ngdoc method
* @name angular.Module#run
* @methodOf angular.Module
* @param {Function} initializationFn Execute this function after injector creation.
* Useful for application initialization.
* @description
* Use this method to register work which should be performed when the injector is done
* loading all modules.
*/
run: function(block) {
runBlocks.push(block);
return this;
}
};
if (configFn) {
config(configFn);
}
return moduleInstance;
/**
* @param {string} provider
* @param {string} method
* @param {String=} insertMethod
* @returns {angular.Module}
*/
function invokeLater(provider, method, insertMethod) {
return function() {
invokeQueue[insertMethod || 'push']([provider, method, arguments]);
return moduleInstance;
}
}
});
};
});
}
)(window);
/**
* Closure compiler type information
*
* @typedef { {
* requires: !Array.<string>,
* invokeQueue: !Array.<Array.<*>>,
*
* service: function(string, Function):angular.Module,
* factory: function(string, Function):angular.Module,
* value: function(string, *):angular.Module,
*
* filter: function(string, Function):angular.Module,
*
* init: function(Function):angular.Module
* } }
*/
angular.Module;

View File

@@ -0,0 +1,7 @@
/*
AngularJS v1.1.5
(c) 2010-2012 Google, Inc. http://angularjs.org
License: MIT
*/
(function(i){'use strict';function d(c,b,e){return c[b]||(c[b]=e())}return d(d(i,"angular",Object),"module",function(){var c={};return function(b,e,f){e&&c.hasOwnProperty(b)&&(c[b]=null);return d(c,b,function(){function a(a,b,d){return function(){c[d||"push"]([a,b,arguments]);return g}}if(!e)throw Error("No module: "+b);var c=[],d=[],h=a("$injector","invoke"),g={_invokeQueue:c,_runBlocks:d,requires:e,name:b,provider:a("$provide","provider"),factory:a("$provide","factory"),service:a("$provide","service"),
value:a("$provide","value"),constant:a("$provide","constant","unshift"),animation:a("$animationProvider","register"),filter:a("$filterProvider","register"),controller:a("$controllerProvider","register"),directive:a("$compileProvider","directive"),config:h,run:function(a){d.push(a);return this}};f&&h(f);return g})}})})(window);

View File

@@ -0,0 +1,460 @@
/**
* @license AngularJS v1.1.5
* (c) 2010-2012 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, angular, undefined) {
'use strict';
/**
* @ngdoc overview
* @name ngMobile
* @description
* Touch events and other mobile helpers.
* Based on jQuery Mobile touch event handling (jquerymobile.com)
*/
// define ngMobile module
var ngMobile = angular.module('ngMobile', []);
/**
* @ngdoc directive
* @name ngMobile.directive:ngClick
*
* @description
* A more powerful replacement for the default ngClick designed to be used on touchscreen
* devices. Most mobile browsers wait about 300ms after a tap-and-release before sending
* the click event. This version handles them immediately, and then prevents the
* following click event from propagating.
*
* This directive can fall back to using an ordinary click event, and so works on desktop
* browsers as well as mobile.
*
* This directive also sets the CSS class `ng-click-active` while the element is being held
* down (by a mouse click or touch) so you can restyle the depressed element if you wish.
*
* @element ANY
* @param {expression} ngClick {@link guide/expression Expression} to evaluate
* upon tap. (Event object is available as `$event`)
*
* @example
<doc:example>
<doc:source>
<button ng-click="count = count + 1" ng-init="count=0">
Increment
</button>
count: {{ count }}
</doc:source>
</doc:example>
*/
ngMobile.config(['$provide', function($provide) {
$provide.decorator('ngClickDirective', ['$delegate', function($delegate) {
// drop the default ngClick directive
$delegate.shift();
return $delegate;
}]);
}]);
ngMobile.directive('ngClick', ['$parse', '$timeout', '$rootElement',
function($parse, $timeout, $rootElement) {
var TAP_DURATION = 750; // Shorter than 750ms is a tap, longer is a taphold or drag.
var MOVE_TOLERANCE = 12; // 12px seems to work in most mobile browsers.
var PREVENT_DURATION = 2500; // 2.5 seconds maximum from preventGhostClick call to click
var CLICKBUSTER_THRESHOLD = 25; // 25 pixels in any dimension is the limit for busting clicks.
var ACTIVE_CLASS_NAME = 'ng-click-active';
var lastPreventedTime;
var touchCoordinates;
// TAP EVENTS AND GHOST CLICKS
//
// Why tap events?
// Mobile browsers detect a tap, then wait a moment (usually ~300ms) to see if you're
// double-tapping, and then fire a click event.
//
// This delay sucks and makes mobile apps feel unresponsive.
// So we detect touchstart, touchmove, touchcancel and touchend ourselves and determine when
// the user has tapped on something.
//
// What happens when the browser then generates a click event?
// The browser, of course, also detects the tap and fires a click after a delay. This results in
// tapping/clicking twice. So we do "clickbusting" to prevent it.
//
// How does it work?
// We attach global touchstart and click handlers, that run during the capture (early) phase.
// So the sequence for a tap is:
// - global touchstart: Sets an "allowable region" at the point touched.
// - element's touchstart: Starts a touch
// (- touchmove or touchcancel ends the touch, no click follows)
// - element's touchend: Determines if the tap is valid (didn't move too far away, didn't hold
// too long) and fires the user's tap handler. The touchend also calls preventGhostClick().
// - preventGhostClick() removes the allowable region the global touchstart created.
// - The browser generates a click event.
// - The global click handler catches the click, and checks whether it was in an allowable region.
// - If preventGhostClick was called, the region will have been removed, the click is busted.
// - If the region is still there, the click proceeds normally. Therefore clicks on links and
// other elements without ngTap on them work normally.
//
// This is an ugly, terrible hack!
// Yeah, tell me about it. The alternatives are using the slow click events, or making our users
// deal with the ghost clicks, so I consider this the least of evils. Fortunately Angular
// encapsulates this ugly logic away from the user.
//
// Why not just put click handlers on the element?
// We do that too, just to be sure. The problem is that the tap event might have caused the DOM
// to change, so that the click fires in the same position but something else is there now. So
// the handlers are global and care only about coordinates and not elements.
// Checks if the coordinates are close enough to be within the region.
function hit(x1, y1, x2, y2) {
return Math.abs(x1 - x2) < CLICKBUSTER_THRESHOLD && Math.abs(y1 - y2) < CLICKBUSTER_THRESHOLD;
}
// Checks a list of allowable regions against a click location.
// Returns true if the click should be allowed.
// Splices out the allowable region from the list after it has been used.
function checkAllowableRegions(touchCoordinates, x, y) {
for (var i = 0; i < touchCoordinates.length; i += 2) {
if (hit(touchCoordinates[i], touchCoordinates[i+1], x, y)) {
touchCoordinates.splice(i, i + 2);
return true; // allowable region
}
}
return false; // No allowable region; bust it.
}
// Global click handler that prevents the click if it's in a bustable zone and preventGhostClick
// was called recently.
function onClick(event) {
if (Date.now() - lastPreventedTime > PREVENT_DURATION) {
return; // Too old.
}
var touches = event.touches && event.touches.length ? event.touches : [event];
var x = touches[0].clientX;
var y = touches[0].clientY;
// Work around desktop Webkit quirk where clicking a label will fire two clicks (on the label
// and on the input element). Depending on the exact browser, this second click we don't want
// to bust has either (0,0) or negative coordinates.
if (x < 1 && y < 1) {
return; // offscreen
}
// Look for an allowable region containing this click.
// If we find one, that means it was created by touchstart and not removed by
// preventGhostClick, so we don't bust it.
if (checkAllowableRegions(touchCoordinates, x, y)) {
return;
}
// If we didn't find an allowable region, bust the click.
event.stopPropagation();
event.preventDefault();
}
// Global touchstart handler that creates an allowable region for a click event.
// This allowable region can be removed by preventGhostClick if we want to bust it.
function onTouchStart(event) {
var touches = event.touches && event.touches.length ? event.touches : [event];
var x = touches[0].clientX;
var y = touches[0].clientY;
touchCoordinates.push(x, y);
$timeout(function() {
// Remove the allowable region.
for (var i = 0; i < touchCoordinates.length; i += 2) {
if (touchCoordinates[i] == x && touchCoordinates[i+1] == y) {
touchCoordinates.splice(i, i + 2);
return;
}
}
}, PREVENT_DURATION, false);
}
// On the first call, attaches some event handlers. Then whenever it gets called, it creates a
// zone around the touchstart where clicks will get busted.
function preventGhostClick(x, y) {
if (!touchCoordinates) {
$rootElement[0].addEventListener('click', onClick, true);
$rootElement[0].addEventListener('touchstart', onTouchStart, true);
touchCoordinates = [];
}
lastPreventedTime = Date.now();
checkAllowableRegions(touchCoordinates, x, y);
}
// Actual linking function.
return function(scope, element, attr) {
var clickHandler = $parse(attr.ngClick),
tapping = false,
tapElement, // Used to blur the element after a tap.
startTime, // Used to check if the tap was held too long.
touchStartX,
touchStartY;
function resetState() {
tapping = false;
element.removeClass(ACTIVE_CLASS_NAME);
}
element.bind('touchstart', function(event) {
tapping = true;
tapElement = event.target ? event.target : event.srcElement; // IE uses srcElement.
// Hack for Safari, which can target text nodes instead of containers.
if(tapElement.nodeType == 3) {
tapElement = tapElement.parentNode;
}
element.addClass(ACTIVE_CLASS_NAME);
startTime = Date.now();
var touches = event.touches && event.touches.length ? event.touches : [event];
var e = touches[0].originalEvent || touches[0];
touchStartX = e.clientX;
touchStartY = e.clientY;
});
element.bind('touchmove', function(event) {
resetState();
});
element.bind('touchcancel', function(event) {
resetState();
});
element.bind('touchend', function(event) {
var diff = Date.now() - startTime;
var touches = (event.changedTouches && event.changedTouches.length) ? event.changedTouches :
((event.touches && event.touches.length) ? event.touches : [event]);
var e = touches[0].originalEvent || touches[0];
var x = e.clientX;
var y = e.clientY;
var dist = Math.sqrt( Math.pow(x - touchStartX, 2) + Math.pow(y - touchStartY, 2) );
if (tapping && diff < TAP_DURATION && dist < MOVE_TOLERANCE) {
// Call preventGhostClick so the clickbuster will catch the corresponding click.
preventGhostClick(x, y);
// Blur the focused element (the button, probably) before firing the callback.
// This doesn't work perfectly on Android Chrome, but seems to work elsewhere.
// I couldn't get anything to work reliably on Android Chrome.
if (tapElement) {
tapElement.blur();
}
scope.$apply(function() {
// TODO(braden): This is sending the touchend, not a tap or click. Is that kosher?
clickHandler(scope, {$event: event});
});
}
resetState();
});
// Hack for iOS Safari's benefit. It goes searching for onclick handlers and is liable to click
// something else nearby.
element.onclick = function(event) { };
// Fallback click handler.
// Busted clicks don't get this far, and adding this handler allows ng-tap to be used on
// desktop as well, to allow more portable sites.
element.bind('click', function(event) {
scope.$apply(function() {
clickHandler(scope, {$event: event});
});
});
element.bind('mousedown', function(event) {
element.addClass(ACTIVE_CLASS_NAME);
});
element.bind('mousemove mouseup', function(event) {
element.removeClass(ACTIVE_CLASS_NAME);
});
};
}]);
/**
* @ngdoc directive
* @name ngMobile.directive:ngSwipeLeft
*
* @description
* Specify custom behavior when an element is swiped to the left on a touchscreen device.
* A leftward swipe is a quick, right-to-left slide of the finger.
* Though ngSwipeLeft is designed for touch-based devices, it will work with a mouse click and drag too.
*
* @element ANY
* @param {expression} ngSwipeLeft {@link guide/expression Expression} to evaluate
* upon left swipe. (Event object is available as `$event`)
*
* @example
<doc:example>
<doc:source>
<div ng-show="!showActions" ng-swipe-left="showActions = true">
Some list content, like an email in the inbox
</div>
<div ng-show="showActions" ng-swipe-right="showActions = false">
<button ng-click="reply()">Reply</button>
<button ng-click="delete()">Delete</button>
</div>
</doc:source>
</doc:example>
*/
/**
* @ngdoc directive
* @name ngMobile.directive:ngSwipeRight
*
* @description
* Specify custom behavior when an element is swiped to the right on a touchscreen device.
* A rightward swipe is a quick, left-to-right slide of the finger.
* Though ngSwipeRight is designed for touch-based devices, it will work with a mouse click and drag too.
*
* @element ANY
* @param {expression} ngSwipeRight {@link guide/expression Expression} to evaluate
* upon right swipe. (Event object is available as `$event`)
*
* @example
<doc:example>
<doc:source>
<div ng-show="!showActions" ng-swipe-left="showActions = true">
Some list content, like an email in the inbox
</div>
<div ng-show="showActions" ng-swipe-right="showActions = false">
<button ng-click="reply()">Reply</button>
<button ng-click="delete()">Delete</button>
</div>
</doc:source>
</doc:example>
*/
function makeSwipeDirective(directiveName, direction) {
ngMobile.directive(directiveName, ['$parse', function($parse) {
// The maximum vertical delta for a swipe should be less than 75px.
var MAX_VERTICAL_DISTANCE = 75;
// Vertical distance should not be more than a fraction of the horizontal distance.
var MAX_VERTICAL_RATIO = 0.3;
// At least a 30px lateral motion is necessary for a swipe.
var MIN_HORIZONTAL_DISTANCE = 30;
// The total distance in any direction before we make the call on swipe vs. scroll.
var MOVE_BUFFER_RADIUS = 10;
function getCoordinates(event) {
var touches = event.touches && event.touches.length ? event.touches : [event];
var e = (event.changedTouches && event.changedTouches[0]) ||
(event.originalEvent && event.originalEvent.changedTouches &&
event.originalEvent.changedTouches[0]) ||
touches[0].originalEvent || touches[0];
return {
x: e.clientX,
y: e.clientY
};
}
return function(scope, element, attr) {
var swipeHandler = $parse(attr[directiveName]);
var startCoords, valid;
var totalX, totalY;
var lastX, lastY;
function validSwipe(event) {
// Check that it's within the coordinates.
// Absolute vertical distance must be within tolerances.
// Horizontal distance, we take the current X - the starting X.
// This is negative for leftward swipes and positive for rightward swipes.
// After multiplying by the direction (-1 for left, +1 for right), legal swipes
// (ie. same direction as the directive wants) will have a positive delta and
// illegal ones a negative delta.
// Therefore this delta must be positive, and larger than the minimum.
if (!startCoords) return false;
var coords = getCoordinates(event);
var deltaY = Math.abs(coords.y - startCoords.y);
var deltaX = (coords.x - startCoords.x) * direction;
return valid && // Short circuit for already-invalidated swipes.
deltaY < MAX_VERTICAL_DISTANCE &&
deltaX > 0 &&
deltaX > MIN_HORIZONTAL_DISTANCE &&
deltaY / deltaX < MAX_VERTICAL_RATIO;
}
element.bind('touchstart mousedown', function(event) {
startCoords = getCoordinates(event);
valid = true;
totalX = 0;
totalY = 0;
lastX = startCoords.x;
lastY = startCoords.y;
});
element.bind('touchcancel', function(event) {
valid = false;
});
element.bind('touchmove mousemove', function(event) {
if (!valid) return;
// Android will send a touchcancel if it thinks we're starting to scroll.
// So when the total distance (+ or - or both) exceeds 10px in either direction,
// we either:
// - On totalX > totalY, we send preventDefault() and treat this as a swipe.
// - On totalY > totalX, we let the browser handle it as a scroll.
// Invalidate a touch while it's in progress if it strays too far away vertically.
// We don't want a scroll down and back up while drifting sideways to be a swipe just
// because you happened to end up vertically close in the end.
if (!startCoords) return;
var coords = getCoordinates(event);
if (Math.abs(coords.y - startCoords.y) > MAX_VERTICAL_DISTANCE) {
valid = false;
return;
}
totalX += Math.abs(coords.x - lastX);
totalY += Math.abs(coords.y - lastY);
lastX = coords.x;
lastY = coords.y;
if (totalX < MOVE_BUFFER_RADIUS && totalY < MOVE_BUFFER_RADIUS) {
return;
}
// One of totalX or totalY has exceeded the buffer, so decide on swipe vs. scroll.
if (totalY > totalX) {
valid = false;
return;
} else {
event.preventDefault();
}
});
element.bind('touchend mouseup', function(event) {
if (validSwipe(event)) {
// Prevent this swipe from bubbling up to any other elements with ngSwipes.
event.stopPropagation();
scope.$apply(function() {
swipeHandler(scope, {$event:event});
});
}
});
};
}]);
}
// Left is negative X-coordinate, right is positive.
makeSwipeDirective('ngSwipeLeft', -1);
makeSwipeDirective('ngSwipeRight', 1);
})(window, window.angular);

View File

@@ -0,0 +1,11 @@
/*
AngularJS v1.1.5
(c) 2010-2012 Google, Inc. http://angularjs.org
License: MIT
*/
(function(u,s){'use strict';function k(i,t){j.directive(i,["$parse",function(l){function g(b){var h=b.touches&&b.touches.length?b.touches:[b],b=b.changedTouches&&b.changedTouches[0]||b.originalEvent&&b.originalEvent.changedTouches&&b.originalEvent.changedTouches[0]||h[0].originalEvent||h[0];return{x:b.clientX,y:b.clientY}}var m=75,j=0.3,p=30;return function(b,h,n){function o(e){if(!a)return!1;var b=g(e),e=Math.abs(b.y-a.y),b=(b.x-a.x)*t;return c&&e<m&&b>0&&b>p&&e/b<j}var d=l(n[i]),a,c,f,e,q,r;h.bind("touchstart mousedown",
function(b){a=g(b);c=!0;e=f=0;q=a.x;r=a.y});h.bind("touchcancel",function(){c=!1});h.bind("touchmove mousemove",function(b){if(c&&a){var d=g(b);Math.abs(d.y-a.y)>m?c=!1:(f+=Math.abs(d.x-q),e+=Math.abs(d.y-r),q=d.x,r=d.y,f<10&&e<10||(e>f?c=!1:b.preventDefault()))}});h.bind("touchend mouseup",function(a){o(a)&&(a.stopPropagation(),b.$apply(function(){d(b,{$event:a})}))})}}])}var j=s.module("ngMobile",[]);j.config(["$provide",function(i){i.decorator("ngClickDirective",["$delegate",function(i){i.shift();
return i}])}]);j.directive("ngClick",["$parse","$timeout","$rootElement",function(i,j,l){function g(a,c,b){for(var e=0;e<a.length;e+=2)if(Math.abs(a[e]-c)<h&&Math.abs(a[e+1]-b)<h)return a.splice(e,e+2),!0;return!1}function m(a){if(!(Date.now()-o>b)){var c=a.touches&&a.touches.length?a.touches:[a],f=c[0].clientX,c=c[0].clientY;!(f<1&&c<1)&&!g(d,f,c)&&(a.stopPropagation(),a.preventDefault())}}function k(a){var a=a.touches&&a.touches.length?a.touches:[a],c=a[0].clientX,f=a[0].clientY;d.push(c,f);j(function(){for(var a=
0;a<d.length;a+=2)if(d[a]==c&&d[a+1]==f){d.splice(a,a+2);break}},b,!1)}function p(a,b){d||(l[0].addEventListener("click",m,!0),l[0].addEventListener("touchstart",k,!0),d=[]);o=Date.now();g(d,a,b)}var b=2500,h=25,n="ng-click-active",o,d;return function(a,b,d){function e(){j=!1;b.removeClass(n)}var h=i(d.ngClick),j=!1,g,k,l,m;b.bind("touchstart",function(a){j=!0;g=a.target?a.target:a.srcElement;if(g.nodeType==3)g=g.parentNode;b.addClass(n);k=Date.now();a=a.touches&&a.touches.length?a.touches:[a];a=
a[0].originalEvent||a[0];l=a.clientX;m=a.clientY});b.bind("touchmove",function(){e()});b.bind("touchcancel",function(){e()});b.bind("touchend",function(b){var d=Date.now()-k,c=b.changedTouches&&b.changedTouches.length?b.changedTouches:b.touches&&b.touches.length?b.touches:[b],f=c[0].originalEvent||c[0],c=f.clientX,f=f.clientY,i=Math.sqrt(Math.pow(c-l,2)+Math.pow(f-m,2));j&&d<750&&i<12&&(p(c,f),g&&g.blur(),a.$apply(function(){h(a,{$event:b})}));e()});b.onclick=function(){};b.bind("click",function(b){a.$apply(function(){h(a,
{$event:b})})});b.bind("mousedown",function(){b.addClass(n)});b.bind("mousemove mouseup",function(){b.removeClass(n)})}}]);k("ngSwipeLeft",-1);k("ngSwipeRight",1)})(window,window.angular);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,537 @@
/**
* @license AngularJS v1.1.5
* (c) 2010-2012 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, angular, undefined) {
'use strict';
/**
* @ngdoc overview
* @name ngResource
* @description
*/
/**
* @ngdoc object
* @name ngResource.$resource
* @requires $http
*
* @description
* A factory which creates a resource object that lets you interact with
* [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources.
*
* The returned resource object has action methods which provide high-level behaviors without
* the need to interact with the low level {@link ng.$http $http} service.
*
* # Installation
* To use $resource make sure you have included the `angular-resource.js` that comes in Angular
* package. You can also find this file on Google CDN, bower as well as at
* {@link http://code.angularjs.org/ code.angularjs.org}.
*
* Finally load the module in your application:
*
* angular.module('app', ['ngResource']);
*
* and you are ready to get started!
*
* @param {string} url A parametrized URL template with parameters prefixed by `:` as in
* `/user/:username`. If you are using a URL with a port number (e.g.
* `http://example.com:8080/api`), you'll need to escape the colon character before the port
* number, like this: `$resource('http://example.com\\:8080/api')`.
*
* If you are using a url with a suffix, just add the suffix, like this:
* `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json')
* or even `$resource('http://example.com/resource/:resource_id.:format')`
* If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be
* collapsed down to a single `.`. If you need this sequence to appear and not collapse then you
* can escape it with `/\.`.
*
* @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in
* `actions` methods. If any of the parameter value is a function, it will be executed every time
* when a param value needs to be obtained for a request (unless the param was overridden).
*
* Each key value in the parameter object is first bound to url template if present and then any
* excess keys are appended to the url search query after the `?`.
*
* Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in
* URL `/path/greet?salutation=Hello`.
*
* If the parameter value is prefixed with `@` then the value of that parameter is extracted from
* the data object (useful for non-GET operations).
*
* @param {Object.<Object>=} actions Hash with declaration of custom action that should extend the
* default set of resource actions. The declaration should be created in the format of {@link
* ng.$http#Parameters $http.config}:
*
* {action1: {method:?, params:?, isArray:?, headers:?, ...},
* action2: {method:?, params:?, isArray:?, headers:?, ...},
* ...}
*
* Where:
*
* - **`action`** {string} The name of action. This name becomes the name of the method on your
* resource object.
* - **`method`** {string} HTTP request method. Valid methods are: `GET`, `POST`, `PUT`, `DELETE`,
* and `JSONP`.
* - **`params`** {Object=} Optional set of pre-bound parameters for this action. If any of the
* parameter value is a function, it will be executed every time when a param value needs to be
* obtained for a request (unless the param was overridden).
* - **`url`** {string} action specific `url` override. The url templating is supported just like
* for the resource-level urls.
* - **`isArray`** {boolean=} If true then the returned object for this action is an array, see
* `returns` section.
* - **`transformRequest`** `{function(data, headersGetter)|Array.<function(data, headersGetter)>}`
* transform function or an array of such functions. The transform function takes the http
* request body and headers and returns its transformed (typically serialized) version.
* - **`transformResponse`** `{function(data, headersGetter)|Array.<function(data, headersGetter)>}`
* transform function or an array of such functions. The transform function takes the http
* response body and headers and returns its transformed (typically deserialized) version.
* - **`cache`** `{boolean|Cache}` If true, a default $http cache will be used to cache the
* GET request, otherwise if a cache instance built with
* {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
* caching.
* - **`timeout`** `{number|Promise}` timeout in milliseconds, or {@link ng.$q promise} that
* should abort the request when resolved.
* - **`withCredentials`** - `{boolean}` - whether to to set the `withCredentials` flag on the
* XHR object. See {@link https://developer.mozilla.org/en/http_access_control#section_5
* requests with credentials} for more information.
* - **`responseType`** - `{string}` - see {@link
* https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType requestType}.
*
* @returns {Object} A resource "class" object with methods for the default set of resource actions
* optionally extended with custom `actions`. The default set contains these actions:
*
* { 'get': {method:'GET'},
* 'save': {method:'POST'},
* 'query': {method:'GET', isArray:true},
* 'remove': {method:'DELETE'},
* 'delete': {method:'DELETE'} };
*
* Calling these methods invoke an {@link ng.$http} with the specified http method,
* destination and parameters. When the data is returned from the server then the object is an
* instance of the resource class. The actions `save`, `remove` and `delete` are available on it
* as methods with the `$` prefix. This allows you to easily perform CRUD operations (create,
* read, update, delete) on server-side data like this:
* <pre>
var User = $resource('/user/:userId', {userId:'@id'});
var user = User.get({userId:123}, function() {
user.abc = true;
user.$save();
});
</pre>
*
* It is important to realize that invoking a $resource object method immediately returns an
* empty reference (object or array depending on `isArray`). Once the data is returned from the
* server the existing reference is populated with the actual data. This is a useful trick since
* usually the resource is assigned to a model which is then rendered by the view. Having an empty
* object results in no rendering, once the data arrives from the server then the object is
* populated with the data and the view automatically re-renders itself showing the new data. This
* means that in most case one never has to write a callback function for the action methods.
*
* The action methods on the class object or instance object can be invoked with the following
* parameters:
*
* - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])`
* - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])`
* - non-GET instance actions: `instance.$action([parameters], [success], [error])`
*
*
* The Resource instances and collection have these additional properties:
*
* - `$then`: the `then` method of a {@link ng.$q promise} derived from the underlying
* {@link ng.$http $http} call.
*
* The success callback for the `$then` method will be resolved if the underlying `$http` requests
* succeeds.
*
* The success callback is called with a single object which is the {@link ng.$http http response}
* object extended with a new property `resource`. This `resource` property is a reference to the
* result of the resource action — resource object or array of resources.
*
* The error callback is called with the {@link ng.$http http response} object when an http
* error occurs.
*
* - `$resolved`: true if the promise has been resolved (either with success or rejection);
* Knowing if the Resource has been resolved is useful in data-binding.
*
* @example
*
* # Credit card resource
*
* <pre>
// Define CreditCard class
var CreditCard = $resource('/user/:userId/card/:cardId',
{userId:123, cardId:'@id'}, {
charge: {method:'POST', params:{charge:true}}
});
// We can retrieve a collection from the server
var cards = CreditCard.query(function() {
// GET: /user/123/card
// server returns: [ {id:456, number:'1234', name:'Smith'} ];
var card = cards[0];
// each item is an instance of CreditCard
expect(card instanceof CreditCard).toEqual(true);
card.name = "J. Smith";
// non GET methods are mapped onto the instances
card.$save();
// POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
// server returns: {id:456, number:'1234', name: 'J. Smith'};
// our custom method is mapped as well.
card.$charge({amount:9.99});
// POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
});
// we can create an instance as well
var newCard = new CreditCard({number:'0123'});
newCard.name = "Mike Smith";
newCard.$save();
// POST: /user/123/card {number:'0123', name:'Mike Smith'}
// server returns: {id:789, number:'01234', name: 'Mike Smith'};
expect(newCard.id).toEqual(789);
* </pre>
*
* The object returned from this function execution is a resource "class" which has "static" method
* for each action in the definition.
*
* Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and `headers`.
* When the data is returned from the server then the object is an instance of the resource type and
* all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD
* operations (create, read, update, delete) on server-side data.
<pre>
var User = $resource('/user/:userId', {userId:'@id'});
var user = User.get({userId:123}, function() {
user.abc = true;
user.$save();
});
</pre>
*
* It's worth noting that the success callback for `get`, `query` and other method gets passed
* in the response that came from the server as well as $http header getter function, so one
* could rewrite the above example and get access to http headers as:
*
<pre>
var User = $resource('/user/:userId', {userId:'@id'});
User.get({userId:123}, function(u, getResponseHeaders){
u.abc = true;
u.$save(function(u, putResponseHeaders) {
//u => saved user object
//putResponseHeaders => $http header getter
});
});
</pre>
* # Buzz client
Let's look at what a buzz client created with the `$resource` service looks like:
<doc:example>
<doc:source jsfiddle="false">
<script>
function BuzzController($resource) {
this.userId = 'googlebuzz';
this.Activity = $resource(
'https://www.googleapis.com/buzz/v1/activities/:userId/:visibility/:activityId/:comments',
{alt:'json', callback:'JSON_CALLBACK'},
{get:{method:'JSONP', params:{visibility:'@self'}}, replies: {method:'JSONP', params:{visibility:'@self', comments:'@comments'}}}
);
}
BuzzController.prototype = {
fetch: function() {
this.activities = this.Activity.get({userId:this.userId});
},
expandReplies: function(activity) {
activity.replies = this.Activity.replies({userId:this.userId, activityId:activity.id});
}
};
BuzzController.$inject = ['$resource'];
</script>
<div ng-controller="BuzzController">
<input ng-model="userId"/>
<button ng-click="fetch()">fetch</button>
<hr/>
<div ng-repeat="item in activities.data.items">
<h1 style="font-size: 15px;">
<img src="{{item.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
<a href="{{item.actor.profileUrl}}">{{item.actor.name}}</a>
<a href ng-click="expandReplies(item)" style="float: right;">Expand replies: {{item.links.replies[0].count}}</a>
</h1>
{{item.object.content | html}}
<div ng-repeat="reply in item.replies.data.items" style="margin-left: 20px;">
<img src="{{reply.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
<a href="{{reply.actor.profileUrl}}">{{reply.actor.name}}</a>: {{reply.content | html}}
</div>
</div>
</div>
</doc:source>
<doc:scenario>
</doc:scenario>
</doc:example>
*/
angular.module('ngResource', ['ng']).
factory('$resource', ['$http', '$parse', function($http, $parse) {
var DEFAULT_ACTIONS = {
'get': {method:'GET'},
'save': {method:'POST'},
'query': {method:'GET', isArray:true},
'remove': {method:'DELETE'},
'delete': {method:'DELETE'}
};
var noop = angular.noop,
forEach = angular.forEach,
extend = angular.extend,
copy = angular.copy,
isFunction = angular.isFunction,
getter = function(obj, path) {
return $parse(path)(obj);
};
/**
* We need our custom method because encodeURIComponent is too aggressive and doesn't follow
* http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
* segments:
* segment = *pchar
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
* pct-encoded = "%" HEXDIG HEXDIG
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
* / "*" / "+" / "," / ";" / "="
*/
function encodeUriSegment(val) {
return encodeUriQuery(val, true).
replace(/%26/gi, '&').
replace(/%3D/gi, '=').
replace(/%2B/gi, '+');
}
/**
* This method is intended for encoding *key* or *value* parts of query component. We need a custom
* method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be
* encoded per http://tools.ietf.org/html/rfc3986:
* query = *( pchar / "/" / "?" )
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
* pct-encoded = "%" HEXDIG HEXDIG
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
* / "*" / "+" / "," / ";" / "="
*/
function encodeUriQuery(val, pctEncodeSpaces) {
return encodeURIComponent(val).
replace(/%40/gi, '@').
replace(/%3A/gi, ':').
replace(/%24/g, '$').
replace(/%2C/gi, ',').
replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
}
function Route(template, defaults) {
this.template = template;
this.defaults = defaults || {};
this.urlParams = {};
}
Route.prototype = {
setUrlParams: function(config, params, actionUrl) {
var self = this,
url = actionUrl || self.template,
val,
encodedVal;
var urlParams = self.urlParams = {};
forEach(url.split(/\W/), function(param){
if (param && (new RegExp("(^|[^\\\\]):" + param + "(\\W|$)").test(url))) {
urlParams[param] = true;
}
});
url = url.replace(/\\:/g, ':');
params = params || {};
forEach(self.urlParams, function(_, urlParam){
val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam];
if (angular.isDefined(val) && val !== null) {
encodedVal = encodeUriSegment(val);
url = url.replace(new RegExp(":" + urlParam + "(\\W|$)", "g"), encodedVal + "$1");
} else {
url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W|$)", "g"), function(match,
leadingSlashes, tail) {
if (tail.charAt(0) == '/') {
return tail;
} else {
return leadingSlashes + tail;
}
});
}
});
// strip trailing slashes and set the url
url = url.replace(/\/+$/, '');
// then replace collapse `/.` if found in the last URL path segment before the query
// E.g. `http://url.com/id./format?q=x` becomes `http://url.com/id.format?q=x`
url = url.replace(/\/\.(?=\w+($|\?))/, '.');
// replace escaped `/\.` with `/.`
config.url = url.replace(/\/\\\./, '/.');
// set params - delegate param encoding to $http
forEach(params, function(value, key){
if (!self.urlParams[key]) {
config.params = config.params || {};
config.params[key] = value;
}
});
}
};
function ResourceFactory(url, paramDefaults, actions) {
var route = new Route(url);
actions = extend({}, DEFAULT_ACTIONS, actions);
function extractParams(data, actionParams){
var ids = {};
actionParams = extend({}, paramDefaults, actionParams);
forEach(actionParams, function(value, key){
if (isFunction(value)) { value = value(); }
ids[key] = value && value.charAt && value.charAt(0) == '@' ? getter(data, value.substr(1)) : value;
});
return ids;
}
function Resource(value){
copy(value || {}, this);
}
forEach(actions, function(action, name) {
action.method = angular.uppercase(action.method);
var hasBody = action.method == 'POST' || action.method == 'PUT' || action.method == 'PATCH';
Resource[name] = function(a1, a2, a3, a4) {
var params = {};
var data;
var success = noop;
var error = null;
var promise;
switch(arguments.length) {
case 4:
error = a4;
success = a3;
//fallthrough
case 3:
case 2:
if (isFunction(a2)) {
if (isFunction(a1)) {
success = a1;
error = a2;
break;
}
success = a2;
error = a3;
//fallthrough
} else {
params = a1;
data = a2;
success = a3;
break;
}
case 1:
if (isFunction(a1)) success = a1;
else if (hasBody) data = a1;
else params = a1;
break;
case 0: break;
default:
throw "Expected between 0-4 arguments [params, data, success, error], got " +
arguments.length + " arguments.";
}
var value = this instanceof Resource ? this : (action.isArray ? [] : new Resource(data));
var httpConfig = {},
promise;
forEach(action, function(value, key) {
if (key != 'params' && key != 'isArray' ) {
httpConfig[key] = copy(value);
}
});
httpConfig.data = data;
route.setUrlParams(httpConfig, extend({}, extractParams(data, action.params || {}), params), action.url);
function markResolved() { value.$resolved = true; }
promise = $http(httpConfig);
value.$resolved = false;
promise.then(markResolved, markResolved);
value.$then = promise.then(function(response) {
var data = response.data;
var then = value.$then, resolved = value.$resolved;
if (data) {
if (action.isArray) {
value.length = 0;
forEach(data, function(item) {
value.push(new Resource(item));
});
} else {
copy(data, value);
value.$then = then;
value.$resolved = resolved;
}
}
(success||noop)(value, response.headers);
response.resource = value;
return response;
}, error).then;
return value;
};
Resource.prototype['$' + name] = function(a1, a2, a3) {
var params = extractParams(this),
success = noop,
error;
switch(arguments.length) {
case 3: params = a1; success = a2; error = a3; break;
case 2:
case 1:
if (isFunction(a1)) {
success = a1;
error = a2;
} else {
params = a1;
success = a2 || noop;
}
case 0: break;
default:
throw "Expected between 1-3 arguments [params, success, error], got " +
arguments.length + " arguments.";
}
var data = hasBody ? this : undefined;
Resource[name].call(this, params, data, success, error);
};
});
Resource.bind = function(additionalParamDefaults){
return ResourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions);
};
return Resource;
}
return ResourceFactory;
}]);
})(window, window.angular);

View File

@@ -0,0 +1,11 @@
/*
AngularJS v1.1.5
(c) 2010-2012 Google, Inc. http://angularjs.org
License: MIT
*/
(function(B,f,w){'use strict';f.module("ngResource",["ng"]).factory("$resource",["$http","$parse",function(x,y){function u(b,d){this.template=b;this.defaults=d||{};this.urlParams={}}function v(b,d,e){function j(c,b){var p={},b=m({},d,b);l(b,function(a,b){k(a)&&(a=a());var g;a&&a.charAt&&a.charAt(0)=="@"?(g=a.substr(1),g=y(g)(c)):g=a;p[b]=g});return p}function c(c){t(c||{},this)}var n=new u(b),e=m({},z,e);l(e,function(b,d){b.method=f.uppercase(b.method);var p=b.method=="POST"||b.method=="PUT"||b.method==
"PATCH";c[d]=function(a,d,g,A){function f(){h.$resolved=!0}var i={},e,o=q,r=null;switch(arguments.length){case 4:r=A,o=g;case 3:case 2:if(k(d)){if(k(a)){o=a;r=d;break}o=d;r=g}else{i=a;e=d;o=g;break}case 1:k(a)?o=a:p?e=a:i=a;break;case 0:break;default:throw"Expected between 0-4 arguments [params, data, success, error], got "+arguments.length+" arguments.";}var h=this instanceof c?this:b.isArray?[]:new c(e),s={};l(b,function(a,b){b!="params"&&b!="isArray"&&(s[b]=t(a))});s.data=e;n.setUrlParams(s,m({},
j(e,b.params||{}),i),b.url);i=x(s);h.$resolved=!1;i.then(f,f);h.$then=i.then(function(a){var d=a.data,g=h.$then,e=h.$resolved;if(d)b.isArray?(h.length=0,l(d,function(a){h.push(new c(a))})):(t(d,h),h.$then=g,h.$resolved=e);(o||q)(h,a.headers);a.resource=h;return a},r).then;return h};c.prototype["$"+d]=function(a,b,g){var e=j(this),f=q,i;switch(arguments.length){case 3:e=a;f=b;i=g;break;case 2:case 1:k(a)?(f=a,i=b):(e=a,f=b||q);case 0:break;default:throw"Expected between 1-3 arguments [params, success, error], got "+
arguments.length+" arguments.";}c[d].call(this,e,p?this:w,f,i)}});c.bind=function(c){return v(b,m({},d,c),e)};return c}var z={get:{method:"GET"},save:{method:"POST"},query:{method:"GET",isArray:!0},remove:{method:"DELETE"},"delete":{method:"DELETE"}},q=f.noop,l=f.forEach,m=f.extend,t=f.copy,k=f.isFunction;u.prototype={setUrlParams:function(b,d,e){var j=this,c=e||j.template,n,k,m=j.urlParams={};l(c.split(/\W/),function(b){b&&RegExp("(^|[^\\\\]):"+b+"(\\W|$)").test(c)&&(m[b]=!0)});c=c.replace(/\\:/g,
":");d=d||{};l(j.urlParams,function(b,a){n=d.hasOwnProperty(a)?d[a]:j.defaults[a];f.isDefined(n)&&n!==null?(k=encodeURIComponent(n).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"%20").replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+"),c=c.replace(RegExp(":"+a+"(\\W|$)","g"),k+"$1")):c=c.replace(RegExp("(/?):"+a+"(\\W|$)","g"),function(b,a,c){return c.charAt(0)=="/"?c:a+c})});c=c.replace(/\/+$/,"");c=c.replace(/\/\.(?=\w+($|\?))/,".");
b.url=c.replace(/\/\\\./,"/.");l(d,function(c,a){if(!j.urlParams[a])b.params=b.params||{},b.params[a]=c})}};return v}])})(window,window.angular);

View File

@@ -0,0 +1,558 @@
/**
* @license AngularJS v1.1.5
* (c) 2010-2012 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, angular, undefined) {
'use strict';
/**
* @ngdoc overview
* @name ngSanitize
* @description
*/
/*
* HTML Parser By Misko Hevery (misko@hevery.com)
* based on: HTML Parser By John Resig (ejohn.org)
* Original code by Erik Arvidsson, Mozilla Public License
* http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
*
* // Use like so:
* htmlParser(htmlString, {
* start: function(tag, attrs, unary) {},
* end: function(tag) {},
* chars: function(text) {},
* comment: function(text) {}
* });
*
*/
/**
* @ngdoc service
* @name ngSanitize.$sanitize
* @function
*
* @description
* The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are
* then serialized back to properly escaped html string. This means that no unsafe input can make
* it into the returned string, however, since our parser is more strict than a typical browser
* parser, it's possible that some obscure input, which would be recognized as valid HTML by a
* browser, won't make it through the sanitizer.
*
* @param {string} html Html input.
* @returns {string} Sanitized html.
*
* @example
<doc:example module="ngSanitize">
<doc:source>
<script>
function Ctrl($scope) {
$scope.snippet =
'<p style="color:blue">an html\n' +
'<em onmouseover="this.textContent=\'PWN3D!\'">click here</em>\n' +
'snippet</p>';
}
</script>
<div ng-controller="Ctrl">
Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
<table>
<tr>
<td>Filter</td>
<td>Source</td>
<td>Rendered</td>
</tr>
<tr id="html-filter">
<td>html filter</td>
<td>
<pre>&lt;div ng-bind-html="snippet"&gt;<br/>&lt;/div&gt;</pre>
</td>
<td>
<div ng-bind-html="snippet"></div>
</td>
</tr>
<tr id="escaped-html">
<td>no filter</td>
<td><pre>&lt;div ng-bind="snippet"&gt;<br/>&lt;/div&gt;</pre></td>
<td><div ng-bind="snippet"></div></td>
</tr>
<tr id="html-unsafe-filter">
<td>unsafe html filter</td>
<td><pre>&lt;div ng-bind-html-unsafe="snippet"&gt;<br/>&lt;/div&gt;</pre></td>
<td><div ng-bind-html-unsafe="snippet"></div></td>
</tr>
</table>
</div>
</doc:source>
<doc:scenario>
it('should sanitize the html snippet ', function() {
expect(using('#html-filter').element('div').html()).
toBe('<p>an html\n<em>click here</em>\nsnippet</p>');
});
it('should escape snippet without any filter', function() {
expect(using('#escaped-html').element('div').html()).
toBe("&lt;p style=\"color:blue\"&gt;an html\n" +
"&lt;em onmouseover=\"this.textContent='PWN3D!'\"&gt;click here&lt;/em&gt;\n" +
"snippet&lt;/p&gt;");
});
it('should inline raw snippet if filtered as unsafe', function() {
expect(using('#html-unsafe-filter').element("div").html()).
toBe("<p style=\"color:blue\">an html\n" +
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
"snippet</p>");
});
it('should update', function() {
input('snippet').enter('new <b>text</b>');
expect(using('#html-filter').binding('snippet')).toBe('new <b>text</b>');
expect(using('#escaped-html').element('div').html()).toBe("new &lt;b&gt;text&lt;/b&gt;");
expect(using('#html-unsafe-filter').binding("snippet")).toBe('new <b>text</b>');
});
</doc:scenario>
</doc:example>
*/
var $sanitize = function(html) {
var buf = [];
htmlParser(html, htmlSanitizeWriter(buf));
return buf.join('');
};
// Regular Expressions for parsing tags and attributes
var START_TAG_REGEXP = /^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,
END_TAG_REGEXP = /^<\s*\/\s*([\w:-]+)[^>]*>/,
ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,
BEGIN_TAG_REGEXP = /^</,
BEGING_END_TAGE_REGEXP = /^<\s*\//,
COMMENT_REGEXP = /<!--(.*?)-->/g,
CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g,
URI_REGEXP = /^((ftp|https?):\/\/|mailto:|tel:|#)/,
NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g; // Match everything outside of normal chars and " (quote character)
// Good source of info about elements and attributes
// http://dev.w3.org/html5/spec/Overview.html#semantics
// http://simon.html5.org/html-elements
// Safe Void Elements - HTML5
// http://dev.w3.org/html5/spec/Overview.html#void-elements
var voidElements = makeMap("area,br,col,hr,img,wbr");
// Elements that you can, intentionally, leave open (and which close themselves)
// http://dev.w3.org/html5/spec/Overview.html#optional-tags
var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),
optionalEndTagInlineElements = makeMap("rp,rt"),
optionalEndTagElements = angular.extend({}, optionalEndTagInlineElements, optionalEndTagBlockElements);
// Safe Block Elements - HTML5
var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article,aside," +
"blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6," +
"header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul"));
// Inline Elements - HTML5
var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b,bdi,bdo," +
"big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small," +
"span,strike,strong,sub,sup,time,tt,u,var"));
// Special Elements (can contain anything)
var specialElements = makeMap("script,style");
var validElements = angular.extend({}, voidElements, blockElements, inlineElements, optionalEndTagElements);
//Attributes that have href and hence need to be sanitized
var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap");
var validAttrs = angular.extend({}, uriAttrs, makeMap(
'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'+
'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,'+
'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,'+
'scope,scrolling,shape,span,start,summary,target,title,type,'+
'valign,value,vspace,width'));
function makeMap(str) {
var obj = {}, items = str.split(','), i;
for (i = 0; i < items.length; i++) obj[items[i]] = true;
return obj;
}
/**
* @example
* htmlParser(htmlString, {
* start: function(tag, attrs, unary) {},
* end: function(tag) {},
* chars: function(text) {},
* comment: function(text) {}
* });
*
* @param {string} html string
* @param {object} handler
*/
function htmlParser( html, handler ) {
var index, chars, match, stack = [], last = html;
stack.last = function() { return stack[ stack.length - 1 ]; };
while ( html ) {
chars = true;
// Make sure we're not in a script or style element
if ( !stack.last() || !specialElements[ stack.last() ] ) {
// Comment
if ( html.indexOf("<!--") === 0 ) {
index = html.indexOf("-->");
if ( index >= 0 ) {
if (handler.comment) handler.comment( html.substring( 4, index ) );
html = html.substring( index + 3 );
chars = false;
}
// end tag
} else if ( BEGING_END_TAGE_REGEXP.test(html) ) {
match = html.match( END_TAG_REGEXP );
if ( match ) {
html = html.substring( match[0].length );
match[0].replace( END_TAG_REGEXP, parseEndTag );
chars = false;
}
// start tag
} else if ( BEGIN_TAG_REGEXP.test(html) ) {
match = html.match( START_TAG_REGEXP );
if ( match ) {
html = html.substring( match[0].length );
match[0].replace( START_TAG_REGEXP, parseStartTag );
chars = false;
}
}
if ( chars ) {
index = html.indexOf("<");
var text = index < 0 ? html : html.substring( 0, index );
html = index < 0 ? "" : html.substring( index );
if (handler.chars) handler.chars( decodeEntities(text) );
}
} else {
html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'), function(all, text){
text = text.
replace(COMMENT_REGEXP, "$1").
replace(CDATA_REGEXP, "$1");
if (handler.chars) handler.chars( decodeEntities(text) );
return "";
});
parseEndTag( "", stack.last() );
}
if ( html == last ) {
throw "Parse Error: " + html;
}
last = html;
}
// Clean up any remaining tags
parseEndTag();
function parseStartTag( tag, tagName, rest, unary ) {
tagName = angular.lowercase(tagName);
if ( blockElements[ tagName ] ) {
while ( stack.last() && inlineElements[ stack.last() ] ) {
parseEndTag( "", stack.last() );
}
}
if ( optionalEndTagElements[ tagName ] && stack.last() == tagName ) {
parseEndTag( "", tagName );
}
unary = voidElements[ tagName ] || !!unary;
if ( !unary )
stack.push( tagName );
var attrs = {};
rest.replace(ATTR_REGEXP, function(match, name, doubleQuotedValue, singleQoutedValue, unqoutedValue) {
var value = doubleQuotedValue
|| singleQoutedValue
|| unqoutedValue
|| '';
attrs[name] = decodeEntities(value);
});
if (handler.start) handler.start( tagName, attrs, unary );
}
function parseEndTag( tag, tagName ) {
var pos = 0, i;
tagName = angular.lowercase(tagName);
if ( tagName )
// Find the closest opened tag of the same type
for ( pos = stack.length - 1; pos >= 0; pos-- )
if ( stack[ pos ] == tagName )
break;
if ( pos >= 0 ) {
// Close all the open elements, up the stack
for ( i = stack.length - 1; i >= pos; i-- )
if (handler.end) handler.end( stack[ i ] );
// Remove the open elements from the stack
stack.length = pos;
}
}
}
/**
* decodes all entities into regular string
* @param value
* @returns {string} A string with decoded entities.
*/
var hiddenPre=document.createElement("pre");
function decodeEntities(value) {
hiddenPre.innerHTML=value.replace(/</g,"&lt;");
return hiddenPre.innerText || hiddenPre.textContent || '';
}
/**
* Escapes all potentially dangerous characters, so that the
* resulting string can be safely inserted into attribute or
* element text.
* @param value
* @returns escaped text
*/
function encodeEntities(value) {
return value.
replace(/&/g, '&amp;').
replace(NON_ALPHANUMERIC_REGEXP, function(value){
return '&#' + value.charCodeAt(0) + ';';
}).
replace(/</g, '&lt;').
replace(/>/g, '&gt;');
}
/**
* create an HTML/XML writer which writes to buffer
* @param {Array} buf use buf.jain('') to get out sanitized html string
* @returns {object} in the form of {
* start: function(tag, attrs, unary) {},
* end: function(tag) {},
* chars: function(text) {},
* comment: function(text) {}
* }
*/
function htmlSanitizeWriter(buf){
var ignore = false;
var out = angular.bind(buf, buf.push);
return {
start: function(tag, attrs, unary){
tag = angular.lowercase(tag);
if (!ignore && specialElements[tag]) {
ignore = tag;
}
if (!ignore && validElements[tag] == true) {
out('<');
out(tag);
angular.forEach(attrs, function(value, key){
var lkey=angular.lowercase(key);
if (validAttrs[lkey]==true && (uriAttrs[lkey]!==true || value.match(URI_REGEXP))) {
out(' ');
out(key);
out('="');
out(encodeEntities(value));
out('"');
}
});
out(unary ? '/>' : '>');
}
},
end: function(tag){
tag = angular.lowercase(tag);
if (!ignore && validElements[tag] == true) {
out('</');
out(tag);
out('>');
}
if (tag == ignore) {
ignore = false;
}
},
chars: function(chars){
if (!ignore) {
out(encodeEntities(chars));
}
}
};
}
// define ngSanitize module and register $sanitize service
angular.module('ngSanitize', []).value('$sanitize', $sanitize);
/**
* @ngdoc directive
* @name ngSanitize.directive:ngBindHtml
*
* @description
* Creates a binding that will sanitize the result of evaluating the `expression` with the
* {@link ngSanitize.$sanitize $sanitize} service and innerHTML the result into the current element.
*
* See {@link ngSanitize.$sanitize $sanitize} docs for examples.
*
* @element ANY
* @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate.
*/
angular.module('ngSanitize').directive('ngBindHtml', ['$sanitize', function($sanitize) {
return function(scope, element, attr) {
element.addClass('ng-binding').data('$binding', attr.ngBindHtml);
scope.$watch(attr.ngBindHtml, function ngBindHtmlWatchAction(value) {
value = $sanitize(value);
element.html(value || '');
});
};
}]);
/**
* @ngdoc filter
* @name ngSanitize.filter:linky
* @function
*
* @description
* Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and
* plain email address links.
*
* @param {string} text Input text.
* @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in.
* @returns {string} Html-linkified text.
*
* @usage
<span ng-bind-html="linky_expression | linky"></span>
*
* @example
<doc:example module="ngSanitize">
<doc:source>
<script>
function Ctrl($scope) {
$scope.snippet =
'Pretty text with some links:\n'+
'http://angularjs.org/,\n'+
'mailto:us@somewhere.org,\n'+
'another@somewhere.org,\n'+
'and one more: ftp://127.0.0.1/.';
$scope.snippetWithTarget = 'http://angularjs.org/';
}
</script>
<div ng-controller="Ctrl">
Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
<table>
<tr>
<td>Filter</td>
<td>Source</td>
<td>Rendered</td>
</tr>
<tr id="linky-filter">
<td>linky filter</td>
<td>
<pre>&lt;div ng-bind-html="snippet | linky"&gt;<br>&lt;/div&gt;</pre>
</td>
<td>
<div ng-bind-html="snippet | linky"></div>
</td>
</tr>
<tr id="linky-target">
<td>linky target</td>
<td>
<pre>&lt;div ng-bind-html="snippetWithTarget | linky:'_blank'"&gt;<br>&lt;/div&gt;</pre>
</td>
<td>
<div ng-bind-html="snippetWithTarget | linky:'_blank'"></div>
</td>
</tr>
<tr id="escaped-html">
<td>no filter</td>
<td><pre>&lt;div ng-bind="snippet"&gt;<br>&lt;/div&gt;</pre></td>
<td><div ng-bind="snippet"></div></td>
</tr>
</table>
</doc:source>
<doc:scenario>
it('should linkify the snippet with urls', function() {
expect(using('#linky-filter').binding('snippet | linky')).
toBe('Pretty text with some links:&#10;' +
'<a href="http://angularjs.org/">http://angularjs.org/</a>,&#10;' +
'<a href="mailto:us@somewhere.org">us@somewhere.org</a>,&#10;' +
'<a href="mailto:another@somewhere.org">another@somewhere.org</a>,&#10;' +
'and one more: <a href="ftp://127.0.0.1/">ftp://127.0.0.1/</a>.');
});
it ('should not linkify snippet without the linky filter', function() {
expect(using('#escaped-html').binding('snippet')).
toBe("Pretty text with some links:\n" +
"http://angularjs.org/,\n" +
"mailto:us@somewhere.org,\n" +
"another@somewhere.org,\n" +
"and one more: ftp://127.0.0.1/.");
});
it('should update', function() {
input('snippet').enter('new http://link.');
expect(using('#linky-filter').binding('snippet | linky')).
toBe('new <a href="http://link">http://link</a>.');
expect(using('#escaped-html').binding('snippet')).toBe('new http://link.');
});
it('should work with the target property', function() {
expect(using('#linky-target').binding("snippetWithTarget | linky:'_blank'")).
toBe('<a target="_blank" href="http://angularjs.org/">http://angularjs.org/</a>');
});
</doc:scenario>
</doc:example>
*/
angular.module('ngSanitize').filter('linky', function() {
var LINKY_URL_REGEXP = /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/,
MAILTO_REGEXP = /^mailto:/;
return function(text, target) {
if (!text) return text;
var match;
var raw = text;
var html = [];
// TODO(vojta): use $sanitize instead
var writer = htmlSanitizeWriter(html);
var url;
var i;
var properties = {};
if (angular.isDefined(target)) {
properties.target = target;
}
while ((match = raw.match(LINKY_URL_REGEXP))) {
// We can not end in these as they are sometimes found at the end of the sentence
url = match[0];
// if we did not match ftp/http/mailto then assume mailto
if (match[2] == match[3]) url = 'mailto:' + url;
i = match.index;
writer.chars(raw.substr(0, i));
properties.href = url;
writer.start('a', properties);
writer.chars(match[0].replace(MAILTO_REGEXP, ''));
writer.end('a');
raw = raw.substring(i + match[0].length);
}
writer.chars(raw);
return html.join('');
};
});
})(window, window.angular);

View File

@@ -0,0 +1,13 @@
/*
AngularJS v1.1.5
(c) 2010-2012 Google, Inc. http://angularjs.org
License: MIT
*/
(function(I,h){'use strict';function i(a){var d={},a=a.split(","),c;for(c=0;c<a.length;c++)d[a[c]]=!0;return d}function z(a,d){function c(a,b,c,f){b=h.lowercase(b);if(m[b])for(;e.last()&&n[e.last()];)g("",e.last());o[b]&&e.last()==b&&g("",b);(f=p[b]||!!f)||e.push(b);var j={};c.replace(A,function(a,b,d,c,g){j[b]=k(d||c||g||"")});d.start&&d.start(b,j,f)}function g(a,b){var c=0,g;if(b=h.lowercase(b))for(c=e.length-1;c>=0;c--)if(e[c]==b)break;if(c>=0){for(g=e.length-1;g>=c;g--)d.end&&d.end(e[g]);e.length=
c}}var b,f,e=[],j=a;for(e.last=function(){return e[e.length-1]};a;){f=!0;if(!e.last()||!q[e.last()]){if(a.indexOf("<\!--")===0)b=a.indexOf("--\>"),b>=0&&(d.comment&&d.comment(a.substring(4,b)),a=a.substring(b+3),f=!1);else if(B.test(a)){if(b=a.match(r))a=a.substring(b[0].length),b[0].replace(r,g),f=!1}else if(C.test(a)&&(b=a.match(s)))a=a.substring(b[0].length),b[0].replace(s,c),f=!1;f&&(b=a.indexOf("<"),f=b<0?a:a.substring(0,b),a=b<0?"":a.substring(b),d.chars&&d.chars(k(f)))}else a=a.replace(RegExp("(.*)<\\s*\\/\\s*"+
e.last()+"[^>]*>","i"),function(a,b){b=b.replace(D,"$1").replace(E,"$1");d.chars&&d.chars(k(b));return""}),g("",e.last());if(a==j)throw"Parse Error: "+a;j=a}g()}function k(a){l.innerHTML=a.replace(/</g,"&lt;");return l.innerText||l.textContent||""}function t(a){return a.replace(/&/g,"&amp;").replace(F,function(a){return"&#"+a.charCodeAt(0)+";"}).replace(/</g,"&lt;").replace(/>/g,"&gt;")}function u(a){var d=!1,c=h.bind(a,a.push);return{start:function(a,b,f){a=h.lowercase(a);!d&&q[a]&&(d=a);!d&&v[a]==
!0&&(c("<"),c(a),h.forEach(b,function(a,b){var d=h.lowercase(b);if(G[d]==!0&&(w[d]!==!0||a.match(H)))c(" "),c(b),c('="'),c(t(a)),c('"')}),c(f?"/>":">"))},end:function(a){a=h.lowercase(a);!d&&v[a]==!0&&(c("</"),c(a),c(">"));a==d&&(d=!1)},chars:function(a){d||c(t(a))}}}var s=/^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,r=/^<\s*\/\s*([\w:-]+)[^>]*>/,A=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,C=/^</,B=/^<\s*\//,D=/<\!--(.*?)--\>/g,
E=/<!\[CDATA\[(.*?)]]\>/g,H=/^((ftp|https?):\/\/|mailto:|tel:|#)/,F=/([^\#-~| |!])/g,p=i("area,br,col,hr,img,wbr"),x=i("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),y=i("rp,rt"),o=h.extend({},y,x),m=h.extend({},x,i("address,article,aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")),n=h.extend({},y,i("a,abbr,acronym,b,bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small,span,strike,strong,sub,sup,time,tt,u,var")),
q=i("script,style"),v=h.extend({},p,m,n,o),w=i("background,cite,href,longdesc,src,usemap"),G=h.extend({},w,i("abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,scope,scrolling,shape,span,start,summary,target,title,type,valign,value,vspace,width")),l=document.createElement("pre");h.module("ngSanitize",[]).value("$sanitize",function(a){var d=[];
z(a,u(d));return d.join("")});h.module("ngSanitize").directive("ngBindHtml",["$sanitize",function(a){return function(d,c,g){c.addClass("ng-binding").data("$binding",g.ngBindHtml);d.$watch(g.ngBindHtml,function(b){b=a(b);c.html(b||"")})}}]);h.module("ngSanitize").filter("linky",function(){var a=/((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/,d=/^mailto:/;return function(c,g){if(!c)return c;var b,f=c,e=[],j=u(e),i,k,l={};if(h.isDefined(g))l.target=g;for(;b=f.match(a);)i=
b[0],b[2]==b[3]&&(i="mailto:"+i),k=b.index,j.chars(f.substr(0,k)),l.href=i,j.start("a",l),j.chars(b[0].replace(d,"")),j.end("a"),f=f.substring(k+b[0].length);j.chars(f);return e.join("")}})})(window,window.angular);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,178 @@
/*
AngularJS v1.1.5
(c) 2010-2012 Google, Inc. http://angularjs.org
License: MIT
*/
(function(M,T,p){'use strict';function lc(){var b=M.angular;M.angular=mc;return b}function Xa(b){return!b||typeof b.length!=="number"?!1:typeof b.hasOwnProperty!="function"&&typeof b.constructor!="function"?!0:b instanceof R||ga&&b instanceof ga||Ea.call(b)!=="[object Object]"||typeof b.callee==="function"}function n(b,a,c){var d;if(b)if(H(b))for(d in b)d!="prototype"&&d!="length"&&d!="name"&&b.hasOwnProperty(d)&&a.call(c,b[d],d);else if(b.forEach&&b.forEach!==n)b.forEach(a,c);else if(Xa(b))for(d=
0;d<b.length;d++)a.call(c,b[d],d);else for(d in b)b.hasOwnProperty(d)&&a.call(c,b[d],d);return b}function qb(b){var a=[],c;for(c in b)b.hasOwnProperty(c)&&a.push(c);return a.sort()}function nc(b,a,c){for(var d=qb(b),e=0;e<d.length;e++)a.call(c,b[d[e]],d[e]);return d}function rb(b){return function(a,c){b(c,a)}}function Fa(){for(var b=ba.length,a;b;){b--;a=ba[b].charCodeAt(0);if(a==57)return ba[b]="A",ba.join("");if(a==90)ba[b]="0";else return ba[b]=String.fromCharCode(a+1),ba.join("")}ba.unshift("0");
return ba.join("")}function sb(b,a){a?b.$$hashKey=a:delete b.$$hashKey}function t(b){var a=b.$$hashKey;n(arguments,function(a){a!==b&&n(a,function(a,c){b[c]=a})});sb(b,a);return b}function N(b){return parseInt(b,10)}function tb(b,a){return t(new (t(function(){},{prototype:b})),a)}function q(){}function qa(b){return b}function S(b){return function(){return b}}function C(b){return typeof b=="undefined"}function B(b){return typeof b!="undefined"}function L(b){return b!=null&&typeof b=="object"}function E(b){return typeof b==
"string"}function Ya(b){return typeof b=="number"}function ra(b){return Ea.apply(b)=="[object Date]"}function F(b){return Ea.apply(b)=="[object Array]"}function H(b){return typeof b=="function"}function sa(b){return b&&b.document&&b.location&&b.alert&&b.setInterval}function U(b){return E(b)?b.replace(/^\s*/,"").replace(/\s*$/,""):b}function oc(b){return b&&(b.nodeName||b.bind&&b.find)}function Za(b,a,c){var d=[];n(b,function(b,g,i){d.push(a.call(c,b,g,i))});return d}function Ga(b,a){if(b.indexOf)return b.indexOf(a);
for(var c=0;c<b.length;c++)if(a===b[c])return c;return-1}function ta(b,a){var c=Ga(b,a);c>=0&&b.splice(c,1);return a}function V(b,a){if(sa(b)||b&&b.$evalAsync&&b.$watch)throw Error("Can't copy Window or Scope");if(a){if(b===a)throw Error("Can't copy equivalent objects or arrays");if(F(b))for(var c=a.length=0;c<b.length;c++)a.push(V(b[c]));else{c=a.$$hashKey;n(a,function(b,c){delete a[c]});for(var d in b)a[d]=V(b[d]);sb(a,c)}}else(a=b)&&(F(b)?a=V(b,[]):ra(b)?a=new Date(b.getTime()):L(b)&&(a=V(b,{})));
return a}function pc(b,a){var a=a||{},c;for(c in b)b.hasOwnProperty(c)&&c.substr(0,2)!=="$$"&&(a[c]=b[c]);return a}function ia(b,a){if(b===a)return!0;if(b===null||a===null)return!1;if(b!==b&&a!==a)return!0;var c=typeof b,d;if(c==typeof a&&c=="object")if(F(b)){if((c=b.length)==a.length){for(d=0;d<c;d++)if(!ia(b[d],a[d]))return!1;return!0}}else if(ra(b))return ra(a)&&b.getTime()==a.getTime();else{if(b&&b.$evalAsync&&b.$watch||a&&a.$evalAsync&&a.$watch||sa(b)||sa(a))return!1;c={};for(d in b)if(!(d.charAt(0)===
"$"||H(b[d]))){if(!ia(b[d],a[d]))return!1;c[d]=!0}for(d in a)if(!c[d]&&d.charAt(0)!=="$"&&a[d]!==p&&!H(a[d]))return!1;return!0}return!1}function $a(b,a){var c=arguments.length>2?ka.call(arguments,2):[];return H(a)&&!(a instanceof RegExp)?c.length?function(){return arguments.length?a.apply(b,c.concat(ka.call(arguments,0))):a.apply(b,c)}:function(){return arguments.length?a.apply(b,arguments):a.call(b)}:a}function qc(b,a){var c=a;/^\$+/.test(b)?c=p:sa(a)?c="$WINDOW":a&&T===a?c="$DOCUMENT":a&&a.$evalAsync&&
a.$watch&&(c="$SCOPE");return c}function ha(b,a){return JSON.stringify(b,qc,a?" ":null)}function ub(b){return E(b)?JSON.parse(b):b}function ua(b){b&&b.length!==0?(b=I(""+b),b=!(b=="f"||b=="0"||b=="false"||b=="no"||b=="n"||b=="[]")):b=!1;return b}function va(b){b=w(b).clone();try{b.html("")}catch(a){}var c=w("<div>").append(b).html();try{return b[0].nodeType===3?I(c):c.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(a,b){return"<"+I(b)})}catch(d){return I(c)}}function vb(b){var a={},c,d;n((b||
"").split("&"),function(b){b&&(c=b.split("="),d=decodeURIComponent(c[0]),a[d]=B(c[1])?decodeURIComponent(c[1]):!0)});return a}function wb(b){var a=[];n(b,function(b,d){a.push(wa(d,!0)+(b===!0?"":"="+wa(b,!0)))});return a.length?a.join("&"):""}function ab(b){return wa(b,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function wa(b,a){return encodeURIComponent(b).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,a?"%20":"+")}function rc(b,
a){function c(a){a&&d.push(a)}var d=[b],e,g,i=["ng:app","ng-app","x-ng-app","data-ng-app"],f=/\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;n(i,function(a){i[a]=!0;c(T.getElementById(a));a=a.replace(":","\\:");b.querySelectorAll&&(n(b.querySelectorAll("."+a),c),n(b.querySelectorAll("."+a+"\\:"),c),n(b.querySelectorAll("["+a+"]"),c))});n(d,function(a){if(!e){var b=f.exec(" "+a.className+" ");b?(e=a,g=(b[2]||"").replace(/\s+/g,",")):n(a.attributes,function(b){if(!e&&i[b.name])e=a,g=b.value})}});e&&a(e,g?[g]:[])}
function xb(b,a){var c=function(){b=w(b);a=a||[];a.unshift(["$provide",function(a){a.value("$rootElement",b)}]);a.unshift("ng");var c=yb(a);c.invoke(["$rootScope","$rootElement","$compile","$injector","$animator",function(a,b,c,d,e){a.$apply(function(){b.data("$injector",d);c(b)(a)});e.enabled(!0)}]);return c},d=/^NG_DEFER_BOOTSTRAP!/;if(M&&!d.test(M.name))return c();M.name=M.name.replace(d,"");Ha.resumeBootstrap=function(b){n(b,function(b){a.push(b)});c()}}function bb(b,a){a=a||"_";return b.replace(sc,
function(b,d){return(d?a:"")+b.toLowerCase()})}function cb(b,a,c){if(!b)throw Error("Argument '"+(a||"?")+"' is "+(c||"required"));return b}function xa(b,a,c){c&&F(b)&&(b=b[b.length-1]);cb(H(b),a,"not a function, got "+(b&&typeof b=="object"?b.constructor.name||"Object":typeof b));return b}function tc(b){function a(a,b,e){return a[b]||(a[b]=e())}return a(a(b,"angular",Object),"module",function(){var b={};return function(d,e,g){e&&b.hasOwnProperty(d)&&(b[d]=null);return a(b,d,function(){function a(c,
d,e){return function(){b[e||"push"]([c,d,arguments]);return m}}if(!e)throw Error("No module: "+d);var b=[],c=[],j=a("$injector","invoke"),m={_invokeQueue:b,_runBlocks:c,requires:e,name:d,provider:a("$provide","provider"),factory:a("$provide","factory"),service:a("$provide","service"),value:a("$provide","value"),constant:a("$provide","constant","unshift"),animation:a("$animationProvider","register"),filter:a("$filterProvider","register"),controller:a("$controllerProvider","register"),directive:a("$compileProvider",
"directive"),config:j,run:function(a){c.push(a);return this}};g&&j(g);return m})}})}function Ia(b){return b.replace(uc,function(a,b,d,e){return e?d.toUpperCase():d}).replace(vc,"Moz$1")}function db(b,a){function c(){var e;for(var b=[this],c=a,i,f,h,j,m,k;b.length;){i=b.shift();f=0;for(h=i.length;f<h;f++){j=w(i[f]);c?j.triggerHandler("$destroy"):c=!c;m=0;for(e=(k=j.children()).length,j=e;m<j;m++)b.push(ga(k[m]))}}return d.apply(this,arguments)}var d=ga.fn[b],d=d.$original||d;c.$original=d;ga.fn[b]=
c}function R(b){if(b instanceof R)return b;if(!(this instanceof R)){if(E(b)&&b.charAt(0)!="<")throw Error("selectors not implemented");return new R(b)}if(E(b)){var a=T.createElement("div");a.innerHTML="<div>&#160;</div>"+b;a.removeChild(a.firstChild);eb(this,a.childNodes);this.remove()}else eb(this,b)}function fb(b){return b.cloneNode(!0)}function ya(b){zb(b);for(var a=0,b=b.childNodes||[];a<b.length;a++)ya(b[a])}function Ab(b,a,c){var d=ca(b,"events");ca(b,"handle")&&(C(a)?n(d,function(a,c){gb(b,
c,a);delete d[c]}):C(c)?(gb(b,a,d[a]),delete d[a]):ta(d[a],c))}function zb(b){var a=b[Ja],c=Ka[a];c&&(c.handle&&(c.events.$destroy&&c.handle({},"$destroy"),Ab(b)),delete Ka[a],b[Ja]=p)}function ca(b,a,c){var d=b[Ja],d=Ka[d||-1];if(B(c))d||(b[Ja]=d=++wc,d=Ka[d]={}),d[a]=c;else return d&&d[a]}function Bb(b,a,c){var d=ca(b,"data"),e=B(c),g=!e&&B(a),i=g&&!L(a);!d&&!i&&ca(b,"data",d={});if(e)d[a]=c;else if(g)if(i)return d&&d[a];else t(d,a);else return d}function La(b,a){return(" "+b.className+" ").replace(/[\n\t]/g,
" ").indexOf(" "+a+" ")>-1}function Cb(b,a){a&&n(a.split(" "),function(a){b.className=U((" "+b.className+" ").replace(/[\n\t]/g," ").replace(" "+U(a)+" "," "))})}function Db(b,a){a&&n(a.split(" "),function(a){if(!La(b,a))b.className=U(b.className+" "+U(a))})}function eb(b,a){if(a)for(var a=!a.nodeName&&B(a.length)&&!sa(a)?a:[a],c=0;c<a.length;c++)b.push(a[c])}function Eb(b,a){return Ma(b,"$"+(a||"ngController")+"Controller")}function Ma(b,a,c){b=w(b);for(b[0].nodeType==9&&(b=b.find("html"));b.length;){if(c=
b.data(a))return c;b=b.parent()}}function Fb(b,a){var c=Na[a.toLowerCase()];return c&&Gb[b.nodeName]&&c}function xc(b,a){var c=function(c,e){if(!c.preventDefault)c.preventDefault=function(){c.returnValue=!1};if(!c.stopPropagation)c.stopPropagation=function(){c.cancelBubble=!0};if(!c.target)c.target=c.srcElement||T;if(C(c.defaultPrevented)){var g=c.preventDefault;c.preventDefault=function(){c.defaultPrevented=!0;g.call(c)};c.defaultPrevented=!1}c.isDefaultPrevented=function(){return c.defaultPrevented||
c.returnValue==!1};n(a[e||c.type],function(a){a.call(b,c)});Z<=8?(c.preventDefault=null,c.stopPropagation=null,c.isDefaultPrevented=null):(delete c.preventDefault,delete c.stopPropagation,delete c.isDefaultPrevented)};c.elem=b;return c}function la(b){var a=typeof b,c;if(a=="object"&&b!==null)if(typeof(c=b.$$hashKey)=="function")c=b.$$hashKey();else{if(c===p)c=b.$$hashKey=Fa()}else c=b;return a+":"+c}function za(b){n(b,this.put,this)}function Hb(b){var a,c;if(typeof b=="function"){if(!(a=b.$inject))a=
[],c=b.toString().replace(yc,""),c=c.match(zc),n(c[1].split(Ac),function(b){b.replace(Bc,function(b,c,d){a.push(d)})}),b.$inject=a}else F(b)?(c=b.length-1,xa(b[c],"fn"),a=b.slice(0,c)):xa(b,"fn",!0);return a}function yb(b){function a(a){return function(b,c){if(L(b))n(b,rb(a));else return a(b,c)}}function c(a,b){if(H(b)||F(b))b=k.instantiate(b);if(!b.$get)throw Error("Provider "+a+" must define $get factory method.");return m[a+f]=b}function d(a,b){return c(a,{$get:b})}function e(a){var b=[];n(a,function(a){if(!j.get(a))if(j.put(a,
!0),E(a)){var c=Aa(a);b=b.concat(e(c.requires)).concat(c._runBlocks);try{for(var d=c._invokeQueue,c=0,f=d.length;c<f;c++){var g=d[c],o=k.get(g[0]);o[g[1]].apply(o,g[2])}}catch(h){throw h.message&&(h.message+=" from "+a),h;}}else if(H(a))try{b.push(k.invoke(a))}catch(l){throw l.message&&(l.message+=" from "+a),l;}else if(F(a))try{b.push(k.invoke(a))}catch(i){throw i.message&&(i.message+=" from "+String(a[a.length-1])),i;}else xa(a,"module")});return b}function g(a,b){function c(d){if(typeof d!=="string")throw Error("Service name expected");
if(a.hasOwnProperty(d)){if(a[d]===i)throw Error("Circular dependency: "+h.join(" <- "));return a[d]}else try{return h.unshift(d),a[d]=i,a[d]=b(d)}finally{h.shift()}}function d(a,b,e){var f=[],j=Hb(a),g,o,h;o=0;for(g=j.length;o<g;o++)h=j[o],f.push(e&&e.hasOwnProperty(h)?e[h]:c(h));a.$inject||(a=a[g]);switch(b?-1:f.length){case 0:return a();case 1:return a(f[0]);case 2:return a(f[0],f[1]);case 3:return a(f[0],f[1],f[2]);case 4:return a(f[0],f[1],f[2],f[3]);case 5:return a(f[0],f[1],f[2],f[3],f[4]);
case 6:return a(f[0],f[1],f[2],f[3],f[4],f[5]);case 7:return a(f[0],f[1],f[2],f[3],f[4],f[5],f[6]);case 8:return a(f[0],f[1],f[2],f[3],f[4],f[5],f[6],f[7]);case 9:return a(f[0],f[1],f[2],f[3],f[4],f[5],f[6],f[7],f[8]);case 10:return a(f[0],f[1],f[2],f[3],f[4],f[5],f[6],f[7],f[8],f[9]);default:return a.apply(b,f)}}return{invoke:d,instantiate:function(a,b){var c=function(){},e;c.prototype=(F(a)?a[a.length-1]:a).prototype;c=new c;e=d(a,c,b);return L(e)?e:c},get:c,annotate:Hb,has:function(b){return m.hasOwnProperty(b+
f)||a.hasOwnProperty(b)}}}var i={},f="Provider",h=[],j=new za,m={$provide:{provider:a(c),factory:a(d),service:a(function(a,b){return d(a,["$injector",function(a){return a.instantiate(b)}])}),value:a(function(a,b){return d(a,S(b))}),constant:a(function(a,b){m[a]=b;l[a]=b}),decorator:function(a,b){var c=k.get(a+f),d=c.$get;c.$get=function(){var a=u.invoke(d,c);return u.invoke(b,null,{$delegate:a})}}}},k=m.$injector=g(m,function(){throw Error("Unknown provider: "+h.join(" <- "));}),l={},u=l.$injector=
g(l,function(a){a=k.get(a+f);return u.invoke(a.$get,a)});n(e(b),function(a){u.invoke(a||q)});return u}function Cc(){var b=!0;this.disableAutoScrolling=function(){b=!1};this.$get=["$window","$location","$rootScope",function(a,c,d){function e(a){var b=null;n(a,function(a){!b&&I(a.nodeName)==="a"&&(b=a)});return b}function g(){var b=c.hash(),d;b?(d=i.getElementById(b))?d.scrollIntoView():(d=e(i.getElementsByName(b)))?d.scrollIntoView():b==="top"&&a.scrollTo(0,0):a.scrollTo(0,0)}var i=a.document;b&&d.$watch(function(){return c.hash()},
function(){d.$evalAsync(g)});return g}]}function Ib(b){this.register=function(a,c){b.factory(Ia(a)+"Animation",c)};this.$get=["$injector",function(a){return function(b){if(b&&(b=Ia(b)+"Animation",a.has(b)))return a.get(b)}}]}function Dc(b,a,c,d){function e(a){try{a.apply(null,ka.call(arguments,1))}finally{if(o--,o===0)for(;z.length;)try{z.pop()()}catch(b){c.error(b)}}}function g(a,b){(function s(){n(r,function(a){a()});y=b(s,a)})()}function i(){x!=f.url()&&(x=f.url(),n(v,function(a){a(f.url())}))}
var f=this,h=a[0],j=b.location,m=b.history,k=b.setTimeout,l=b.clearTimeout,u={};f.isMock=!1;var o=0,z=[];f.$$completeOutstandingRequest=e;f.$$incOutstandingRequestCount=function(){o++};f.notifyWhenNoOutstandingRequests=function(a){n(r,function(a){a()});o===0?a():z.push(a)};var r=[],y;f.addPollFn=function(a){C(y)&&g(100,k);r.push(a);return a};var x=j.href,W=a.find("base");f.url=function(a,b){if(a){if(x!=a)return x=a,d.history?b?m.replaceState(null,"",a):(m.pushState(null,"",a),W.attr("href",W.attr("href"))):
b?j.replace(a):j.href=a,f}else return j.href.replace(/%27/g,"'")};var v=[],A=!1;f.onUrlChange=function(a){A||(d.history&&w(b).bind("popstate",i),d.hashchange?w(b).bind("hashchange",i):f.addPollFn(i),A=!0);v.push(a);return a};f.baseHref=function(){var a=W.attr("href");return a?a.replace(/^https?\:\/\/[^\/]*/,""):""};var G={},D="",$=f.baseHref();f.cookies=function(a,b){var d,e,f,j;if(a)if(b===p)h.cookie=escape(a)+"=;path="+$+";expires=Thu, 01 Jan 1970 00:00:00 GMT";else{if(E(b))d=(h.cookie=escape(a)+
"="+escape(b)+";path="+$).length+1,d>4096&&c.warn("Cookie '"+a+"' possibly not set or overflowed because it was too large ("+d+" > 4096 bytes)!")}else{if(h.cookie!==D){D=h.cookie;d=D.split("; ");G={};for(f=0;f<d.length;f++)e=d[f],j=e.indexOf("="),j>0&&(a=unescape(e.substring(0,j)),G[a]===p&&(G[a]=unescape(e.substring(j+1))))}return G}};f.defer=function(a,b){var c;o++;c=k(function(){delete u[c];e(a)},b||0);u[c]=!0;return c};f.defer.cancel=function(a){return u[a]?(delete u[a],l(a),e(q),!0):!1}}function Ec(){this.$get=
["$window","$log","$sniffer","$document",function(b,a,c,d){return new Dc(b,d,a,c)}]}function Fc(){this.$get=function(){function b(b,d){function e(a){if(a!=k){if(l){if(l==a)l=a.n}else l=a;g(a.n,a.p);g(a,k);k=a;k.n=null}}function g(a,b){if(a!=b){if(a)a.p=b;if(b)b.n=a}}if(b in a)throw Error("cacheId "+b+" taken");var i=0,f=t({},d,{id:b}),h={},j=d&&d.capacity||Number.MAX_VALUE,m={},k=null,l=null;return a[b]={put:function(a,b){var c=m[a]||(m[a]={key:a});e(c);if(!C(b))return a in h||i++,h[a]=b,i>j&&this.remove(l.key),
b},get:function(a){var b=m[a];if(b)return e(b),h[a]},remove:function(a){var b=m[a];if(b){if(b==k)k=b.p;if(b==l)l=b.n;g(b.n,b.p);delete m[a];delete h[a];i--}},removeAll:function(){h={};i=0;m={};k=l=null},destroy:function(){m=f=h=null;delete a[b]},info:function(){return t({},f,{size:i})}}}var a={};b.info=function(){var b={};n(a,function(a,e){b[e]=a.info()});return b};b.get=function(b){return a[b]};return b}}function Gc(){this.$get=["$cacheFactory",function(b){return b("templates")}]}function Jb(b){var a=
{},c="Directive",d=/^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,e=/(([\d\w\-_]+)(?:\:([^;]+))?;?)/,g="Template must have exactly one root element. was: ",i=/^\s*(https?|ftp|mailto|file):/;this.directive=function h(d,e){E(d)?(cb(e,"directive"),a.hasOwnProperty(d)||(a[d]=[],b.factory(d+c,["$injector","$exceptionHandler",function(b,c){var e=[];n(a[d],function(a){try{var g=b.invoke(a);if(H(g))g={compile:S(g)};else if(!g.compile&&g.link)g.compile=S(g.link);g.priority=g.priority||0;g.name=g.name||d;g.require=
g.require||g.controller&&g.name;g.restrict=g.restrict||"A";e.push(g)}catch(h){c(h)}});return e}])),a[d].push(e)):n(d,rb(h));return this};this.urlSanitizationWhitelist=function(a){return B(a)?(i=a,this):i};this.$get=["$injector","$interpolate","$exceptionHandler","$http","$templateCache","$parse","$controller","$rootScope","$document",function(b,j,m,k,l,u,o,z,r){function y(a,b,c){a instanceof w||(a=w(a));n(a,function(b,c){b.nodeType==3&&b.nodeValue.match(/\S+/)&&(a[c]=w(b).wrap("<span></span>").parent()[0])});
var d=W(a,b,a,c);return function(b,c){cb(b,"scope");for(var e=c?Ba.clone.call(a):a,j=0,g=e.length;j<g;j++){var h=e[j];(h.nodeType==1||h.nodeType==9)&&e.eq(j).data("$scope",b)}x(e,"ng-scope");c&&c(e,b);d&&d(b,e,e);return e}}function x(a,b){try{a.addClass(b)}catch(c){}}function W(a,b,c,d){function e(a,c,d,g){var h,i,k,l,o,m,u,z=[];o=0;for(m=c.length;o<m;o++)z.push(c[o]);u=o=0;for(m=j.length;o<m;u++)i=z[u],c=j[o++],h=j[o++],c?(c.scope?(k=a.$new(L(c.scope)),w(i).data("$scope",k)):k=a,(l=c.transclude)||
!g&&b?c(h,k,i,d,function(b){return function(c){var d=a.$new();d.$$transcluded=!0;return b(d,c).bind("$destroy",$a(d,d.$destroy))}}(l||b)):c(h,k,i,p,g)):h&&h(a,i.childNodes,p,g)}for(var j=[],g,h,k,i=0;i<a.length;i++)h=new ma,g=v(a[i],[],h,d),h=(g=g.length?A(g,a[i],h,b,c):null)&&g.terminal||!a[i].childNodes||!a[i].childNodes.length?null:W(a[i].childNodes,g?g.transclude:b),j.push(g),j.push(h),k=k||g||h;return k?e:null}function v(a,b,c,j){var g=c.$attr,h;switch(a.nodeType){case 1:G(b,da(hb(a).toLowerCase()),
"E",j);var i,k,l;h=a.attributes;for(var o=0,m=h&&h.length;o<m;o++)if(i=h[o],i.specified)k=i.name,l=da(k),Y.test(l)&&(k=l.substr(6).toLowerCase()),l=da(k.toLowerCase()),g[l]=k,c[l]=i=U(Z&&k=="href"?decodeURIComponent(a.getAttribute(k,2)):i.value),Fb(a,l)&&(c[l]=!0),s(a,b,i,l),G(b,l,"A",j);a=a.className;if(E(a)&&a!=="")for(;h=e.exec(a);)l=da(h[2]),G(b,l,"C",j)&&(c[l]=U(h[3])),a=a.substr(h.index+h[0].length);break;case 3:P(b,a.nodeValue);break;case 8:try{if(h=d.exec(a.nodeValue))l=da(h[1]),G(b,l,"M",
j)&&(c[l]=U(h[2]))}catch(u){}}b.sort(K);return b}function A(a,b,c,d,e){function h(a,b){if(a)a.require=s.require,z.push(a);if(b)b.require=s.require,ea.push(b)}function i(a,b){var c,d="data",e=!1;if(E(a)){for(;(c=a.charAt(0))=="^"||c=="?";)a=a.substr(1),c=="^"&&(d="inheritedData"),e=e||c=="?";c=b[d]("$"+a+"Controller");if(!c&&!e)throw Error("No controller: "+a);}else F(a)&&(c=[],n(a,function(a){c.push(i(a,b))}));return c}function k(a,d,e,g,h){var l,v,r,D,x;l=b===e?c:pc(c,new ma(w(e),c.$attr));v=l.$$element;
if(K){var y=/^\s*([@=&])(\??)\s*(\w*)\s*$/,s=d.$parent||d;n(K.scope,function(a,b){var c=a.match(y)||[],e=c[3]||b,g=c[2]=="?",c=c[1],h,k,i;d.$$isolateBindings[b]=c+e;switch(c){case "@":l.$observe(e,function(a){d[b]=a});l.$$observers[e].$$scope=s;l[e]&&(d[b]=j(l[e])(s));break;case "=":if(g&&!l[e])break;k=u(l[e]);i=k.assign||function(){h=d[b]=k(s);throw Error(Kb+l[e]+" (directive: "+K.name+")");};h=d[b]=k(s);d.$watch(function(){var a=k(s);a!==d[b]&&(a!==h?h=d[b]=a:i(s,a=h=d[b]));return a});break;case "&":k=
u(l[e]);d[b]=function(a){return k(s,a)};break;default:throw Error("Invalid isolate scope definition for directive "+K.name+": "+a);}})}q&&n(q,function(a){var b={$scope:d,$element:v,$attrs:l,$transclude:h};x=a.controller;x=="@"&&(x=l[a.name]);v.data("$"+a.name+"Controller",o(x,b))});g=0;for(r=z.length;g<r;g++)try{D=z[g],D(d,v,l,D.require&&i(D.require,v))}catch(Hc){m(Hc,va(v))}a&&a(d,e.childNodes,p,h);g=0;for(r=ea.length;g<r;g++)try{D=ea[g],D(d,v,l,D.require&&i(D.require,v))}catch(J){m(J,va(v))}}for(var l=
-Number.MAX_VALUE,z=[],ea=[],r=null,K=null,W=null,J=c.$$element=w(b),s,A,Y,G,P=d,q,na,t,B=0,C=a.length;B<C;B++){s=a[B];Y=p;if(l>s.priority)break;if(t=s.scope)O("isolated scope",K,s,J),L(t)&&(x(J,"ng-isolate-scope"),K=s),x(J,"ng-scope"),r=r||s;A=s.name;if(t=s.controller)q=q||{},O("'"+A+"' controller",q[A],s,J),q[A]=s;if(t=s.transclude)O("transclusion",G,s,J),G=s,l=s.priority,t=="element"?(Y=w(b),J=c.$$element=w(T.createComment(" "+A+": "+c[A]+" ")),b=J[0],ja(e,w(Y[0]),b),P=y(Y,d,l)):(Y=w(fb(b)).contents(),
J.html(""),P=y(Y,d));if(s.template)if(O("template",W,s,J),W=s,t=H(s.template)?s.template(J,c):s.template,t=Lb(t),s.replace){Y=w("<div>"+U(t)+"</div>").contents();b=Y[0];if(Y.length!=1||b.nodeType!==1)throw Error(g+t);ja(e,J,b);A={$attr:{}};a=a.concat(v(b,a.splice(B+1,a.length-(B+1)),A));D(c,A);C=a.length}else J.html(t);if(s.templateUrl)O("template",W,s,J),W=s,k=$(a.splice(B,a.length-B),k,J,c,e,s.replace,P),C=a.length;else if(s.compile)try{na=s.compile(J,c,P),H(na)?h(null,na):na&&h(na.pre,na.post)}catch(I){m(I,
va(J))}if(s.terminal)k.terminal=!0,l=Math.max(l,s.priority)}k.scope=r&&r.scope;k.transclude=G&&P;return k}function G(d,e,j,g){var l=!1;if(a.hasOwnProperty(e))for(var k,e=b.get(e+c),i=0,o=e.length;i<o;i++)try{if(k=e[i],(g===p||g>k.priority)&&k.restrict.indexOf(j)!=-1)d.push(k),l=!0}catch(u){m(u)}return l}function D(a,b){var c=b.$attr,d=a.$attr,e=a.$$element;n(a,function(d,e){e.charAt(0)!="$"&&(b[e]&&(d+=(e==="style"?";":" ")+b[e]),a.$set(e,d,!0,c[e]))});n(b,function(b,j){j=="class"?(x(e,b),a["class"]=
(a["class"]?a["class"]+" ":"")+b):j=="style"?e.attr("style",e.attr("style")+";"+b):j.charAt(0)!="$"&&!a.hasOwnProperty(j)&&(a[j]=b,d[j]=c[j])})}function $(a,b,c,d,e,j,h){var i=[],o,m,u=c[0],z=a.shift(),r=t({},z,{controller:null,templateUrl:null,transclude:null,scope:null}),z=H(z.templateUrl)?z.templateUrl(c,d):z.templateUrl;c.html("");k.get(z,{cache:l}).success(function(l){var k,z,l=Lb(l);if(j){z=w("<div>"+U(l)+"</div>").contents();k=z[0];if(z.length!=1||k.nodeType!==1)throw Error(g+l);l={$attr:{}};
ja(e,c,k);v(k,a,l);D(d,l)}else k=u,c.html(l);a.unshift(r);o=A(a,k,d,h);for(m=W(c[0].childNodes,h);i.length;){var ea=i.shift(),l=i.shift();z=i.shift();var x=i.shift(),y=k;l!==u&&(y=fb(k),ja(z,w(l),y));o(function(){b(m,ea,y,e,x)},ea,y,e,x)}i=null}).error(function(a,b,c,d){throw Error("Failed to load template: "+d.url);});return function(a,c,d,e,j){i?(i.push(c),i.push(d),i.push(e),i.push(j)):o(function(){b(m,c,d,e,j)},c,d,e,j)}}function K(a,b){return b.priority-a.priority}function O(a,b,c,d){if(b)throw Error("Multiple directives ["+
b.name+", "+c.name+"] asking for "+a+" on: "+va(d));}function P(a,b){var c=j(b,!0);c&&a.push({priority:0,compile:S(function(a,b){var d=b.parent(),e=d.data("$binding")||[];e.push(c);x(d.data("$binding",e),"ng-binding");a.$watch(c,function(a){b[0].nodeValue=a})})})}function s(a,b,c,d){var e=j(c,!0);e&&b.push({priority:100,compile:S(function(a,b,c){b=c.$$observers||(c.$$observers={});if(e=j(c[d],!0))c[d]=e(a),(b[d]||(b[d]=[])).$$inter=!0,(c.$$observers&&c.$$observers[d].$$scope||a).$watch(e,function(a){c.$set(d,
a)})})})}function ja(a,b,c){var d=b[0],e=d.parentNode,j,g;if(a){j=0;for(g=a.length;j<g;j++)if(a[j]==d){a[j]=c;break}}e&&e.replaceChild(c,d);c[w.expando]=d[w.expando];b[0]=c}var ma=function(a,b){this.$$element=a;this.$attr=b||{}};ma.prototype={$normalize:da,$set:function(a,b,c,d){var e=Fb(this.$$element[0],a),j=this.$$observers;e&&(this.$$element.prop(a,b),d=e);this[a]=b;d?this.$attr[a]=d:(d=this.$attr[a])||(this.$attr[a]=d=bb(a,"-"));if(hb(this.$$element[0])==="A"&&a==="href")q.setAttribute("href",
b),e=q.href,e.match(i)||(this[a]=b="unsafe:"+e);c!==!1&&(b===null||b===p?this.$$element.removeAttr(d):this.$$element.attr(d,b));j&&n(j[a],function(a){try{a(b)}catch(c){m(c)}})},$observe:function(a,b){var c=this,d=c.$$observers||(c.$$observers={}),e=d[a]||(d[a]=[]);e.push(b);z.$evalAsync(function(){e.$$inter||b(c[a])});return b}};var q=r[0].createElement("a"),ea=j.startSymbol(),J=j.endSymbol(),Lb=ea=="{{"||J=="}}"?qa:function(a){return a.replace(/\{\{/g,ea).replace(/}}/g,J)},Y=/^ngAttr[A-Z]/;return y}]}
function da(b){return Ia(b.replace(Ic,""))}function Jc(){var b={},a=/^(\S+)(\s+as\s+(\w+))?$/;this.register=function(a,d){L(a)?t(b,a):b[a]=d};this.$get=["$injector","$window",function(c,d){return function(e,g){var i,f;E(e)&&(f=e.match(a),i=f[1],f=f[3],e=b.hasOwnProperty(i)?b[i]:ib(g.$scope,i,!0)||ib(d,i,!0),xa(e,i,!0));i=c.instantiate(e,g);if(f){if(typeof g.$scope!=="object")throw Error('Can not export controller as "'+f+'". No scope object provided!');g.$scope[f]=i}return i}}]}function Kc(){this.$get=
["$window",function(b){return w(b.document)}]}function Lc(){this.$get=["$log",function(b){return function(a,c){b.error.apply(b,arguments)}}]}function Mc(){var b="{{",a="}}";this.startSymbol=function(a){return a?(b=a,this):b};this.endSymbol=function(b){return b?(a=b,this):a};this.$get=["$parse","$exceptionHandler",function(c,d){function e(e,h){for(var j,m,k=0,l=[],u=e.length,o=!1,z=[];k<u;)(j=e.indexOf(b,k))!=-1&&(m=e.indexOf(a,j+g))!=-1?(k!=j&&l.push(e.substring(k,j)),l.push(k=c(o=e.substring(j+g,
m))),k.exp=o,k=m+i,o=!0):(k!=u&&l.push(e.substring(k)),k=u);if(!(u=l.length))l.push(""),u=1;if(!h||o)return z.length=u,k=function(a){try{for(var b=0,c=u,j;b<c;b++){if(typeof(j=l[b])=="function")j=j(a),j==null||j==p?j="":typeof j!="string"&&(j=ha(j));z[b]=j}return z.join("")}catch(g){d(Error("Error while interpolating: "+e+"\n"+g.toString()))}},k.exp=e,k.parts=l,k}var g=b.length,i=a.length;e.startSymbol=function(){return b};e.endSymbol=function(){return a};return e}]}function Mb(b){for(var b=b.split("/"),
a=b.length;a--;)b[a]=ab(b[a]);return b.join("/")}function Nb(b,a){var c=jb.exec(b);a.$$protocol=c[1];a.$$host=c[3];a.$$port=N(c[5])||Oa[c[1]]||null}function Ob(b,a){var c=Pb.exec(b);a.$$path=decodeURIComponent(c[1]);a.$$search=vb(c[3]);a.$$hash=decodeURIComponent(c[5]||"");if(a.$$path&&a.$$path.charAt(0)!="/")a.$$path="/"+a.$$path}function fa(b,a,c){return a.indexOf(b)==0?a.substr(b.length):c}function Ca(b){var a=b.indexOf("#");return a==-1?b:b.substr(0,a)}function kb(b){return b.substr(0,Ca(b).lastIndexOf("/")+
1)}function Qb(b,a){var a=a||"",c=kb(b);this.$$parse=function(a){var b={};Nb(a,b);var g=fa(c,a);if(!E(g))throw Error('Invalid url "'+a+'", missing path prefix "'+c+'".');Ob(g,b);t(this,b);if(!this.$$path)this.$$path="/";this.$$compose()};this.$$compose=function(){var a=wb(this.$$search),b=this.$$hash?"#"+ab(this.$$hash):"";this.$$url=Mb(this.$$path)+(a?"?"+a:"")+b;this.$$absUrl=c+this.$$url.substr(1)};this.$$rewrite=function(d){var e;if((e=fa(b,d))!==p)return d=e,(e=fa(a,e))!==p?c+(fa("/",e)||e):
b+d;else if((e=fa(c,d))!==p)return c+e;else if(c==d+"/")return c}}function lb(b,a){var c=kb(b);this.$$parse=function(d){Nb(d,this);var e=fa(b,d)||fa(c,d);if(!E(e))throw Error('Invalid url "'+d+'", does not start with "'+b+'".');e=e.charAt(0)=="#"?fa(a,e):e;if(!E(e))throw Error('Invalid url "'+d+'", missing hash prefix "'+a+'".');Ob(e,this);this.$$compose()};this.$$compose=function(){var c=wb(this.$$search),e=this.$$hash?"#"+ab(this.$$hash):"";this.$$url=Mb(this.$$path)+(c?"?"+c:"")+e;this.$$absUrl=
b+(this.$$url?a+this.$$url:"")};this.$$rewrite=function(a){if(Ca(b)==Ca(a))return a}}function Rb(b,a){lb.apply(this,arguments);var c=kb(b);this.$$rewrite=function(d){var e;if(b==Ca(d))return d;else if(e=fa(c,d))return b+a+e;else if(c===d+"/")return c}}function Pa(b){return function(){return this[b]}}function Sb(b,a){return function(c){if(C(c))return this[b];this[b]=a(c);this.$$compose();return this}}function Nc(){var b="",a=!1;this.hashPrefix=function(a){return B(a)?(b=a,this):b};this.html5Mode=function(b){return B(b)?
(a=b,this):a};this.$get=["$rootScope","$browser","$sniffer","$rootElement",function(c,d,e,g){function i(a){c.$broadcast("$locationChangeSuccess",f.absUrl(),a)}var f,h=d.baseHref(),j=d.url();a?(h=h?j.substring(0,j.indexOf("/",j.indexOf("//")+2))+h:j,e=e.history?Qb:Rb):(h=Ca(j),e=lb);f=new e(h,"#"+b);f.$$parse(f.$$rewrite(j));g.bind("click",function(a){if(!a.ctrlKey&&!(a.metaKey||a.which==2)){for(var b=w(a.target);I(b[0].nodeName)!=="a";)if(b[0]===g[0]||!(b=b.parent())[0])return;var e=b.prop("href"),
j=f.$$rewrite(e);e&&!b.attr("target")&&j&&!a.isDefaultPrevented()&&(a.preventDefault(),j!=d.url()&&(f.$$parse(j),c.$apply(),M.angular["ff-684208-preventDefault"]=!0))}});f.absUrl()!=j&&d.url(f.absUrl(),!0);d.onUrlChange(function(a){f.absUrl()!=a&&(c.$broadcast("$locationChangeStart",a,f.absUrl()).defaultPrevented?d.url(f.absUrl()):(c.$evalAsync(function(){var b=f.absUrl();f.$$parse(a);i(b)}),c.$$phase||c.$digest()))});var m=0;c.$watch(function(){var a=d.url(),b=f.$$replace;if(!m||a!=f.absUrl())m++,
c.$evalAsync(function(){c.$broadcast("$locationChangeStart",f.absUrl(),a).defaultPrevented?f.$$parse(a):(d.url(f.absUrl(),b),i(a))});f.$$replace=!1;return m});return f}]}function Oc(){var b=!0,a=this;this.debugEnabled=function(a){return B(a)?(b=a,this):b};this.$get=["$window",function(c){function d(a){a instanceof Error&&(a.stack?a=a.message&&a.stack.indexOf(a.message)===-1?"Error: "+a.message+"\n"+a.stack:a.stack:a.sourceURL&&(a=a.message+"\n"+a.sourceURL+":"+a.line));return a}function e(a){var b=
c.console||{},e=b[a]||b.log||q;return e.apply?function(){var a=[];n(arguments,function(b){a.push(d(b))});return e.apply(b,a)}:function(a,b){e(a,b)}}return{log:e("log"),warn:e("warn"),info:e("info"),error:e("error"),debug:function(){var c=e("debug");return function(){b&&c.apply(a,arguments)}}()}}]}function Pc(b,a){function c(a){return a.indexOf(r)!=-1}function d(a){a=a||1;return o+a<b.length?b.charAt(o+a):!1}function e(a){return"0"<=a&&a<="9"}function g(a){return a==" "||a=="\r"||a=="\t"||a=="\n"||
a=="\u000b"||a=="\u00a0"}function i(a){return"a"<=a&&a<="z"||"A"<=a&&a<="Z"||"_"==a||a=="$"}function f(a){return a=="-"||a=="+"||e(a)}function h(a,c,d){d=d||o;throw Error("Lexer Error: "+a+" at column"+(B(c)?"s "+c+"-"+o+" ["+b.substring(c,d)+"]":" "+d)+" in expression ["+b+"].");}function j(){for(var a="",c=o;o<b.length;){var j=I(b.charAt(o));if(j=="."||e(j))a+=j;else{var g=d();if(j=="e"&&f(g))a+=j;else if(f(j)&&g&&e(g)&&a.charAt(a.length-1)=="e")a+=j;else if(f(j)&&(!g||!e(g))&&a.charAt(a.length-
1)=="e")h("Invalid exponent");else break}o++}a*=1;l.push({index:c,text:a,json:!0,fn:function(){return a}})}function m(){for(var c="",d=o,f,j,h,k;o<b.length;){k=b.charAt(o);if(k=="."||i(k)||e(k))k=="."&&(f=o),c+=k;else break;o++}if(f)for(j=o;j<b.length;){k=b.charAt(j);if(k=="("){h=c.substr(f-d+1);c=c.substr(0,f-d);o=j;break}if(g(k))j++;else break}d={index:d,text:c};if(Da.hasOwnProperty(c))d.fn=d.json=Da[c];else{var m=Tb(c,a);d.fn=t(function(a,b){return m(a,b)},{assign:function(a,b){return Ub(a,c,b)}})}l.push(d);
h&&(l.push({index:f,text:".",json:!1}),l.push({index:f+1,text:h,json:!1}))}function k(a){var c=o;o++;for(var d="",e=a,f=!1;o<b.length;){var j=b.charAt(o);e+=j;if(f)j=="u"?(j=b.substring(o+1,o+5),j.match(/[\da-f]{4}/i)||h("Invalid unicode escape [\\u"+j+"]"),o+=4,d+=String.fromCharCode(parseInt(j,16))):(f=Qc[j],d+=f?f:j),f=!1;else if(j=="\\")f=!0;else if(j==a){o++;l.push({index:c,text:e,string:d,json:!0,fn:function(){return d}});return}else d+=j;o++}h("Unterminated quote",c)}for(var l=[],u,o=0,z=[],
r,y=":";o<b.length;){r=b.charAt(o);if(c("\"'"))k(r);else if(e(r)||c(".")&&e(d()))j();else if(i(r)){if(m(),"{,".indexOf(y)!=-1&&z[0]=="{"&&(u=l[l.length-1]))u.json=u.text.indexOf(".")==-1}else if(c("(){}[].,;:?"))l.push({index:o,text:r,json:":[,".indexOf(y)!=-1&&c("{[")||c("}]:,")}),c("{[")&&z.unshift(r),c("}]")&&z.shift(),o++;else if(g(r)){o++;continue}else{var x=r+d(),n=x+d(2),v=Da[r],A=Da[x],G=Da[n];G?(l.push({index:o,text:n,fn:G}),o+=3):A?(l.push({index:o,text:x,fn:A}),o+=2):v?(l.push({index:o,
text:r,fn:v,json:"[,:".indexOf(y)!=-1&&c("+-")}),o+=1):h("Unexpected next character ",o,o+1)}y=r}return l}function Rc(b,a,c,d){function e(a,c){throw Error("Syntax Error: Token '"+c.text+"' "+a+" at column "+(c.index+1)+" of the expression ["+b+"] starting at ["+b.substring(c.index)+"].");}function g(){if(O.length===0)throw Error("Unexpected end of expression: "+b);return O[0]}function i(a,b,c,d){if(O.length>0){var e=O[0],f=e.text;if(f==a||f==b||f==c||f==d||!a&&!b&&!c&&!d)return e}return!1}function f(b,
c,d,f){return(b=i(b,c,d,f))?(a&&!b.json&&e("is not valid json",b),O.shift(),b):!1}function h(a){f(a)||e("is unexpected, expecting ["+a+"]",i())}function j(a,b){return t(function(c,d){return a(c,d,b)},{constant:b.constant})}function m(a,b,c){return t(function(d,e){return a(d,e)?b(d,e):c(d,e)},{constant:a.constant&&b.constant&&c.constant})}function k(a,b,c){return t(function(d,e){return b(d,e,a,c)},{constant:a.constant&&c.constant})}function l(){for(var a=[];;)if(O.length>0&&!i("}",")",";","]")&&a.push(w()),
!f(";"))return a.length==1?a[0]:function(b,c){for(var d,e=0;e<a.length;e++){var f=a[e];f&&(d=f(b,c))}return d}}function u(){for(var a=f(),b=c(a.text),d=[];;)if(a=f(":"))d.push(P());else{var e=function(a,c,e){for(var e=[e],f=0;f<d.length;f++)e.push(d[f](a,c));return b.apply(a,e)};return function(){return e}}}function o(){var a=z(),b,c;if(f("?"))if(b=o(),c=f(":"))return m(a,b,o());else e("expected :",c);else return a}function z(){for(var a=r(),b;;)if(b=f("||"))a=k(a,b.fn,r());else return a}function r(){var a=
y(),b;if(b=f("&&"))a=k(a,b.fn,r());return a}function y(){var a=x(),b;if(b=f("==","!=","===","!=="))a=k(a,b.fn,y());return a}function x(){var a;a=n();for(var b;b=f("+","-");)a=k(a,b.fn,n());if(b=f("<",">","<=",">="))a=k(a,b.fn,x());return a}function n(){for(var a=v(),b;b=f("*","/","%");)a=k(a,b.fn,v());return a}function v(){var a;return f("+")?A():(a=f("-"))?k($,a.fn,v()):(a=f("!"))?j(a.fn,v()):A()}function A(){var a;if(f("("))a=w(),h(")");else if(f("["))a=G();else if(f("{"))a=D();else{var b=f();(a=
b.fn)||e("not a primary expression",b);if(b.json)a.constant=a.literal=!0}for(var c;b=f("(","[",".");)b.text==="("?(a=s(a,c),c=null):b.text==="["?(c=a,a=ma(a)):b.text==="."?(c=a,a=ja(a)):e("IMPOSSIBLE");return a}function G(){var a=[],b=!0;if(g().text!="]"){do{var c=P();a.push(c);c.constant||(b=!1)}while(f(","))}h("]");return t(function(b,c){for(var d=[],e=0;e<a.length;e++)d.push(a[e](b,c));return d},{literal:!0,constant:b})}function D(){var a=[],b=!0;if(g().text!="}"){do{var c=f(),c=c.string||c.text;
h(":");var d=P();a.push({key:c,value:d});d.constant||(b=!1)}while(f(","))}h("}");return t(function(b,c){for(var d={},e=0;e<a.length;e++){var f=a[e];d[f.key]=f.value(b,c)}return d},{literal:!0,constant:b})}var $=S(0),K,O=Pc(b,d),P=function(){var a=o(),c,d;return(d=f("="))?(a.assign||e("implies assignment but ["+b.substring(0,d.index)+"] can not be assigned to",d),c=o(),function(b,d){return a.assign(b,c(b,d),d)}):a},s=function(a,b){var c=[];if(g().text!=")"){do c.push(P());while(f(","))}h(")");return function(d,
e){for(var f=[],j=b?b(d,e):d,g=0;g<c.length;g++)f.push(c[g](d,e));g=a(d,e,j)||q;return g.apply?g.apply(j,f):g(f[0],f[1],f[2],f[3],f[4])}},ja=function(a){var b=f().text,c=Tb(b,d);return t(function(b,d,e){return c(e||a(b,d),d)},{assign:function(c,d,e){return Ub(a(c,e),b,d)}})},ma=function(a){var b=P();h("]");return t(function(c,d){var e=a(c,d),f=b(c,d),j;if(!e)return p;if((e=e[f])&&e.then){j=e;if(!("$$v"in e))j.$$v=p,j.then(function(a){j.$$v=a});e=e.$$v}return e},{assign:function(c,d,e){return a(c,
e)[b(c,e)]=d}})},w=function(){for(var a=P(),b;;)if(b=f("|"))a=k(a,b.fn,u());else return a};a?(P=z,s=ja=ma=w=function(){e("is not valid json",{text:b,index:0})},K=A()):K=l();O.length!==0&&e("is an unexpected token",O[0]);K.literal=!!K.literal;K.constant=!!K.constant;return K}function Ub(b,a,c){for(var a=a.split("."),d=0;a.length>1;d++){var e=a.shift(),g=b[e];g||(g={},b[e]=g);b=g}return b[a.shift()]=c}function ib(b,a,c){if(!a)return b;for(var a=a.split("."),d,e=b,g=a.length,i=0;i<g;i++)d=a[i],b&&(b=
(e=b)[d]);return!c&&H(b)?$a(e,b):b}function Vb(b,a,c,d,e){return function(g,i){var f=i&&i.hasOwnProperty(b)?i:g,h;if(f===null||f===p)return f;if((f=f[b])&&f.then){if(!("$$v"in f))h=f,h.$$v=p,h.then(function(a){h.$$v=a});f=f.$$v}if(!a||f===null||f===p)return f;if((f=f[a])&&f.then){if(!("$$v"in f))h=f,h.$$v=p,h.then(function(a){h.$$v=a});f=f.$$v}if(!c||f===null||f===p)return f;if((f=f[c])&&f.then){if(!("$$v"in f))h=f,h.$$v=p,h.then(function(a){h.$$v=a});f=f.$$v}if(!d||f===null||f===p)return f;if((f=
f[d])&&f.then){if(!("$$v"in f))h=f,h.$$v=p,h.then(function(a){h.$$v=a});f=f.$$v}if(!e||f===null||f===p)return f;if((f=f[e])&&f.then){if(!("$$v"in f))h=f,h.$$v=p,h.then(function(a){h.$$v=a});f=f.$$v}return f}}function Tb(b,a){if(mb.hasOwnProperty(b))return mb[b];var c=b.split("."),d=c.length,e;if(a)e=d<6?Vb(c[0],c[1],c[2],c[3],c[4]):function(a,b){var e=0,j;do j=Vb(c[e++],c[e++],c[e++],c[e++],c[e++])(a,b),b=p,a=j;while(e<d);return j};else{var g="var l, fn, p;\n";n(c,function(a,b){g+="if(s === null || s === undefined) return s;\nl=s;\ns="+
(b?"s":'((k&&k.hasOwnProperty("'+a+'"))?k:s)')+'["'+a+'"];\nif (s && s.then) {\n if (!("$$v" in s)) {\n p=s;\n p.$$v = undefined;\n p.then(function(v) {p.$$v=v;});\n}\n s=s.$$v\n}\n'});g+="return s;";e=Function("s","k",g);e.toString=function(){return g}}return mb[b]=e}function Sc(){var b={};this.$get=["$filter","$sniffer",function(a,c){return function(d){switch(typeof d){case "string":return b.hasOwnProperty(d)?b[d]:b[d]=Rc(d,!1,a,c.csp);case "function":return d;default:return q}}}]}function Tc(){this.$get=
["$rootScope","$exceptionHandler",function(b,a){return Uc(function(a){b.$evalAsync(a)},a)}]}function Uc(b,a){function c(a){return a}function d(a){return i(a)}var e=function(){var f=[],h,j;return j={resolve:function(a){if(f){var c=f;f=p;h=g(a);c.length&&b(function(){for(var a,b=0,d=c.length;b<d;b++)a=c[b],h.then(a[0],a[1])})}},reject:function(a){j.resolve(i(a))},promise:{then:function(b,j){var g=e(),i=function(d){try{g.resolve((b||c)(d))}catch(e){a(e),g.reject(e)}},o=function(b){try{g.resolve((j||
d)(b))}catch(c){a(c),g.reject(c)}};f?f.push([i,o]):h.then(i,o);return g.promise},always:function(a){function b(a,c){var d=e();c?d.resolve(a):d.reject(a);return d.promise}function d(e,f){var j=null;try{j=(a||c)()}catch(g){return b(g,!1)}return j&&j.then?j.then(function(){return b(e,f)},function(a){return b(a,!1)}):b(e,f)}return this.then(function(a){return d(a,!0)},function(a){return d(a,!1)})}}}},g=function(a){return a&&a.then?a:{then:function(c){var d=e();b(function(){d.resolve(c(a))});return d.promise}}},
i=function(a){return{then:function(c,j){var g=e();b(function(){g.resolve((j||d)(a))});return g.promise}}};return{defer:e,reject:i,when:function(f,h,j){var m=e(),k,l=function(b){try{return(h||c)(b)}catch(d){return a(d),i(d)}},u=function(b){try{return(j||d)(b)}catch(c){return a(c),i(c)}};b(function(){g(f).then(function(a){k||(k=!0,m.resolve(g(a).then(l,u)))},function(a){k||(k=!0,m.resolve(u(a)))})});return m.promise},all:function(a){var b=e(),c=0,d=F(a)?[]:{};n(a,function(a,e){c++;g(a).then(function(a){d.hasOwnProperty(e)||
(d[e]=a,--c||b.resolve(d))},function(a){d.hasOwnProperty(e)||b.reject(a)})});c===0&&b.resolve(d);return b.promise}}}function Vc(){var b={};this.when=function(a,c){b[a]=t({reloadOnSearch:!0,caseInsensitiveMatch:!1},c);if(a){var d=a[a.length-1]=="/"?a.substr(0,a.length-1):a+"/";b[d]={redirectTo:a}}return this};this.otherwise=function(a){this.when(null,a);return this};this.$get=["$rootScope","$location","$routeParams","$q","$injector","$http","$templateCache",function(a,c,d,e,g,i,f){function h(a,b,c){for(var b=
"^"+b.replace(/[-\/\\^$:*+?.()|[\]{}]/g,"\\$&")+"$",d="",e=[],f={},j=/\\([:*])(\w+)/g,g,i=0;(g=j.exec(b))!==null;){d+=b.slice(i,g.index);switch(g[1]){case ":":d+="([^\\/]*)";break;case "*":d+="(.*)"}e.push(g[2]);i=j.lastIndex}d+=b.substr(i);var h=a.match(RegExp(d,c.caseInsensitiveMatch?"i":""));h&&n(e,function(a,b){f[a]=h[b+1]});return h?f:null}function j(){var b=m(),j=u.current;if(b&&j&&b.$$route===j.$$route&&ia(b.pathParams,j.pathParams)&&!b.reloadOnSearch&&!l)j.params=b.params,V(j.params,d),a.$broadcast("$routeUpdate",
j);else if(b||j)l=!1,a.$broadcast("$routeChangeStart",b,j),(u.current=b)&&b.redirectTo&&(E(b.redirectTo)?c.path(k(b.redirectTo,b.params)).search(b.params).replace():c.url(b.redirectTo(b.pathParams,c.path(),c.search())).replace()),e.when(b).then(function(){if(b){var a=t({},b.resolve),c;n(a,function(b,c){a[c]=E(b)?g.get(b):g.invoke(b)});if(B(c=b.template))H(c)&&(c=c(b.params));else if(B(c=b.templateUrl))if(H(c)&&(c=c(b.params)),B(c))b.loadedTemplateUrl=c,c=i.get(c,{cache:f}).then(function(a){return a.data});
B(c)&&(a.$template=c);return e.all(a)}}).then(function(c){if(b==u.current){if(b)b.locals=c,V(b.params,d);a.$broadcast("$routeChangeSuccess",b,j)}},function(c){b==u.current&&a.$broadcast("$routeChangeError",b,j,c)})}function m(){var a,d;n(b,function(b,e){if(!d&&(a=h(c.path(),e,b)))d=tb(b,{params:t({},c.search(),a),pathParams:a}),d.$$route=b});return d||b[null]&&tb(b[null],{params:{},pathParams:{}})}function k(a,b){var c=[];n((a||"").split(":"),function(a,d){if(d==0)c.push(a);else{var e=a.match(/(\w+)(.*)/),
f=e[1];c.push(b[f]);c.push(e[2]||"");delete b[f]}});return c.join("")}var l=!1,u={routes:b,reload:function(){l=!0;a.$evalAsync(j)}};a.$on("$locationChangeSuccess",j);return u}]}function Wc(){this.$get=S({})}function Xc(){var b=10;this.digestTtl=function(a){arguments.length&&(b=a);return b};this.$get=["$injector","$exceptionHandler","$parse",function(a,c,d){function e(){this.$id=Fa();this.$$phase=this.$parent=this.$$watchers=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=null;
this["this"]=this.$root=this;this.$$destroyed=!1;this.$$asyncQueue=[];this.$$listeners={};this.$$isolateBindings={}}function g(a){if(h.$$phase)throw Error(h.$$phase+" already in progress");h.$$phase=a}function i(a,b){var c=d(a);xa(c,b);return c}function f(){}e.prototype={$new:function(a){if(H(a))throw Error("API-CHANGE: Use $controller to instantiate controllers.");a?(a=new e,a.$root=this.$root):(a=function(){},a.prototype=this,a=new a,a.$id=Fa());a["this"]=a;a.$$listeners={};a.$parent=this;a.$$watchers=
a.$$nextSibling=a.$$childHead=a.$$childTail=null;a.$$prevSibling=this.$$childTail;this.$$childHead?this.$$childTail=this.$$childTail.$$nextSibling=a:this.$$childHead=this.$$childTail=a;return a},$watch:function(a,b,c){var d=i(a,"watch"),e=this.$$watchers,g={fn:b,last:f,get:d,exp:a,eq:!!c};if(!H(b)){var h=i(b||q,"listener");g.fn=function(a,b,c){h(c)}}if(typeof a=="string"&&d.constant){var r=g.fn;g.fn=function(a,b,c){r.call(this,a,b,c);ta(e,g)}}if(!e)e=this.$$watchers=[];e.unshift(g);return function(){ta(e,
g)}},$watchCollection:function(a,b){var c=this,e,f,g=0,i=d(a),h=[],n={},x=0;return this.$watch(function(){f=i(c);var a,b;if(L(f))if(Xa(f)){if(e!==h)e=h,x=e.length=0,g++;a=f.length;if(x!==a)g++,e.length=x=a;for(b=0;b<a;b++)e[b]!==f[b]&&(g++,e[b]=f[b])}else{e!==n&&(e=n={},x=0,g++);a=0;for(b in f)f.hasOwnProperty(b)&&(a++,e.hasOwnProperty(b)?e[b]!==f[b]&&(g++,e[b]=f[b]):(x++,e[b]=f[b],g++));if(x>a)for(b in g++,e)e.hasOwnProperty(b)&&!f.hasOwnProperty(b)&&(x--,delete e[b])}else e!==f&&(e=f,g++);return g},
function(){b(f,e,c)})},$digest:function(){var a,d,e,i,u=this.$$asyncQueue,o,z,r=b,n,x=[],p,v;g("$digest");do{z=!1;for(n=this;u.length;)try{n.$eval(u.shift())}catch(A){c(A)}do{if(i=n.$$watchers)for(o=i.length;o--;)try{if(a=i[o],(d=a.get(n))!==(e=a.last)&&!(a.eq?ia(d,e):typeof d=="number"&&typeof e=="number"&&isNaN(d)&&isNaN(e)))z=!0,a.last=a.eq?V(d):d,a.fn(d,e===f?d:e,n),r<5&&(p=4-r,x[p]||(x[p]=[]),v=H(a.exp)?"fn: "+(a.exp.name||a.exp.toString()):a.exp,v+="; newVal: "+ha(d)+"; oldVal: "+ha(e),x[p].push(v))}catch(G){c(G)}if(!(i=
n.$$childHead||n!==this&&n.$$nextSibling))for(;n!==this&&!(i=n.$$nextSibling);)n=n.$parent}while(n=i);if(z&&!r--)throw h.$$phase=null,Error(b+" $digest() iterations reached. Aborting!\nWatchers fired in the last 5 iterations: "+ha(x));}while(z||u.length);h.$$phase=null},$destroy:function(){if(!(h==this||this.$$destroyed)){var a=this.$parent;this.$broadcast("$destroy");this.$$destroyed=!0;if(a.$$childHead==this)a.$$childHead=this.$$nextSibling;if(a.$$childTail==this)a.$$childTail=this.$$prevSibling;
if(this.$$prevSibling)this.$$prevSibling.$$nextSibling=this.$$nextSibling;if(this.$$nextSibling)this.$$nextSibling.$$prevSibling=this.$$prevSibling;this.$parent=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=null}},$eval:function(a,b){return d(a)(this,b)},$evalAsync:function(a){this.$$asyncQueue.push(a)},$apply:function(a){try{return g("$apply"),this.$eval(a)}catch(b){c(b)}finally{h.$$phase=null;try{h.$digest()}catch(d){throw c(d),d;}}},$on:function(a,b){var c=this.$$listeners[a];
c||(this.$$listeners[a]=c=[]);c.push(b);return function(){c[Ga(c,b)]=null}},$emit:function(a,b){var d=[],e,f=this,g=!1,i={name:a,targetScope:f,stopPropagation:function(){g=!0},preventDefault:function(){i.defaultPrevented=!0},defaultPrevented:!1},h=[i].concat(ka.call(arguments,1)),n,x;do{e=f.$$listeners[a]||d;i.currentScope=f;n=0;for(x=e.length;n<x;n++)if(e[n])try{if(e[n].apply(null,h),g)return i}catch(p){c(p)}else e.splice(n,1),n--,x--;f=f.$parent}while(f);return i},$broadcast:function(a,b){var d=
this,e=this,f={name:a,targetScope:this,preventDefault:function(){f.defaultPrevented=!0},defaultPrevented:!1},g=[f].concat(ka.call(arguments,1)),i,h;do{d=e;f.currentScope=d;e=d.$$listeners[a]||[];i=0;for(h=e.length;i<h;i++)if(e[i])try{e[i].apply(null,g)}catch(n){c(n)}else e.splice(i,1),i--,h--;if(!(e=d.$$childHead||d!==this&&d.$$nextSibling))for(;d!==this&&!(e=d.$$nextSibling);)d=d.$parent}while(d=e);return f}};var h=new e;return h}]}function Yc(){this.$get=["$window","$document",function(b,a){var c=
{},d=N((/android (\d+)/.exec(I((b.navigator||{}).userAgent))||[])[1]),e=a[0]||{},g,i=/^(Moz|webkit|O|ms)(?=[A-Z])/,f=e.body&&e.body.style,h=!1,j=!1;if(f){for(var m in f)if(h=i.exec(m)){g=h[0];g=g.substr(0,1).toUpperCase()+g.substr(1);break}h=!!("transition"in f||g+"Transition"in f);j=!!("animation"in f||g+"Animation"in f)}return{history:!(!b.history||!b.history.pushState||d<4),hashchange:"onhashchange"in b&&(!e.documentMode||e.documentMode>7),hasEvent:function(a){if(a=="input"&&Z==9)return!1;if(C(c[a])){var b=
e.createElement("div");c[a]="on"+a in b}return c[a]},csp:e.securityPolicy?e.securityPolicy.isActive:!1,vendorPrefix:g,transitions:h,animations:j}}]}function Zc(){this.$get=S(M)}function Wb(b){var a={},c,d,e;if(!b)return a;n(b.split("\n"),function(b){e=b.indexOf(":");c=I(U(b.substr(0,e)));d=U(b.substr(e+1));c&&(a[c]?a[c]+=", "+d:a[c]=d)});return a}function $c(b,a){var c=ad.exec(b);if(c==null)return!0;var d={protocol:c[2],host:c[4],port:N(c[6])||Oa[c[2]]||null,relativeProtocol:c[2]===p||c[2]===""},
c=jb.exec(a),c={protocol:c[1],host:c[3],port:N(c[5])||Oa[c[1]]||null};return(d.protocol==c.protocol||d.relativeProtocol)&&d.host==c.host&&(d.port==c.port||d.relativeProtocol&&c.port==Oa[c.protocol])}function Xb(b){var a=L(b)?b:p;return function(c){a||(a=Wb(b));return c?a[I(c)]||null:a}}function Yb(b,a,c){if(H(c))return c(b,a);n(c,function(c){b=c(b,a)});return b}function bd(){var b=/^\s*(\[|\{[^\{])/,a=/[\}\]]\s*$/,c=/^\)\]\}',?\n/,d={"Content-Type":"application/json;charset=utf-8"},e=this.defaults=
{transformResponse:[function(d){E(d)&&(d=d.replace(c,""),b.test(d)&&a.test(d)&&(d=ub(d,!0)));return d}],transformRequest:[function(a){return L(a)&&Ea.apply(a)!=="[object File]"?ha(a):a}],headers:{common:{Accept:"application/json, text/plain, */*"},post:d,put:d,patch:d},xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN"},g=this.interceptors=[],i=this.responseInterceptors=[];this.$get=["$httpBackend","$browser","$cacheFactory","$rootScope","$q","$injector",function(a,b,c,d,k,l){function u(a){function c(a){var b=
t({},a,{data:Yb(a.data,a.headers,d.transformResponse)});return 200<=a.status&&a.status<300?b:k.reject(b)}var d={transformRequest:e.transformRequest,transformResponse:e.transformResponse},f={};t(d,a);d.headers=f;d.method=oa(d.method);t(f,e.headers.common,e.headers[I(d.method)],a.headers);(a=$c(d.url,b.url())?b.cookies()[d.xsrfCookieName||e.xsrfCookieName]:p)&&(f[d.xsrfHeaderName||e.xsrfHeaderName]=a);var g=[function(a){var b=Yb(a.data,Xb(f),a.transformRequest);C(a.data)&&delete f["Content-Type"];if(C(a.withCredentials)&&
!C(e.withCredentials))a.withCredentials=e.withCredentials;return o(a,b,f).then(c,c)},p],j=k.when(d);for(n(y,function(a){(a.request||a.requestError)&&g.unshift(a.request,a.requestError);(a.response||a.responseError)&&g.push(a.response,a.responseError)});g.length;)var a=g.shift(),i=g.shift(),j=j.then(a,i);j.success=function(a){j.then(function(b){a(b.data,b.status,b.headers,d)});return j};j.error=function(a){j.then(null,function(b){a(b.data,b.status,b.headers,d)});return j};return j}function o(b,c,g){function j(a,
b,c){n&&(200<=a&&a<300?n.put(s,[a,b,Wb(c)]):n.remove(s));i(b,a,c);d.$$phase||d.$apply()}function i(a,c,d){c=Math.max(c,0);(200<=c&&c<300?l.resolve:l.reject)({data:a,status:c,headers:Xb(d),config:b})}function h(){var a=Ga(u.pendingRequests,b);a!==-1&&u.pendingRequests.splice(a,1)}var l=k.defer(),o=l.promise,n,p,s=z(b.url,b.params);u.pendingRequests.push(b);o.then(h,h);if((b.cache||e.cache)&&b.cache!==!1&&b.method=="GET")n=L(b.cache)?b.cache:L(e.cache)?e.cache:r;if(n)if(p=n.get(s))if(p.then)return p.then(h,
h),p;else F(p)?i(p[1],p[0],V(p[2])):i(p,200,{});else n.put(s,o);p||a(b.method,s,c,j,g,b.timeout,b.withCredentials,b.responseType);return o}function z(a,b){if(!b)return a;var c=[];nc(b,function(a,b){a==null||a==p||(F(a)||(a=[a]),n(a,function(a){L(a)&&(a=ha(a));c.push(wa(b)+"="+wa(a))}))});return a+(a.indexOf("?")==-1?"?":"&")+c.join("&")}var r=c("$http"),y=[];n(g,function(a){y.unshift(E(a)?l.get(a):l.invoke(a))});n(i,function(a,b){var c=E(a)?l.get(a):l.invoke(a);y.splice(b,0,{response:function(a){return c(k.when(a))},
responseError:function(a){return c(k.reject(a))}})});u.pendingRequests=[];(function(a){n(arguments,function(a){u[a]=function(b,c){return u(t(c||{},{method:a,url:b}))}})})("get","delete","head","jsonp");(function(a){n(arguments,function(a){u[a]=function(b,c,d){return u(t(d||{},{method:a,url:b,data:c}))}})})("post","put");u.defaults=e;return u}]}function cd(){this.$get=["$browser","$window","$document",function(b,a,c){return dd(b,ed,b.defer,a.angular.callbacks,c[0],a.location.protocol.replace(":",""))}]}
function dd(b,a,c,d,e,g){function i(a,b){var c=e.createElement("script"),d=function(){e.body.removeChild(c);b&&b()};c.type="text/javascript";c.src=a;Z?c.onreadystatechange=function(){/loaded|complete/.test(c.readyState)&&d()}:c.onload=c.onerror=d;e.body.appendChild(c);return d}return function(e,h,j,m,k,l,u,o){function z(){p=-1;t&&t();v&&v.abort()}function r(a,d,e,f){var j=(h.match(jb)||["",g])[1];A&&c.cancel(A);t=v=null;d=j=="file"?e?200:404:d;a(d==1223?204:d,e,f);b.$$completeOutstandingRequest(q)}
var p;b.$$incOutstandingRequestCount();h=h||b.url();if(I(e)=="jsonp"){var x="_"+(d.counter++).toString(36);d[x]=function(a){d[x].data=a};var t=i(h.replace("JSON_CALLBACK","angular.callbacks."+x),function(){d[x].data?r(m,200,d[x].data):r(m,p||-2);delete d[x]})}else{var v=new a;v.open(e,h,!0);n(k,function(a,b){a&&v.setRequestHeader(b,a)});v.onreadystatechange=function(){if(v.readyState==4){var a=v.getAllResponseHeaders(),b=["Cache-Control","Content-Language","Content-Type","Expires","Last-Modified",
"Pragma"];a||(a="",n(b,function(b){var c=v.getResponseHeader(b);c&&(a+=b+": "+c+"\n")}));r(m,p||v.status,v.responseType?v.response:v.responseText,a)}};if(u)v.withCredentials=!0;if(o)v.responseType=o;v.send(j||"")}if(l>0)var A=c(z,l);else l&&l.then&&l.then(z)}}function fd(){this.$get=function(){return{id:"en-us",NUMBER_FORMATS:{DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{minInt:1,minFrac:0,maxFrac:3,posPre:"",posSuf:"",negPre:"-",negSuf:"",gSize:3,lgSize:3},{minInt:1,minFrac:2,maxFrac:2,posPre:"\u00a4",
posSuf:"",negPre:"(\u00a4",negSuf:")",gSize:3,lgSize:3}],CURRENCY_SYM:"$"},DATETIME_FORMATS:{MONTH:"January,February,March,April,May,June,July,August,September,October,November,December".split(","),SHORTMONTH:"Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec".split(","),DAY:"Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday".split(","),SHORTDAY:"Sun,Mon,Tue,Wed,Thu,Fri,Sat".split(","),AMPMS:["AM","PM"],medium:"MMM d, y h:mm:ss a","short":"M/d/yy h:mm a",fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",
mediumDate:"MMM d, y",shortDate:"M/d/yy",mediumTime:"h:mm:ss a",shortTime:"h:mm a"},pluralCat:function(b){return b===1?"one":"other"}}}}function gd(){this.$get=["$rootScope","$browser","$q","$exceptionHandler",function(b,a,c,d){function e(e,f,h){var j=c.defer(),m=j.promise,k=B(h)&&!h,f=a.defer(function(){try{j.resolve(e())}catch(a){j.reject(a),d(a)}k||b.$apply()},f),h=function(){delete g[m.$$timeoutId]};m.$$timeoutId=f;g[f]=j;m.then(h,h);return m}var g={};e.cancel=function(b){return b&&b.$$timeoutId in
g?(g[b.$$timeoutId].reject("canceled"),a.defer.cancel(b.$$timeoutId)):!1};return e}]}function Zb(b){function a(a,e){return b.factory(a+c,e)}var c="Filter";this.register=a;this.$get=["$injector",function(a){return function(b){return a.get(b+c)}}];a("currency",$b);a("date",ac);a("filter",hd);a("json",id);a("limitTo",jd);a("lowercase",kd);a("number",bc);a("orderBy",cc);a("uppercase",ld)}function hd(){return function(b,a,c){if(!F(b))return b;var d=[];d.check=function(a){for(var b=0;b<d.length;b++)if(!d[b](a))return!1;
return!0};switch(typeof c){case "function":break;case "boolean":if(c==!0){c=function(a,b){return Ha.equals(a,b)};break}default:c=function(a,b){b=(""+b).toLowerCase();return(""+a).toLowerCase().indexOf(b)>-1}}var e=function(a,b){if(typeof b=="string"&&b.charAt(0)==="!")return!e(a,b.substr(1));switch(typeof a){case "boolean":case "number":case "string":return c(a,b);case "object":switch(typeof b){case "object":return c(a,b);default:for(var d in a)if(d.charAt(0)!=="$"&&e(a[d],b))return!0}return!1;case "array":for(d=
0;d<a.length;d++)if(e(a[d],b))return!0;return!1;default:return!1}};switch(typeof a){case "boolean":case "number":case "string":a={$:a};case "object":for(var g in a)g=="$"?function(){if(a[g]){var b=g;d.push(function(c){return e(c,a[b])})}}():function(){if(a[g]){var b=g;d.push(function(c){return e(ib(c,b),a[b])})}}();break;case "function":d.push(a);break;default:return b}for(var i=[],f=0;f<b.length;f++){var h=b[f];d.check(h)&&i.push(h)}return i}}function $b(b){var a=b.NUMBER_FORMATS;return function(b,
d){if(C(d))d=a.CURRENCY_SYM;return dc(b,a.PATTERNS[1],a.GROUP_SEP,a.DECIMAL_SEP,2).replace(/\u00A4/g,d)}}function bc(b){var a=b.NUMBER_FORMATS;return function(b,d){return dc(b,a.PATTERNS[0],a.GROUP_SEP,a.DECIMAL_SEP,d)}}function dc(b,a,c,d,e){if(isNaN(b)||!isFinite(b))return"";var g=b<0,b=Math.abs(b),i=b+"",f="",h=[],j=!1;if(i.indexOf("e")!==-1){var m=i.match(/([\d\.]+)e(-?)(\d+)/);m&&m[2]=="-"&&m[3]>e+1?i="0":(f=i,j=!0)}if(!j){i=(i.split(ec)[1]||"").length;C(e)&&(e=Math.min(Math.max(a.minFrac,i),
a.maxFrac));var i=Math.pow(10,e),b=Math.round(b*i)/i,b=(""+b).split(ec),i=b[0],b=b[1]||"",j=0,m=a.lgSize,k=a.gSize;if(i.length>=m+k)for(var j=i.length-m,l=0;l<j;l++)(j-l)%k===0&&l!==0&&(f+=c),f+=i.charAt(l);for(l=j;l<i.length;l++)(i.length-l)%m===0&&l!==0&&(f+=c),f+=i.charAt(l);for(;b.length<e;)b+="0";e&&e!=="0"&&(f+=d+b.substr(0,e))}h.push(g?a.negPre:a.posPre);h.push(f);h.push(g?a.negSuf:a.posSuf);return h.join("")}function nb(b,a,c){var d="";b<0&&(d="-",b=-b);for(b=""+b;b.length<a;)b="0"+b;c&&(b=
b.substr(b.length-a));return d+b}function Q(b,a,c,d){c=c||0;return function(e){e=e["get"+b]();if(c>0||e>-c)e+=c;e===0&&c==-12&&(e=12);return nb(e,a,d)}}function Qa(b,a){return function(c,d){var e=c["get"+b](),g=oa(a?"SHORT"+b:b);return d[g][e]}}function ac(b){function a(a){var b;if(b=a.match(c)){var a=new Date(0),g=0,i=0,f=b[8]?a.setUTCFullYear:a.setFullYear,h=b[8]?a.setUTCHours:a.setHours;b[9]&&(g=N(b[9]+b[10]),i=N(b[9]+b[11]));f.call(a,N(b[1]),N(b[2])-1,N(b[3]));g=N(b[4]||0)-g;i=N(b[5]||0)-i;f=
N(b[6]||0);b=Math.round(parseFloat("0."+(b[7]||0))*1E3);h.call(a,g,i,f,b)}return a}var c=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;return function(c,e){var g="",i=[],f,h,e=e||"mediumDate",e=b.DATETIME_FORMATS[e]||e;E(c)&&(c=md.test(c)?N(c):a(c));Ya(c)&&(c=new Date(c));if(!ra(c))return c;for(;e;)(h=nd.exec(e))?(i=i.concat(ka.call(h,1)),e=i.pop()):(i.push(e),e=null);n(i,function(a){f=od[a];g+=f?f(c,b.DATETIME_FORMATS):a.replace(/(^'|'$)/g,
"").replace(/''/g,"'")});return g}}function id(){return function(b){return ha(b,!0)}}function jd(){return function(b,a){if(!F(b)&&!E(b))return b;a=N(a);if(E(b))return a?a>=0?b.slice(0,a):b.slice(a,b.length):"";var c=[],d,e;a>b.length?a=b.length:a<-b.length&&(a=-b.length);a>0?(d=0,e=a):(d=b.length+a,e=b.length);for(;d<e;d++)c.push(b[d]);return c}}function cc(b){return function(a,c,d){function e(a,b){return ua(b)?function(b,c){return a(c,b)}:a}if(!F(a))return a;if(!c)return a;for(var c=F(c)?c:[c],c=
Za(c,function(a){var c=!1,d=a||qa;if(E(a)){if(a.charAt(0)=="+"||a.charAt(0)=="-")c=a.charAt(0)=="-",a=a.substring(1);d=b(a)}return e(function(a,b){var c;c=d(a);var e=d(b),f=typeof c,g=typeof e;f==g?(f=="string"&&(c=c.toLowerCase()),f=="string"&&(e=e.toLowerCase()),c=c===e?0:c<e?-1:1):c=f<g?-1:1;return c},c)}),g=[],i=0;i<a.length;i++)g.push(a[i]);return g.sort(e(function(a,b){for(var d=0;d<c.length;d++){var e=c[d](a,b);if(e!==0)return e}return 0},d))}}function aa(b){H(b)&&(b={link:b});b.restrict=b.restrict||
"AC";return S(b)}function fc(b,a){function c(a,c){c=c?"-"+bb(c,"-"):"";b.removeClass((a?Ra:Sa)+c).addClass((a?Sa:Ra)+c)}var d=this,e=b.parent().controller("form")||Ta,g=0,i=d.$error={},f=[];d.$name=a.name;d.$dirty=!1;d.$pristine=!0;d.$valid=!0;d.$invalid=!1;e.$addControl(d);b.addClass(pa);c(!0);d.$addControl=function(a){f.push(a);a.$name&&!d.hasOwnProperty(a.$name)&&(d[a.$name]=a)};d.$removeControl=function(a){a.$name&&d[a.$name]===a&&delete d[a.$name];n(i,function(b,c){d.$setValidity(c,!0,a)});ta(f,
a)};d.$setValidity=function(a,b,f){var k=i[a];if(b){if(k&&(ta(k,f),!k.length)){g--;if(!g)c(b),d.$valid=!0,d.$invalid=!1;i[a]=!1;c(!0,a);e.$setValidity(a,!0,d)}}else{g||c(b);if(k){if(Ga(k,f)!=-1)return}else i[a]=k=[],g++,c(!1,a),e.$setValidity(a,!1,d);k.push(f);d.$valid=!1;d.$invalid=!0}};d.$setDirty=function(){b.removeClass(pa).addClass(Ua);d.$dirty=!0;d.$pristine=!1;e.$setDirty()};d.$setPristine=function(){b.removeClass(Ua).addClass(pa);d.$dirty=!1;d.$pristine=!0;n(f,function(a){a.$setPristine()})}}
function X(b){return C(b)||b===""||b===null||b!==b}function Va(b,a,c,d,e,g){var i=function(){var e=a.val();if(ua(c.ngTrim||"T"))e=U(e);d.$viewValue!==e&&b.$apply(function(){d.$setViewValue(e)})};if(e.hasEvent("input"))a.bind("input",i);else{var f,h=function(){f||(f=g.defer(function(){i();f=null}))};a.bind("keydown",function(a){a=a.keyCode;a===91||15<a&&a<19||37<=a&&a<=40||h()});a.bind("change",i);e.hasEvent("paste")&&a.bind("paste cut",h)}d.$render=function(){a.val(X(d.$viewValue)?"":d.$viewValue)};
var j=c.ngPattern,m=function(a,b){return X(b)||a.test(b)?(d.$setValidity("pattern",!0),b):(d.$setValidity("pattern",!1),p)};j&&((e=j.match(/^\/(.*)\/([gim]*)$/))?(j=RegExp(e[1],e[2]),e=function(a){return m(j,a)}):e=function(a){var c=b.$eval(j);if(!c||!c.test)throw Error("Expected "+j+" to be a RegExp but was "+c);return m(c,a)},d.$formatters.push(e),d.$parsers.push(e));if(c.ngMinlength){var k=N(c.ngMinlength),e=function(a){return!X(a)&&a.length<k?(d.$setValidity("minlength",!1),p):(d.$setValidity("minlength",
!0),a)};d.$parsers.push(e);d.$formatters.push(e)}if(c.ngMaxlength){var l=N(c.ngMaxlength),e=function(a){return!X(a)&&a.length>l?(d.$setValidity("maxlength",!1),p):(d.$setValidity("maxlength",!0),a)};d.$parsers.push(e);d.$formatters.push(e)}}function ob(b,a){b="ngClass"+b;return aa(function(c,d,e){function g(b){if(a===!0||c.$index%2===a)h&&!ia(b,h)&&i(h),f(b);h=V(b)}function i(a){L(a)&&!F(a)&&(a=Za(a,function(a,b){if(a)return b}));d.removeClass(F(a)?a.join(" "):a)}function f(a){L(a)&&!F(a)&&(a=Za(a,
function(a,b){if(a)return b}));a&&d.addClass(F(a)?a.join(" "):a)}var h=p;c.$watch(e[b],g,!0);e.$observe("class",function(){var a=c.$eval(e[b]);g(a,a)});b!=="ngClass"&&c.$watch("$index",function(d,g){var h=d&1;h!==g&1&&(h===a?f(c.$eval(e[b])):i(c.$eval(e[b])))})})}var I=function(b){return E(b)?b.toLowerCase():b},oa=function(b){return E(b)?b.toUpperCase():b},Z=N((/msie (\d+)/.exec(I(navigator.userAgent))||[])[1]),w,ga,ka=[].slice,Wa=[].push,Ea=Object.prototype.toString,mc=M.angular,Ha=M.angular||(M.angular=
{}),Aa,hb,ba=["0","0","0"];q.$inject=[];qa.$inject=[];hb=Z<9?function(b){b=b.nodeName?b:b[0];return b.scopeName&&b.scopeName!="HTML"?oa(b.scopeName+":"+b.nodeName):b.nodeName}:function(b){return b.nodeName?b.nodeName:b[0].nodeName};var sc=/[A-Z]/g,pd={full:"1.1.5",major:1,minor:1,dot:5,codeName:"triangle-squarification"},Ka=R.cache={},Ja=R.expando="ng-"+(new Date).getTime(),wc=1,gc=M.document.addEventListener?function(b,a,c){b.addEventListener(a,c,!1)}:function(b,a,c){b.attachEvent("on"+a,c)},gb=
M.document.removeEventListener?function(b,a,c){b.removeEventListener(a,c,!1)}:function(b,a,c){b.detachEvent("on"+a,c)},uc=/([\:\-\_]+(.))/g,vc=/^moz([A-Z])/,Ba=R.prototype={ready:function(b){function a(){c||(c=!0,b())}var c=!1;T.readyState==="complete"?setTimeout(a):(this.bind("DOMContentLoaded",a),R(M).bind("load",a))},toString:function(){var b=[];n(this,function(a){b.push(""+a)});return"["+b.join(", ")+"]"},eq:function(b){return b>=0?w(this[b]):w(this[this.length+b])},length:0,push:Wa,sort:[].sort,
splice:[].splice},Na={};n("multiple,selected,checked,disabled,readOnly,required,open".split(","),function(b){Na[I(b)]=b});var Gb={};n("input,select,option,textarea,button,form,details".split(","),function(b){Gb[oa(b)]=!0});n({data:Bb,inheritedData:Ma,scope:function(b){return Ma(b,"$scope")},controller:Eb,injector:function(b){return Ma(b,"$injector")},removeAttr:function(b,a){b.removeAttribute(a)},hasClass:La,css:function(b,a,c){a=Ia(a);if(B(c))b.style[a]=c;else{var d;Z<=8&&(d=b.currentStyle&&b.currentStyle[a],
d===""&&(d="auto"));d=d||b.style[a];Z<=8&&(d=d===""?p:d);return d}},attr:function(b,a,c){var d=I(a);if(Na[d])if(B(c))c?(b[a]=!0,b.setAttribute(a,d)):(b[a]=!1,b.removeAttribute(d));else return b[a]||(b.attributes.getNamedItem(a)||q).specified?d:p;else if(B(c))b.setAttribute(a,c);else if(b.getAttribute)return b=b.getAttribute(a,2),b===null?p:b},prop:function(b,a,c){if(B(c))b[a]=c;else return b[a]},text:t(Z<9?function(b,a){if(b.nodeType==1){if(C(a))return b.innerText;b.innerText=a}else{if(C(a))return b.nodeValue;
b.nodeValue=a}}:function(b,a){if(C(a))return b.textContent;b.textContent=a},{$dv:""}),val:function(b,a){if(C(a))return b.value;b.value=a},html:function(b,a){if(C(a))return b.innerHTML;for(var c=0,d=b.childNodes;c<d.length;c++)ya(d[c]);b.innerHTML=a}},function(b,a){R.prototype[a]=function(a,d){var e,g;if((b.length==2&&b!==La&&b!==Eb?a:d)===p)if(L(a)){for(e=0;e<this.length;e++)if(b===Bb)b(this[e],a);else for(g in a)b(this[e],g,a[g]);return this}else{if(this.length)return b(this[0],a,d)}else{for(e=0;e<
this.length;e++)b(this[e],a,d);return this}return b.$dv}});n({removeData:zb,dealoc:ya,bind:function a(c,d,e){var g=ca(c,"events"),i=ca(c,"handle");g||ca(c,"events",g={});i||ca(c,"handle",i=xc(c,g));n(d.split(" "),function(d){var h=g[d];if(!h){if(d=="mouseenter"||d=="mouseleave"){var j=T.body.contains||T.body.compareDocumentPosition?function(a,c){var d=a.nodeType===9?a.documentElement:a,e=c&&c.parentNode;return a===e||!(!e||!(e.nodeType===1&&(d.contains?d.contains(e):a.compareDocumentPosition&&a.compareDocumentPosition(e)&
16)))}:function(a,c){if(c)for(;c=c.parentNode;)if(c===a)return!0;return!1};g[d]=[];a(c,{mouseleave:"mouseout",mouseenter:"mouseover"}[d],function(a){var c=a.relatedTarget;(!c||c!==this&&!j(this,c))&&i(a,d)})}else gc(c,d,i),g[d]=[];h=g[d]}h.push(e)})},unbind:Ab,replaceWith:function(a,c){var d,e=a.parentNode;ya(a);n(new R(c),function(c){d?e.insertBefore(c,d.nextSibling):e.replaceChild(c,a);d=c})},children:function(a){var c=[];n(a.childNodes,function(a){a.nodeType===1&&c.push(a)});return c},contents:function(a){return a.childNodes||
[]},append:function(a,c){n(new R(c),function(c){(a.nodeType===1||a.nodeType===11)&&a.appendChild(c)})},prepend:function(a,c){if(a.nodeType===1){var d=a.firstChild;n(new R(c),function(c){d?a.insertBefore(c,d):(a.appendChild(c),d=c)})}},wrap:function(a,c){var c=w(c)[0],d=a.parentNode;d&&d.replaceChild(c,a);c.appendChild(a)},remove:function(a){ya(a);var c=a.parentNode;c&&c.removeChild(a)},after:function(a,c){var d=a,e=a.parentNode;n(new R(c),function(a){e.insertBefore(a,d.nextSibling);d=a})},addClass:Db,
removeClass:Cb,toggleClass:function(a,c,d){C(d)&&(d=!La(a,c));(d?Db:Cb)(a,c)},parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},next:function(a){if(a.nextElementSibling)return a.nextElementSibling;for(a=a.nextSibling;a!=null&&a.nodeType!==1;)a=a.nextSibling;return a},find:function(a,c){return a.getElementsByTagName(c)},clone:fb,triggerHandler:function(a,c){var d=(ca(a,"events")||{})[c];n(d,function(c){c.call(a,{preventDefault:q})})}},function(a,c){R.prototype[c]=function(c,e){for(var g,
i=0;i<this.length;i++)g==p?(g=a(this[i],c,e),g!==p&&(g=w(g))):eb(g,a(this[i],c,e));return g==p?this:g}});za.prototype={put:function(a,c){this[la(a)]=c},get:function(a){return this[la(a)]},remove:function(a){var c=this[a=la(a)];delete this[a];return c}};var zc=/^function\s*[^\(]*\(\s*([^\)]*)\)/m,Ac=/,/,Bc=/^\s*(_?)(\S+?)\1\s*$/,yc=/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;Ib.$inject=["$provide"];var qd=function(){var a="$ngAnimateController",c={running:!0};this.$get=["$animation","$window","$sniffer","$rootElement",
"$rootScope",function(d,e,g,i){i.data(a,c);i=function(c,i){function j(j,k,o){return function(m,r,p){function x(a){var c=0,a=E(a)?a.split(/\s*,\s*/):[];n(a,function(a){c=Math.max(parseFloat(a)||0,c)});return c}function t(){m.addClass(K);if($)$(m,v,P);else if(H(e.getComputedStyle)){var a=g.vendorPrefix+"Animation",c=g.vendorPrefix+"Transition",d=0;n(m,function(f){if(f.nodeType==1){var g="transition",i=c,j=1,h=e.getComputedStyle(f)||{};if(parseFloat(h.animationDuration)>0||parseFloat(h[a+"Duration"])>
0)g="animation",i=a,j=Math.max(parseInt(h[g+"IterationCount"])||0,parseInt(h[i+"IterationCount"])||0,j);f=Math.max(x(h[g+"Delay"]),x(h[i+"Delay"]));g=Math.max(x(h[g+"Duration"]),x(h[i+"Duration"]));d=Math.max(f+j*g,d)}});e.setTimeout(v,d*1E3)}else v()}function v(){if(!v.run)v.run=!0,o(m,r,p),m.removeClass(w),m.removeClass(K),m.removeData(a)}var A=c.$eval(i.ngAnimate),w=A?L(A)?A[j]:A+"-"+j:"",D=d(w),A=D&&D.setup,$=D&&D.start,D=D&&D.cancel;if(w){var K=w+"-active";r||(r=p?p.parent():m.parent());if(!g.transitions&&
!A&&!$||(r.inheritedData(a)||q).running)k(m,r,p),o(m,r,p);else{var O=m.data(a)||{};O.running&&((D||q)(m),O.done());m.data(a,{running:!0,done:v});m.addClass(w);k(m,r,p);if(m.length==0)return v();var P=(A||q)(m);e.setTimeout(t,1)}}else k(m,r,p),o(m,r,p)}}function m(a,c,d){d?d.after(a):c.append(a)}var k={};k.enter=j("enter",m,q);k.leave=j("leave",q,function(a){a.remove()});k.move=j("move",function(a,c,d){m(a,c,d)},q);k.show=j("show",function(a){a.css("display","")},q);k.hide=j("hide",q,function(a){a.css("display",
"none")});k.animate=function(a,c){j(a,q,q)(c)};return k};i.enabled=function(a){if(arguments.length)c.running=!a;return!c.running};return i}]},Kb="Non-assignable model expression: ";Jb.$inject=["$provide"];var Ic=/^(x[\:\-_]|data[\:\-_])/i,jb=/^([^:]+):\/\/(\w+:{0,1}\w*@)?(\{?[\w\.-]*\}?)(:([0-9]+))?(\/[^\?#]*)?(\?([^#]*))?(#(.*))?$/,Pb=/^([^\?#]*)(\?([^#]*))?(#(.*))?$/,Oa={http:80,https:443,ftp:21};Rb.prototype=lb.prototype=Qb.prototype={$$replace:!1,absUrl:Pa("$$absUrl"),url:function(a,c){if(C(a))return this.$$url;
var d=Pb.exec(a);d[1]&&this.path(decodeURIComponent(d[1]));if(d[2]||d[1])this.search(d[3]||"");this.hash(d[5]||"",c);return this},protocol:Pa("$$protocol"),host:Pa("$$host"),port:Pa("$$port"),path:Sb("$$path",function(a){return a.charAt(0)=="/"?a:"/"+a}),search:function(a,c){if(C(a))return this.$$search;B(c)?c===null?delete this.$$search[a]:this.$$search[a]=c:this.$$search=E(a)?vb(a):a;this.$$compose();return this},hash:Sb("$$hash",qa),replace:function(){this.$$replace=!0;return this}};var Da={"null":function(){return null},
"true":function(){return!0},"false":function(){return!1},undefined:q,"+":function(a,c,d,e){d=d(a,c);e=e(a,c);return B(d)?B(e)?d+e:d:B(e)?e:p},"-":function(a,c,d,e){d=d(a,c);e=e(a,c);return(B(d)?d:0)-(B(e)?e:0)},"*":function(a,c,d,e){return d(a,c)*e(a,c)},"/":function(a,c,d,e){return d(a,c)/e(a,c)},"%":function(a,c,d,e){return d(a,c)%e(a,c)},"^":function(a,c,d,e){return d(a,c)^e(a,c)},"=":q,"===":function(a,c,d,e){return d(a,c)===e(a,c)},"!==":function(a,c,d,e){return d(a,c)!==e(a,c)},"==":function(a,
c,d,e){return d(a,c)==e(a,c)},"!=":function(a,c,d,e){return d(a,c)!=e(a,c)},"<":function(a,c,d,e){return d(a,c)<e(a,c)},">":function(a,c,d,e){return d(a,c)>e(a,c)},"<=":function(a,c,d,e){return d(a,c)<=e(a,c)},">=":function(a,c,d,e){return d(a,c)>=e(a,c)},"&&":function(a,c,d,e){return d(a,c)&&e(a,c)},"||":function(a,c,d,e){return d(a,c)||e(a,c)},"&":function(a,c,d,e){return d(a,c)&e(a,c)},"|":function(a,c,d,e){return e(a,c)(a,c,d(a,c))},"!":function(a,c,d){return!d(a,c)}},Qc={n:"\n",f:"\u000c",r:"\r",
t:"\t",v:"\u000b","'":"'",'"':'"'},mb={},ad=/^(([^:]+):)?\/\/(\w+:{0,1}\w*@)?([\w\.-]*)?(:([0-9]+))?(.*)$/,ed=M.XMLHttpRequest||function(){try{return new ActiveXObject("Msxml2.XMLHTTP.6.0")}catch(a){}try{return new ActiveXObject("Msxml2.XMLHTTP.3.0")}catch(c){}try{return new ActiveXObject("Msxml2.XMLHTTP")}catch(d){}throw Error("This browser does not support XMLHttpRequest.");};Zb.$inject=["$provide"];$b.$inject=["$locale"];bc.$inject=["$locale"];var ec=".",od={yyyy:Q("FullYear",4),yy:Q("FullYear",
2,0,!0),y:Q("FullYear",1),MMMM:Qa("Month"),MMM:Qa("Month",!0),MM:Q("Month",2,1),M:Q("Month",1,1),dd:Q("Date",2),d:Q("Date",1),HH:Q("Hours",2),H:Q("Hours",1),hh:Q("Hours",2,-12),h:Q("Hours",1,-12),mm:Q("Minutes",2),m:Q("Minutes",1),ss:Q("Seconds",2),s:Q("Seconds",1),sss:Q("Milliseconds",3),EEEE:Qa("Day"),EEE:Qa("Day",!0),a:function(a,c){return a.getHours()<12?c.AMPMS[0]:c.AMPMS[1]},Z:function(a){var a=-1*a.getTimezoneOffset(),c=a>=0?"+":"";c+=nb(Math[a>0?"floor":"ceil"](a/60),2)+nb(Math.abs(a%60),
2);return c}},nd=/((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/,md=/^\d+$/;ac.$inject=["$locale"];var kd=S(I),ld=S(oa);cc.$inject=["$parse"];var rd=S({restrict:"E",compile:function(a,c){Z<=8&&(!c.href&&!c.name&&c.$set("href",""),a.append(T.createComment("IE fix")));return function(a,c){c.bind("click",function(a){c.attr("href")||a.preventDefault()})}}}),pb={};n(Na,function(a,c){var d=da("ng-"+c);pb[d]=function(){return{priority:100,compile:function(){return function(a,
g,i){a.$watch(i[d],function(a){i.$set(c,!!a)})}}}}});n(["src","srcset","href"],function(a){var c=da("ng-"+a);pb[c]=function(){return{priority:99,link:function(d,e,g){g.$observe(c,function(c){c&&(g.$set(a,c),Z&&e.prop(a,g[a]))})}}}});var Ta={$addControl:q,$removeControl:q,$setValidity:q,$setDirty:q,$setPristine:q};fc.$inject=["$element","$attrs","$scope"];var Wa=function(a){return["$timeout",function(c){var d={name:"form",restrict:"E",controller:fc,compile:function(){return{pre:function(a,d,i,f){if(!i.action){var h=
function(a){a.preventDefault?a.preventDefault():a.returnValue=!1};gc(d[0],"submit",h);d.bind("$destroy",function(){c(function(){gb(d[0],"submit",h)},0,!1)})}var j=d.parent().controller("form"),m=i.name||i.ngForm;m&&(a[m]=f);j&&d.bind("$destroy",function(){j.$removeControl(f);m&&(a[m]=p);t(f,Ta)})}}}};return a?t(V(d),{restrict:"EAC"}):d}]},sd=Wa(),td=Wa(!0),ud=/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/,vd=/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/,
wd=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/,hc={text:Va,number:function(a,c,d,e,g,i){Va(a,c,d,e,g,i);e.$parsers.push(function(a){var c=X(a);return c||wd.test(a)?(e.$setValidity("number",!0),a===""?null:c?a:parseFloat(a)):(e.$setValidity("number",!1),p)});e.$formatters.push(function(a){return X(a)?"":""+a});if(d.min){var f=parseFloat(d.min),a=function(a){return!X(a)&&a<f?(e.$setValidity("min",!1),p):(e.$setValidity("min",!0),a)};e.$parsers.push(a);e.$formatters.push(a)}if(d.max){var h=parseFloat(d.max),
d=function(a){return!X(a)&&a>h?(e.$setValidity("max",!1),p):(e.$setValidity("max",!0),a)};e.$parsers.push(d);e.$formatters.push(d)}e.$formatters.push(function(a){return X(a)||Ya(a)?(e.$setValidity("number",!0),a):(e.$setValidity("number",!1),p)})},url:function(a,c,d,e,g,i){Va(a,c,d,e,g,i);a=function(a){return X(a)||ud.test(a)?(e.$setValidity("url",!0),a):(e.$setValidity("url",!1),p)};e.$formatters.push(a);e.$parsers.push(a)},email:function(a,c,d,e,g,i){Va(a,c,d,e,g,i);a=function(a){return X(a)||vd.test(a)?
(e.$setValidity("email",!0),a):(e.$setValidity("email",!1),p)};e.$formatters.push(a);e.$parsers.push(a)},radio:function(a,c,d,e){C(d.name)&&c.attr("name",Fa());c.bind("click",function(){c[0].checked&&a.$apply(function(){e.$setViewValue(d.value)})});e.$render=function(){c[0].checked=d.value==e.$viewValue};d.$observe("value",e.$render)},checkbox:function(a,c,d,e){var g=d.ngTrueValue,i=d.ngFalseValue;E(g)||(g=!0);E(i)||(i=!1);c.bind("click",function(){a.$apply(function(){e.$setViewValue(c[0].checked)})});
e.$render=function(){c[0].checked=e.$viewValue};e.$formatters.push(function(a){return a===g});e.$parsers.push(function(a){return a?g:i})},hidden:q,button:q,submit:q,reset:q},ic=["$browser","$sniffer",function(a,c){return{restrict:"E",require:"?ngModel",link:function(d,e,g,i){i&&(hc[I(g.type)]||hc.text)(d,e,g,i,c,a)}}}],Sa="ng-valid",Ra="ng-invalid",pa="ng-pristine",Ua="ng-dirty",xd=["$scope","$exceptionHandler","$attrs","$element","$parse",function(a,c,d,e,g){function i(a,c){c=c?"-"+bb(c,"-"):"";
e.removeClass((a?Ra:Sa)+c).addClass((a?Sa:Ra)+c)}this.$modelValue=this.$viewValue=Number.NaN;this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$pristine=!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$name=d.name;var f=g(d.ngModel),h=f.assign;if(!h)throw Error(Kb+d.ngModel+" ("+va(e)+")");this.$render=q;var j=e.inheritedData("$formController")||Ta,m=0,k=this.$error={};e.addClass(pa);i(!0);this.$setValidity=function(a,c){if(k[a]!==!c){if(c){if(k[a]&&m--,!m)i(!0),this.$valid=
!0,this.$invalid=!1}else i(!1),this.$invalid=!0,this.$valid=!1,m++;k[a]=!c;i(c,a);j.$setValidity(a,c,this)}};this.$setPristine=function(){this.$dirty=!1;this.$pristine=!0;e.removeClass(Ua).addClass(pa)};this.$setViewValue=function(d){this.$viewValue=d;if(this.$pristine)this.$dirty=!0,this.$pristine=!1,e.removeClass(pa).addClass(Ua),j.$setDirty();n(this.$parsers,function(a){d=a(d)});if(this.$modelValue!==d)this.$modelValue=d,h(a,d),n(this.$viewChangeListeners,function(a){try{a()}catch(d){c(d)}})};
var l=this;a.$watch(function(){var c=f(a);if(l.$modelValue!==c){var d=l.$formatters,e=d.length;for(l.$modelValue=c;e--;)c=d[e](c);if(l.$viewValue!==c)l.$viewValue=c,l.$render()}})}],yd=function(){return{require:["ngModel","^?form"],controller:xd,link:function(a,c,d,e){var g=e[0],i=e[1]||Ta;i.$addControl(g);c.bind("$destroy",function(){i.$removeControl(g)})}}},zd=S({require:"ngModel",link:function(a,c,d,e){e.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),jc=function(){return{require:"?ngModel",
link:function(a,c,d,e){if(e){d.required=!0;var g=function(a){if(d.required&&(X(a)||a===!1))e.$setValidity("required",!1);else return e.$setValidity("required",!0),a};e.$formatters.push(g);e.$parsers.unshift(g);d.$observe("required",function(){g(e.$viewValue)})}}}},Ad=function(){return{require:"ngModel",link:function(a,c,d,e){var g=(a=/\/(.*)\//.exec(d.ngList))&&RegExp(a[1])||d.ngList||",";e.$parsers.push(function(a){var c=[];a&&n(a.split(g),function(a){a&&c.push(U(a))});return c});e.$formatters.push(function(a){return F(a)?
a.join(", "):p})}}},Bd=/^(true|false|\d+)$/,Cd=function(){return{priority:100,compile:function(a,c){return Bd.test(c.ngValue)?function(a,c,g){g.$set("value",a.$eval(g.ngValue))}:function(a,c,g){a.$watch(g.ngValue,function(a){g.$set("value",a,!1)})}}}},Dd=aa(function(a,c,d){c.addClass("ng-binding").data("$binding",d.ngBind);a.$watch(d.ngBind,function(a){c.text(a==p?"":a)})}),Ed=["$interpolate",function(a){return function(c,d,e){c=a(d.attr(e.$attr.ngBindTemplate));d.addClass("ng-binding").data("$binding",
c);e.$observe("ngBindTemplate",function(a){d.text(a)})}}],Fd=[function(){return function(a,c,d){c.addClass("ng-binding").data("$binding",d.ngBindHtmlUnsafe);a.$watch(d.ngBindHtmlUnsafe,function(a){c.html(a||"")})}}],Gd=ob("",!0),Hd=ob("Odd",0),Id=ob("Even",1),Jd=aa({compile:function(a,c){c.$set("ngCloak",p);a.removeClass("ng-cloak")}}),Kd=[function(){return{scope:!0,controller:"@"}}],Ld=["$sniffer",function(a){return{priority:1E3,compile:function(){a.csp=!0}}}],kc={};n("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress".split(" "),
function(a){var c=da("ng-"+a);kc[c]=["$parse",function(d){return function(e,g,i){var f=d(i[c]);g.bind(I(a),function(a){e.$apply(function(){f(e,{$event:a})})})}}]});var Md=aa(function(a,c,d){c.bind("submit",function(){a.$apply(d.ngSubmit)})}),Nd=["$animator",function(a){return{transclude:"element",priority:1E3,terminal:!0,restrict:"A",compile:function(c,d,e){return function(c,d,f){var h=a(c,f),j,m;c.$watch(f.ngIf,function(a){j&&(h.leave(j),j=p);m&&(m.$destroy(),m=p);ua(a)&&(m=c.$new(),e(m,function(a){j=
a;h.enter(a,d.parent(),d)}))})}}}}],Od=["$http","$templateCache","$anchorScroll","$compile","$animator",function(a,c,d,e,g){return{restrict:"ECA",terminal:!0,compile:function(i,f){var h=f.ngInclude||f.src,j=f.onload||"",m=f.autoscroll;return function(f,i,n){var o=g(f,n),p=0,r,t=function(){r&&(r.$destroy(),r=null);o.leave(i.contents(),i)};f.$watch(h,function(g){var h=++p;g?(a.get(g,{cache:c}).success(function(a){h===p&&(r&&r.$destroy(),r=f.$new(),o.leave(i.contents(),i),a=w("<div/>").html(a).contents(),
o.enter(a,i),e(a)(r),B(m)&&(!m||f.$eval(m))&&d(),r.$emit("$includeContentLoaded"),f.$eval(j))}).error(function(){h===p&&t()}),f.$emit("$includeContentRequested")):t()})}}}}],Pd=aa({compile:function(){return{pre:function(a,c,d){a.$eval(d.ngInit)}}}}),Qd=aa({terminal:!0,priority:1E3}),Rd=["$locale","$interpolate",function(a,c){var d=/{}/g;return{restrict:"EA",link:function(e,g,i){var f=i.count,h=g.attr(i.$attr.when),j=i.offset||0,m=e.$eval(h),k={},l=c.startSymbol(),p=c.endSymbol();n(m,function(a,e){k[e]=
c(a.replace(d,l+f+"-"+j+p))});e.$watch(function(){var c=parseFloat(e.$eval(f));return isNaN(c)?"":(c in m||(c=a.pluralCat(c-j)),k[c](e,g,!0))},function(a){g.text(a)})}}}],Sd=["$parse","$animator",function(a,c){return{transclude:"element",priority:1E3,terminal:!0,compile:function(d,e,g){return function(d,e,h){var j=c(d,h),m=h.ngRepeat,k=m.match(/^\s*(.+)\s+in\s+(.*?)\s*(\s+track\s+by\s+(.+)\s*)?$/),l,p,o,z,r,t={$id:la};if(!k)throw Error("Expected ngRepeat in form of '_item_ in _collection_[ track by _id_]' but got '"+
m+"'.");h=k[1];o=k[2];(k=k[4])?(l=a(k),p=function(a,c,e){r&&(t[r]=a);t[z]=c;t.$index=e;return l(d,t)}):p=function(a,c){return la(c)};k=h.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);if(!k)throw Error("'item' in 'item in collection' should be identifier or (key, value) but got '"+h+"'.");z=k[3]||k[1];r=k[2];var x={};d.$watchCollection(o,function(a){var c,h,k=e,l,o={},t,q,w,s,B,y,C=[];if(Xa(a))B=a;else{B=[];for(w in a)a.hasOwnProperty(w)&&w.charAt(0)!="$"&&B.push(w);B.sort()}t=B.length;h=
C.length=B.length;for(c=0;c<h;c++)if(w=a===B?c:B[c],s=a[w],l=p(w,s,c),x.hasOwnProperty(l))y=x[l],delete x[l],o[l]=y,C[c]=y;else if(o.hasOwnProperty(l))throw n(C,function(a){a&&a.element&&(x[a.id]=a)}),Error("Duplicates in a repeater are not allowed. Repeater: "+m+" key: "+l);else C[c]={id:l},o[l]=!1;for(w in x)if(x.hasOwnProperty(w))y=x[w],j.leave(y.element),y.element[0].$$NG_REMOVED=!0,y.scope.$destroy();c=0;for(h=B.length;c<h;c++){w=a===B?c:B[c];s=a[w];y=C[c];if(y.element){q=y.scope;l=k[0];do l=
l.nextSibling;while(l&&l.$$NG_REMOVED);y.element[0]!=l&&j.move(y.element,null,k);k=y.element}else q=d.$new();q[z]=s;r&&(q[r]=w);q.$index=c;q.$first=c===0;q.$last=c===t-1;q.$middle=!(q.$first||q.$last);y.element||g(q,function(a){j.enter(a,null,k);k=a;y.scope=q;y.element=a;o[y.id]=y})}x=o})}}}}],Td=["$animator",function(a){return function(c,d,e){var g=a(c,e);c.$watch(e.ngShow,function(a){g[ua(a)?"show":"hide"](d)})}}],Ud=["$animator",function(a){return function(c,d,e){var g=a(c,e);c.$watch(e.ngHide,
function(a){g[ua(a)?"hide":"show"](d)})}}],Vd=aa(function(a,c,d){a.$watch(d.ngStyle,function(a,d){d&&a!==d&&n(d,function(a,d){c.css(d,"")});a&&c.css(a)},!0)}),Wd=["$animator",function(a){return{restrict:"EA",require:"ngSwitch",controller:["$scope",function(){this.cases={}}],link:function(c,d,e,g){var i=a(c,e),f,h,j=[];c.$watch(e.ngSwitch||e.on,function(a){for(var d=0,l=j.length;d<l;d++)j[d].$destroy(),i.leave(h[d]);h=[];j=[];if(f=g.cases["!"+a]||g.cases["?"])c.$eval(e.change),n(f,function(a){var d=
c.$new();j.push(d);a.transclude(d,function(c){var d=a.element;h.push(c);i.enter(c,d.parent(),d)})})})}}}],Xd=aa({transclude:"element",priority:500,require:"^ngSwitch",compile:function(a,c,d){return function(a,g,i,f){f.cases["!"+c.ngSwitchWhen]=f.cases["!"+c.ngSwitchWhen]||[];f.cases["!"+c.ngSwitchWhen].push({transclude:d,element:g})}}}),Yd=aa({transclude:"element",priority:500,require:"^ngSwitch",compile:function(a,c,d){return function(a,c,i,f){f.cases["?"]=f.cases["?"]||[];f.cases["?"].push({transclude:d,
element:c})}}}),Zd=aa({controller:["$transclude","$element",function(a,c){a(function(a){c.append(a)})}]}),$d=["$http","$templateCache","$route","$anchorScroll","$compile","$controller","$animator",function(a,c,d,e,g,i,f){return{restrict:"ECA",terminal:!0,link:function(a,c,m){function k(){var f=d.current&&d.current.locals,k=f&&f.$template;if(k){o.leave(c.contents(),c);l&&(l.$destroy(),l=null);k=w("<div></div>").html(k).contents();o.enter(k,c);var k=g(k),m=d.current;l=m.scope=a.$new();if(m.controller)f.$scope=
l,f=i(m.controller,f),m.controllerAs&&(l[m.controllerAs]=f),c.children().data("$ngControllerController",f);k(l);l.$emit("$viewContentLoaded");l.$eval(n);e()}else o.leave(c.contents(),c),l&&(l.$destroy(),l=null)}var l,n=m.onload||"",o=f(a,m);a.$on("$routeChangeSuccess",k);k()}}}],ae=["$templateCache",function(a){return{restrict:"E",terminal:!0,compile:function(c,d){d.type=="text/ng-template"&&a.put(d.id,c[0].text)}}}],be=S({terminal:!0}),ce=["$compile","$parse",function(a,c){var d=/^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w\d]*)|(?:\(\s*([\$\w][\$\w\d]*)\s*,\s*([\$\w][\$\w\d]*)\s*\)))\s+in\s+(.*?)(?:\s+track\s+by\s+(.*?))?$/,
e={$setViewValue:q};return{restrict:"E",require:["select","?ngModel"],controller:["$element","$scope","$attrs",function(a,c,d){var h=this,j={},m=e,k;h.databound=d.ngModel;h.init=function(a,c,d){m=a;k=d};h.addOption=function(c){j[c]=!0;m.$viewValue==c&&(a.val(c),k.parent()&&k.remove())};h.removeOption=function(a){this.hasOption(a)&&(delete j[a],m.$viewValue==a&&this.renderUnknownOption(a))};h.renderUnknownOption=function(c){c="? "+la(c)+" ?";k.val(c);a.prepend(k);a.val(c);k.prop("selected",!0)};h.hasOption=
function(a){return j.hasOwnProperty(a)};c.$on("$destroy",function(){h.renderUnknownOption=q})}],link:function(e,i,f,h){function j(a,c,d,e){d.$render=function(){var a=d.$viewValue;e.hasOption(a)?(v.parent()&&v.remove(),c.val(a),a===""&&t.prop("selected",!0)):C(a)&&t?c.val(""):e.renderUnknownOption(a)};c.bind("change",function(){a.$apply(function(){v.parent()&&v.remove();d.$setViewValue(c.val())})})}function m(a,c,d){var e;d.$render=function(){var a=new za(d.$viewValue);n(c.find("option"),function(c){c.selected=
B(a.get(c.value))})};a.$watch(function(){ia(e,d.$viewValue)||(e=V(d.$viewValue),d.$render())});c.bind("change",function(){a.$apply(function(){var a=[];n(c.find("option"),function(c){c.selected&&a.push(c.value)});d.$setViewValue(a)})})}function k(e,f,g){function i(){var a={"":[]},c=[""],d,h,q,v,s;q=g.$modelValue;v=u(e)||[];var z=l?qb(v):v,B,y,A;y={};s=!1;var C,D;if(o)if(t&&F(q)){s=new za([]);for(h=0;h<q.length;h++)y[k]=q[h],s.put(t(e,y),q[h])}else s=new za(q);for(A=0;B=z.length,A<B;A++){y[k]=v[l?y[l]=
z[A]:A];d=m(e,y)||"";if(!(h=a[d]))h=a[d]=[],c.push(d);o?d=s.remove(t?t(e,y):n(e,y))!=p:(t?(d={},d[k]=q,d=t(e,d)===t(e,y)):d=q===n(e,y),s=s||d);C=j(e,y);C=C===p?"":C;h.push({id:t?t(e,y):l?z[A]:A,label:C,selected:d})}o||(r||q===null?a[""].unshift({id:"",label:"",selected:!s}):s||a[""].unshift({id:"?",label:"",selected:!0}));y=0;for(z=c.length;y<z;y++){d=c[y];h=a[d];if(w.length<=y)q={element:E.clone().attr("label",d),label:h.label},v=[q],w.push(v),f.append(q.element);else if(v=w[y],q=v[0],q.label!=d)q.element.attr("label",
q.label=d);C=null;A=0;for(B=h.length;A<B;A++)if(d=h[A],s=v[A+1]){C=s.element;if(s.label!==d.label)C.text(s.label=d.label);if(s.id!==d.id)C.val(s.id=d.id);if(C[0].selected!==d.selected)C.prop("selected",s.selected=d.selected)}else d.id===""&&r?D=r:(D=x.clone()).val(d.id).attr("selected",d.selected).text(d.label),v.push({element:D,label:d.label,id:d.id,selected:d.selected}),C?C.after(D):q.element.append(D),C=D;for(A++;v.length>A;)v.pop().element.remove()}for(;w.length>y;)w.pop()[0].element.remove()}
var h;if(!(h=q.match(d)))throw Error("Expected ngOptions in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_ (track by _expr_)?' but got '"+q+"'.");var j=c(h[2]||h[1]),k=h[4]||h[6],l=h[5],m=c(h[3]||""),n=c(h[2]?h[1]:k),u=c(h[7]),t=h[8]?c(h[8]):null,w=[[{element:f,label:""}]];r&&(a(r)(e),r.removeClass("ng-scope"),r.remove());f.html("");f.bind("change",function(){e.$apply(function(){var a,c=u(e)||[],d={},h,i,j,m,q,r;if(o){i=[];m=0;for(r=w.length;m<r;m++){a=w[m];j=1;for(q=a.length;j<
q;j++)if((h=a[j].element)[0].selected){h=h.val();l&&(d[l]=h);if(t)for(var s=0;s<c.length;s++){if(d[k]=c[s],t(e,d)==h)break}else d[k]=c[h];i.push(n(e,d))}}}else if(h=f.val(),h=="?")i=p;else if(h=="")i=null;else if(t)for(s=0;s<c.length;s++){if(d[k]=c[s],t(e,d)==h){i=n(e,d);break}}else d[k]=c[h],l&&(d[l]=h),i=n(e,d);g.$setViewValue(i)})});g.$render=i;e.$watch(i)}if(h[1]){for(var l=h[0],u=h[1],o=f.multiple,q=f.ngOptions,r=!1,t,x=w(T.createElement("option")),E=w(T.createElement("optgroup")),v=x.clone(),
h=0,A=i.children(),G=A.length;h<G;h++)if(A[h].value==""){t=r=A.eq(h);break}l.init(u,r,v);if(o&&(f.required||f.ngRequired)){var D=function(a){u.$setValidity("required",!f.required||a&&a.length);return a};u.$parsers.push(D);u.$formatters.unshift(D);f.$observe("required",function(){D(u.$viewValue)})}q?k(e,i,u):o?m(e,i,u):j(e,i,u,l)}}}}],de=["$interpolate",function(a){var c={addOption:q,removeOption:q};return{restrict:"E",priority:100,compile:function(d,e){if(C(e.value)){var g=a(d.text(),!0);g||e.$set("value",
d.text())}return function(a,d,e){var j=d.parent(),m=j.data("$selectController")||j.parent().data("$selectController");m&&m.databound?d.prop("selected",!1):m=c;g?a.$watch(g,function(a,c){e.$set("value",a);a!==c&&m.removeOption(c);m.addOption(a)}):m.addOption(e.value);d.bind("$destroy",function(){m.removeOption(e.value)})}}}}],ee=S({restrict:"E",terminal:!0});(ga=M.jQuery)?(w=ga,t(ga.fn,{scope:Ba.scope,controller:Ba.controller,injector:Ba.injector,inheritedData:Ba.inheritedData}),db("remove",!0),db("empty"),
db("html")):w=R;Ha.element=w;(function(a){t(a,{bootstrap:xb,copy:V,extend:t,equals:ia,element:w,forEach:n,injector:yb,noop:q,bind:$a,toJson:ha,fromJson:ub,identity:qa,isUndefined:C,isDefined:B,isString:E,isFunction:H,isObject:L,isNumber:Ya,isElement:oc,isArray:F,version:pd,isDate:ra,lowercase:I,uppercase:oa,callbacks:{counter:0},noConflict:lc});Aa=tc(M);try{Aa("ngLocale")}catch(c){Aa("ngLocale",[]).provider("$locale",fd)}Aa("ng",["ngLocale"],["$provide",function(a){a.provider("$compile",Jb).directive({a:rd,
input:ic,textarea:ic,form:sd,script:ae,select:ce,style:ee,option:de,ngBind:Dd,ngBindHtmlUnsafe:Fd,ngBindTemplate:Ed,ngClass:Gd,ngClassEven:Id,ngClassOdd:Hd,ngCsp:Ld,ngCloak:Jd,ngController:Kd,ngForm:td,ngHide:Ud,ngIf:Nd,ngInclude:Od,ngInit:Pd,ngNonBindable:Qd,ngPluralize:Rd,ngRepeat:Sd,ngShow:Td,ngSubmit:Md,ngStyle:Vd,ngSwitch:Wd,ngSwitchWhen:Xd,ngSwitchDefault:Yd,ngOptions:be,ngView:$d,ngTransclude:Zd,ngModel:yd,ngList:Ad,ngChange:zd,required:jc,ngRequired:jc,ngValue:Cd}).directive(pb).directive(kc);
a.provider({$anchorScroll:Cc,$animation:Ib,$animator:qd,$browser:Ec,$cacheFactory:Fc,$controller:Jc,$document:Kc,$exceptionHandler:Lc,$filter:Zb,$interpolate:Mc,$http:bd,$httpBackend:cd,$location:Nc,$log:Oc,$parse:Sc,$route:Vc,$routeParams:Wc,$rootScope:Xc,$q:Tc,$sniffer:Yc,$templateCache:Gc,$timeout:gd,$window:Zc})}])})(Ha);w(T).ready(function(){rc(T,xb)})})(window,document);angular.element(document).find("head").append('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak{display:none;}ng\\:form{display:block;}</style>');

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
{
"app": "Sismics Docs"
}

View File

@@ -0,0 +1,18 @@
<form class="form-horizontal">
<div class="control-group">
<label class="control-label" for="inputTitle">Title</label>
<div class="controls">
<input class="input-block-level" type="text" id="inputTitle" placeholder="Title" ng-model="document.title" />
</div>
</div>
<div class="control-group">
<label class="control-label" for="inputDescription">Description</label>
<div class="controls">
<textarea class="input-block-level" rows="5" id="inputDescription" ng-model="document.description"></textarea>
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary" ng-click="edit()">{{ isEdit() ? 'Edit' : 'Add' }}</button>
<button type="submit" class="btn" ng-click="cancel()">Cancel</button>
</div>
</form>

View File

@@ -0,0 +1,26 @@
<div class="container-fluid">
<div class="row-fluid">
<div class="span5 well">
<p class="text-center">
<button class="btn btn-primary" type="button" ng-click="addDocument()">Add a document</button>
</p>
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Title</th>
<th>Creation date</th>
</tr>
</thead>
<tbody>
<tr ng-click="viewDocument(document.id)" ng-repeat="document in documents">
<td>{{ document.title }}</td>
<td>{{ document.create_date | date: 'short' }}</td>
</tr>
</tbody>
</table>
</div>
<div class="span7 well">
<div ui-view="document"></div>
</div>
</div>
</div>

View File

@@ -0,0 +1 @@
View doc

View File

@@ -0,0 +1,27 @@
<div class="row-fluid">
<div class="span4 offset4">
<h1 class="text-center">Sismics Docs</h1>
<form class="form-horizontal">
<div class="control-group">
<label class="control-label" for="inputUsername">Username</label>
<div class="controls">
<input type="text" id="inputUsername" placeholder="Username" ng-model="user.username" />
</div>
</div>
<div class="control-group">
<label class="control-label" for="inputPassword">Password</label>
<div class="controls">
<input type="password" id="inputPassword" placeholder="Password" ng-model="user.password" />
</div>
</div>
<div class="control-group">
<div class="controls">
<label class="checkbox">
<input type="checkbox" ng-model="user.remember" /> Remember me
</label>
<button type="submit" class="btn" ng-click="login()">Sign in</button>
</div>
</div>
</form>
</div>
</div>

View File

@@ -0,0 +1,5 @@
<div class="row-fluid">
<div class="span12">
<h4 class="text-center">Loading...</h4>
</div>
</div>

View File

@@ -0,0 +1,310 @@
27 Jul 2013 13:37:48,704 INFO com.sismics.util.jpa.EMF.getEntityManagerProperties(EMF.java:69) Configuring EntityManager from hibernate.properties
27 Jul 2013 13:37:49,155 INFO com.sismics.util.jpa.DbOpenHelper.open(DbOpenHelper.java:67) Opening database and executing incremental updates
27 Jul 2013 13:37:49,157 INFO com.sismics.util.jpa.DbOpenHelper.open(DbOpenHelper.java:95) Unable to get database version: Table T_CONFIG not found
27 Jul 2013 13:37:49,157 INFO com.sismics.util.jpa.DbOpenHelper.open(DbOpenHelper.java:109) Executing initial schema creation script
27 Jul 2013 13:37:49,159 ERROR com.sismics.util.jpa.DbOpenHelper.open(DbOpenHelper.java:122) Unable to complete schema update
java.lang.UnsupportedOperationException: Cannot list files for URL file:/C:/Users/Ben/workspace/bgd/docs/trunk/docs-core/target/classes/com/sismics/util/jpa/EMF$1.class
at com.sismics.util.ResourceUtil.list(ResourceUtil.java:86)
at com.sismics.util.jpa.DbOpenHelper.executeAllScript(DbOpenHelper.java:152)
at com.sismics.util.jpa.EMF$1.onCreate(EMF.java:45)
at com.sismics.util.jpa.DbOpenHelper.open(DbOpenHelper.java:110)
at com.sismics.util.jpa.EMF.<clinit>(EMF.java:55)
at com.sismics.docs.core.util.TransactionUtil.handle(TransactionUtil.java:38)
at com.sismics.util.filter.RequestContextFilter.init(RequestContextFilter.java:76)
at org.eclipse.jetty.servlet.FilterHolder.doStart(FilterHolder.java:114)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:59)
at org.eclipse.jetty.servlet.ServletHandler.initialize(ServletHandler.java:754)
at org.eclipse.jetty.servlet.ServletContextHandler.startContext(ServletContextHandler.java:258)
at org.eclipse.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1221)
at org.eclipse.jetty.server.handler.ContextHandler.doStart(ContextHandler.java:699)
at org.eclipse.jetty.webapp.WebAppContext.doStart(WebAppContext.java:454)
at org.mortbay.jetty.plugin.JettyWebAppContext.doStart(JettyWebAppContext.java:256)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:59)
at org.eclipse.jetty.server.handler.HandlerCollection.doStart(HandlerCollection.java:224)
at org.eclipse.jetty.server.handler.ContextHandlerCollection.doStart(ContextHandlerCollection.java:167)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:59)
at org.eclipse.jetty.server.handler.HandlerCollection.doStart(HandlerCollection.java:224)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:59)
at org.eclipse.jetty.server.handler.HandlerWrapper.doStart(HandlerWrapper.java:90)
at org.eclipse.jetty.server.Server.doStart(Server.java:263)
at org.mortbay.jetty.plugin.JettyServer.doStart(JettyServer.java:65)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:59)
at org.mortbay.jetty.plugin.AbstractJettyMojo.startJetty(AbstractJettyMojo.java:511)
at org.mortbay.jetty.plugin.AbstractJettyMojo.execute(AbstractJettyMojo.java:364)
at org.mortbay.jetty.plugin.JettyRunMojo.execute(JettyRunMojo.java:516)
at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildPluginManager.java:101)
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:209)
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:153)
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:145)
at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:84)
at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:59)
at org.apache.maven.lifecycle.internal.LifecycleStarter.singleThreadedBuild(LifecycleStarter.java:183)
at org.apache.maven.lifecycle.internal.LifecycleStarter.execute(LifecycleStarter.java:161)
at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:320)
at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:156)
at org.apache.maven.cli.MavenCli.execute(MavenCli.java:537)
at org.apache.maven.cli.MavenCli.doMain(MavenCli.java:196)
at org.apache.maven.cli.MavenCli.main(MavenCli.java:141)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced(Launcher.java:290)
at org.codehaus.plexus.classworlds.launcher.Launcher.launch(Launcher.java:230)
at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode(Launcher.java:409)
at org.codehaus.plexus.classworlds.launcher.Launcher.main(Launcher.java:352)
27 Jul 2013 13:37:49,162 INFO com.sismics.util.jpa.EMF.getEntityManagerProperties(EMF.java:69) Configuring EntityManager from hibernate.properties
27 Jul 2013 13:37:49,244 ERROR com.sismics.util.jpa.EMF.<clinit>(EMF.java:60) Error creating EMF
javax.persistence.PersistenceException: No Persistence provider for EntityManager named transactions-optional
at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:69)
at com.sismics.util.jpa.EMF.<clinit>(EMF.java:57)
at com.sismics.docs.core.util.TransactionUtil.handle(TransactionUtil.java:38)
at com.sismics.util.filter.RequestContextFilter.init(RequestContextFilter.java:76)
at org.eclipse.jetty.servlet.FilterHolder.doStart(FilterHolder.java:114)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:59)
at org.eclipse.jetty.servlet.ServletHandler.initialize(ServletHandler.java:754)
at org.eclipse.jetty.servlet.ServletContextHandler.startContext(ServletContextHandler.java:258)
at org.eclipse.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1221)
at org.eclipse.jetty.server.handler.ContextHandler.doStart(ContextHandler.java:699)
at org.eclipse.jetty.webapp.WebAppContext.doStart(WebAppContext.java:454)
at org.mortbay.jetty.plugin.JettyWebAppContext.doStart(JettyWebAppContext.java:256)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:59)
at org.eclipse.jetty.server.handler.HandlerCollection.doStart(HandlerCollection.java:224)
at org.eclipse.jetty.server.handler.ContextHandlerCollection.doStart(ContextHandlerCollection.java:167)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:59)
at org.eclipse.jetty.server.handler.HandlerCollection.doStart(HandlerCollection.java:224)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:59)
at org.eclipse.jetty.server.handler.HandlerWrapper.doStart(HandlerWrapper.java:90)
at org.eclipse.jetty.server.Server.doStart(Server.java:263)
at org.mortbay.jetty.plugin.JettyServer.doStart(JettyServer.java:65)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:59)
at org.mortbay.jetty.plugin.AbstractJettyMojo.startJetty(AbstractJettyMojo.java:511)
at org.mortbay.jetty.plugin.AbstractJettyMojo.execute(AbstractJettyMojo.java:364)
at org.mortbay.jetty.plugin.JettyRunMojo.execute(JettyRunMojo.java:516)
at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildPluginManager.java:101)
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:209)
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:153)
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:145)
at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:84)
at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:59)
at org.apache.maven.lifecycle.internal.LifecycleStarter.singleThreadedBuild(LifecycleStarter.java:183)
at org.apache.maven.lifecycle.internal.LifecycleStarter.execute(LifecycleStarter.java:161)
at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:320)
at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:156)
at org.apache.maven.cli.MavenCli.execute(MavenCli.java:537)
at org.apache.maven.cli.MavenCli.doMain(MavenCli.java:196)
at org.apache.maven.cli.MavenCli.main(MavenCli.java:141)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced(Launcher.java:290)
at org.codehaus.plexus.classworlds.launcher.Launcher.launch(Launcher.java:230)
at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode(Launcher.java:409)
at org.codehaus.plexus.classworlds.launcher.Launcher.main(Launcher.java:352)
27 Jul 2013 13:37:49,245 ERROR com.sismics.docs.core.util.TransactionUtil.handle(TransactionUtil.java:40) Cannot create entity manager
java.lang.NullPointerException
at com.sismics.docs.core.util.TransactionUtil.handle(TransactionUtil.java:38)
at com.sismics.util.filter.RequestContextFilter.init(RequestContextFilter.java:76)
at org.eclipse.jetty.servlet.FilterHolder.doStart(FilterHolder.java:114)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:59)
at org.eclipse.jetty.servlet.ServletHandler.initialize(ServletHandler.java:754)
at org.eclipse.jetty.servlet.ServletContextHandler.startContext(ServletContextHandler.java:258)
at org.eclipse.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1221)
at org.eclipse.jetty.server.handler.ContextHandler.doStart(ContextHandler.java:699)
at org.eclipse.jetty.webapp.WebAppContext.doStart(WebAppContext.java:454)
at org.mortbay.jetty.plugin.JettyWebAppContext.doStart(JettyWebAppContext.java:256)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:59)
at org.eclipse.jetty.server.handler.HandlerCollection.doStart(HandlerCollection.java:224)
at org.eclipse.jetty.server.handler.ContextHandlerCollection.doStart(ContextHandlerCollection.java:167)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:59)
at org.eclipse.jetty.server.handler.HandlerCollection.doStart(HandlerCollection.java:224)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:59)
at org.eclipse.jetty.server.handler.HandlerWrapper.doStart(HandlerWrapper.java:90)
at org.eclipse.jetty.server.Server.doStart(Server.java:263)
at org.mortbay.jetty.plugin.JettyServer.doStart(JettyServer.java:65)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:59)
at org.mortbay.jetty.plugin.AbstractJettyMojo.startJetty(AbstractJettyMojo.java:511)
at org.mortbay.jetty.plugin.AbstractJettyMojo.execute(AbstractJettyMojo.java:364)
at org.mortbay.jetty.plugin.JettyRunMojo.execute(JettyRunMojo.java:516)
at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildPluginManager.java:101)
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:209)
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:153)
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:145)
at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:84)
at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:59)
at org.apache.maven.lifecycle.internal.LifecycleStarter.singleThreadedBuild(LifecycleStarter.java:183)
at org.apache.maven.lifecycle.internal.LifecycleStarter.execute(LifecycleStarter.java:161)
at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:320)
at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:156)
at org.apache.maven.cli.MavenCli.execute(MavenCli.java:537)
at org.apache.maven.cli.MavenCli.doMain(MavenCli.java:196)
at org.apache.maven.cli.MavenCli.main(MavenCli.java:141)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced(Launcher.java:290)
at org.codehaus.plexus.classworlds.launcher.Launcher.launch(Launcher.java:230)
at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode(Launcher.java:409)
at org.codehaus.plexus.classworlds.launcher.Launcher.main(Launcher.java:352)
27 Jul 2013 13:43:02,167 INFO com.sismics.util.jpa.EMF.getEntityManagerProperties(EMF.java:69) Configuring EntityManager from hibernate.properties
27 Jul 2013 13:43:02,621 INFO com.sismics.util.jpa.DbOpenHelper.open(DbOpenHelper.java:67) Opening database and executing incremental updates
27 Jul 2013 13:43:02,623 INFO com.sismics.util.jpa.DbOpenHelper.open(DbOpenHelper.java:95) Unable to get database version: Table T_CONFIG not found
27 Jul 2013 13:43:02,624 INFO com.sismics.util.jpa.DbOpenHelper.open(DbOpenHelper.java:109) Executing initial schema creation script
27 Jul 2013 13:44:01,672 ERROR com.sismics.util.jpa.DbOpenHelper.open(DbOpenHelper.java:122) Unable to complete schema update
java.lang.UnsupportedOperationException: Cannot list files for URL file:/C:/Users/Ben/workspace/bgd/docs/trunk/docs-core/target/classes/com/sismics/util/jpa/EMF$1.class
at com.sismics.util.ResourceUtil.list(ResourceUtil.java:86)
at com.sismics.util.jpa.DbOpenHelper.executeAllScript(DbOpenHelper.java:152)
at com.sismics.util.jpa.EMF$1.onCreate(EMF.java:45)
at com.sismics.util.jpa.DbOpenHelper.open(DbOpenHelper.java:110)
at com.sismics.util.jpa.EMF.<clinit>(EMF.java:55)
at com.sismics.docs.core.util.TransactionUtil.handle(TransactionUtil.java:38)
at com.sismics.util.filter.RequestContextFilter.init(RequestContextFilter.java:76)
at org.eclipse.jetty.servlet.FilterHolder.doStart(FilterHolder.java:114)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:59)
at org.eclipse.jetty.servlet.ServletHandler.initialize(ServletHandler.java:754)
at org.eclipse.jetty.servlet.ServletContextHandler.startContext(ServletContextHandler.java:258)
at org.eclipse.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1221)
at org.eclipse.jetty.server.handler.ContextHandler.doStart(ContextHandler.java:699)
at org.eclipse.jetty.webapp.WebAppContext.doStart(WebAppContext.java:454)
at org.mortbay.jetty.plugin.JettyWebAppContext.doStart(JettyWebAppContext.java:256)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:59)
at org.eclipse.jetty.server.handler.HandlerCollection.doStart(HandlerCollection.java:224)
at org.eclipse.jetty.server.handler.ContextHandlerCollection.doStart(ContextHandlerCollection.java:167)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:59)
at org.eclipse.jetty.server.handler.HandlerCollection.doStart(HandlerCollection.java:224)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:59)
at org.eclipse.jetty.server.handler.HandlerWrapper.doStart(HandlerWrapper.java:90)
at org.eclipse.jetty.server.Server.doStart(Server.java:263)
at org.mortbay.jetty.plugin.JettyServer.doStart(JettyServer.java:65)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:59)
at org.mortbay.jetty.plugin.AbstractJettyMojo.startJetty(AbstractJettyMojo.java:511)
at org.mortbay.jetty.plugin.AbstractJettyMojo.execute(AbstractJettyMojo.java:364)
at org.mortbay.jetty.plugin.JettyRunMojo.execute(JettyRunMojo.java:516)
at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildPluginManager.java:101)
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:209)
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:153)
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:145)
at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:84)
at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:59)
at org.apache.maven.lifecycle.internal.LifecycleStarter.singleThreadedBuild(LifecycleStarter.java:183)
at org.apache.maven.lifecycle.internal.LifecycleStarter.execute(LifecycleStarter.java:161)
at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:320)
at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:156)
at org.apache.maven.cli.MavenCli.execute(MavenCli.java:537)
at org.apache.maven.cli.MavenCli.doMain(MavenCli.java:196)
at org.apache.maven.cli.MavenCli.main(MavenCli.java:141)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced(Launcher.java:290)
at org.codehaus.plexus.classworlds.launcher.Launcher.launch(Launcher.java:230)
at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode(Launcher.java:409)
at org.codehaus.plexus.classworlds.launcher.Launcher.main(Launcher.java:352)
27 Jul 2013 13:44:01,677 INFO com.sismics.util.jpa.EMF.getEntityManagerProperties(EMF.java:69) Configuring EntityManager from hibernate.properties
27 Jul 2013 13:44:01,769 ERROR com.sismics.util.jpa.EMF.<clinit>(EMF.java:60) Error creating EMF
javax.persistence.PersistenceException: No Persistence provider for EntityManager named transactions-optional
at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:69)
at com.sismics.util.jpa.EMF.<clinit>(EMF.java:57)
at com.sismics.docs.core.util.TransactionUtil.handle(TransactionUtil.java:38)
at com.sismics.util.filter.RequestContextFilter.init(RequestContextFilter.java:76)
at org.eclipse.jetty.servlet.FilterHolder.doStart(FilterHolder.java:114)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:59)
at org.eclipse.jetty.servlet.ServletHandler.initialize(ServletHandler.java:754)
at org.eclipse.jetty.servlet.ServletContextHandler.startContext(ServletContextHandler.java:258)
at org.eclipse.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1221)
at org.eclipse.jetty.server.handler.ContextHandler.doStart(ContextHandler.java:699)
at org.eclipse.jetty.webapp.WebAppContext.doStart(WebAppContext.java:454)
at org.mortbay.jetty.plugin.JettyWebAppContext.doStart(JettyWebAppContext.java:256)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:59)
at org.eclipse.jetty.server.handler.HandlerCollection.doStart(HandlerCollection.java:224)
at org.eclipse.jetty.server.handler.ContextHandlerCollection.doStart(ContextHandlerCollection.java:167)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:59)
at org.eclipse.jetty.server.handler.HandlerCollection.doStart(HandlerCollection.java:224)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:59)
at org.eclipse.jetty.server.handler.HandlerWrapper.doStart(HandlerWrapper.java:90)
at org.eclipse.jetty.server.Server.doStart(Server.java:263)
at org.mortbay.jetty.plugin.JettyServer.doStart(JettyServer.java:65)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:59)
at org.mortbay.jetty.plugin.AbstractJettyMojo.startJetty(AbstractJettyMojo.java:511)
at org.mortbay.jetty.plugin.AbstractJettyMojo.execute(AbstractJettyMojo.java:364)
at org.mortbay.jetty.plugin.JettyRunMojo.execute(JettyRunMojo.java:516)
at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildPluginManager.java:101)
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:209)
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:153)
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:145)
at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:84)
at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:59)
at org.apache.maven.lifecycle.internal.LifecycleStarter.singleThreadedBuild(LifecycleStarter.java:183)
at org.apache.maven.lifecycle.internal.LifecycleStarter.execute(LifecycleStarter.java:161)
at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:320)
at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:156)
at org.apache.maven.cli.MavenCli.execute(MavenCli.java:537)
at org.apache.maven.cli.MavenCli.doMain(MavenCli.java:196)
at org.apache.maven.cli.MavenCli.main(MavenCli.java:141)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced(Launcher.java:290)
at org.codehaus.plexus.classworlds.launcher.Launcher.launch(Launcher.java:230)
at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode(Launcher.java:409)
at org.codehaus.plexus.classworlds.launcher.Launcher.main(Launcher.java:352)
27 Jul 2013 13:44:01,770 ERROR com.sismics.docs.core.util.TransactionUtil.handle(TransactionUtil.java:40) Cannot create entity manager
java.lang.NullPointerException
at com.sismics.docs.core.util.TransactionUtil.handle(TransactionUtil.java:38)
at com.sismics.util.filter.RequestContextFilter.init(RequestContextFilter.java:76)
at org.eclipse.jetty.servlet.FilterHolder.doStart(FilterHolder.java:114)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:59)
at org.eclipse.jetty.servlet.ServletHandler.initialize(ServletHandler.java:754)
at org.eclipse.jetty.servlet.ServletContextHandler.startContext(ServletContextHandler.java:258)
at org.eclipse.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1221)
at org.eclipse.jetty.server.handler.ContextHandler.doStart(ContextHandler.java:699)
at org.eclipse.jetty.webapp.WebAppContext.doStart(WebAppContext.java:454)
at org.mortbay.jetty.plugin.JettyWebAppContext.doStart(JettyWebAppContext.java:256)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:59)
at org.eclipse.jetty.server.handler.HandlerCollection.doStart(HandlerCollection.java:224)
at org.eclipse.jetty.server.handler.ContextHandlerCollection.doStart(ContextHandlerCollection.java:167)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:59)
at org.eclipse.jetty.server.handler.HandlerCollection.doStart(HandlerCollection.java:224)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:59)
at org.eclipse.jetty.server.handler.HandlerWrapper.doStart(HandlerWrapper.java:90)
at org.eclipse.jetty.server.Server.doStart(Server.java:263)
at org.mortbay.jetty.plugin.JettyServer.doStart(JettyServer.java:65)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:59)
at org.mortbay.jetty.plugin.AbstractJettyMojo.startJetty(AbstractJettyMojo.java:511)
at org.mortbay.jetty.plugin.AbstractJettyMojo.execute(AbstractJettyMojo.java:364)
at org.mortbay.jetty.plugin.JettyRunMojo.execute(JettyRunMojo.java:516)
at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildPluginManager.java:101)
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:209)
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:153)
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:145)
at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:84)
at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:59)
at org.apache.maven.lifecycle.internal.LifecycleStarter.singleThreadedBuild(LifecycleStarter.java:183)
at org.apache.maven.lifecycle.internal.LifecycleStarter.execute(LifecycleStarter.java:161)
at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:320)
at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:156)
at org.apache.maven.cli.MavenCli.execute(MavenCli.java:537)
at org.apache.maven.cli.MavenCli.doMain(MavenCli.java:196)
at org.apache.maven.cli.MavenCli.main(MavenCli.java:141)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced(Launcher.java:290)
at org.codehaus.plexus.classworlds.launcher.Launcher.launch(Launcher.java:230)
at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode(Launcher.java:409)
at org.codehaus.plexus.classworlds.launcher.Launcher.main(Launcher.java:352)
27 Jul 2013 13:51:34,136 INFO com.sismics.util.jpa.EMF.getEntityManagerProperties(EMF.java:69) Configuring EntityManager from hibernate.properties
27 Jul 2013 13:51:34,600 INFO com.sismics.util.jpa.DbOpenHelper.open(DbOpenHelper.java:67) Opening database and executing incremental updates
27 Jul 2013 13:51:34,602 INFO com.sismics.util.jpa.DbOpenHelper.open(DbOpenHelper.java:95) Unable to get database version: Table T_CONFIG not found
27 Jul 2013 13:51:34,602 INFO com.sismics.util.jpa.DbOpenHelper.open(DbOpenHelper.java:109) Executing initial schema creation script
27 Jul 2013 13:51:48,941 INFO com.sismics.util.jpa.DbOpenHelper.executeAllScript(DbOpenHelper.java:163) Executing script: dbupdate-000-0.sql
27 Jul 2013 13:51:48,984 INFO com.sismics.util.jpa.DbOpenHelper.executeAllScript(DbOpenHelper.java:163) Executing script: dbupdate-000-1.sql
27 Jul 2013 13:51:48,987 INFO com.sismics.util.jpa.DbOpenHelper.open(DbOpenHelper.java:117) Found database version 0, new version is 1, executing database incremental update scripts
27 Jul 2013 13:51:50,951 INFO com.sismics.util.jpa.DbOpenHelper.open(DbOpenHelper.java:119) Database upgrade complete
27 Jul 2013 13:51:50,951 INFO com.sismics.util.jpa.EMF.getEntityManagerProperties(EMF.java:69) Configuring EntityManager from hibernate.properties
27 Jul 2013 13:51:51,824 INFO com.sismics.docs.core.service.IndexingService.startUp(IndexingService.java:53) Using file Lucene storage: C:\Users\Ben\workspace\bgd\docs\trunk\docs-web\src\main\webapp\sismicsdocs\lucene

File diff suppressed because it is too large Load Diff

View File

View File

@@ -0,0 +1,3 @@
api.current_version=${project.version}
api.min_version=1.0
db.version=1

View File

@@ -0,0 +1,5 @@
log4j.rootCategory=WARN, MEMORY
log4j.appender.MEMORY=com.sismics.util.log4j.MemoryAppender
log4j.appender.MEMORY.size=1000
log4j.logger.com.sismics=INFO

View File

@@ -0,0 +1,86 @@
package com.sismics.docs.rest;
import junit.framework.Assert;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.junit.Test;
import com.sismics.docs.rest.BaseJerseyTest;
import com.sismics.docs.rest.filter.CookieAuthenticationFilter;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.ClientResponse.Status;
import com.sun.jersey.api.client.WebResource;
/**
* Test the app resource.
*
* @author jtremeaux
*/
public class TestAppResource extends BaseJerseyTest {
/**
* Test the API resource.
*
* @throws JSONException
*/
@Test
public void testAppResource() throws JSONException {
// Check the application info
WebResource appResource = resource().path("/app");
ClientResponse response = appResource.get(ClientResponse.class);
response = appResource.get(ClientResponse.class);
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
JSONObject json = response.getEntity(JSONObject.class);
String currentVersion = json.getString("current_version");
Assert.assertNotNull(currentVersion);
String minVersion = json.getString("min_version");
Assert.assertNotNull(minVersion);
Long freeMemory = json.getLong("free_memory");
Assert.assertTrue(freeMemory > 0);
Long totalMemory = json.getLong("total_memory");
Assert.assertTrue(totalMemory > 0 && totalMemory > freeMemory);
}
/**
* Test the log resource.
*
* @throws JSONException
*/
@Test
public void testLogResource() throws JSONException {
// Login admin
String adminAuthenticationToken = clientUtil.login("admin", "admin", false);
// Check the logs (page 1)
WebResource appResource = resource()
.path("/app/log")
.queryParam("level", "DEBUG");
ClientResponse response = appResource.get(ClientResponse.class);
appResource.addFilter(new CookieAuthenticationFilter(adminAuthenticationToken));
response = appResource.get(ClientResponse.class);
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
JSONObject json = response.getEntity(JSONObject.class);
JSONArray logs = json.getJSONArray("logs");
Assert.assertTrue(logs.length() == 10);
Long date1 = logs.optJSONObject(0).optLong("date");
Long date2 = logs.optJSONObject(9).optLong("date");
Assert.assertTrue(date1 > date2);
// Check the logs (page 2)
appResource = resource()
.path("/app/log")
.queryParam("offset", "10")
.queryParam("level", "DEBUG");
response = appResource.get(ClientResponse.class);
appResource.addFilter(new CookieAuthenticationFilter(adminAuthenticationToken));
response = appResource.get(ClientResponse.class);
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
json = response.getEntity(JSONObject.class);
logs = json.getJSONArray("logs");
Assert.assertTrue(logs.length() == 10);
Long date3 = logs.optJSONObject(0).optLong("date");
Long date4 = logs.optJSONObject(9).optLong("date");
Assert.assertTrue(date3 > date4);
}
}

View File

@@ -0,0 +1,106 @@
package com.sismics.docs.rest;
import junit.framework.Assert;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.junit.Test;
import com.sismics.docs.rest.filter.CookieAuthenticationFilter;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.ClientResponse.Status;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.core.util.MultivaluedMapImpl;
/**
* Exhaustive test of the document resource.
*
* @author bgamard
*/
public class TestDocumentResource extends BaseJerseyTest {
/**
* Test the document resource.
*
* @throws JSONException
*/
@Test
public void testDocumentResource() throws JSONException {
// Login admin
String adminAuthenticationToken = clientUtil.login("admin", "admin", false);
// Create a document
WebResource documentResource = resource().path("/document");
documentResource.addFilter(new CookieAuthenticationFilter(adminAuthenticationToken));
MultivaluedMapImpl postParams = new MultivaluedMapImpl();
postParams.add("title", "My super document 1");
postParams.add("description", "My super description for document 1");
ClientResponse response = documentResource.put(ClientResponse.class, postParams);
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
JSONObject json = response.getEntity(JSONObject.class);
String document1Id = json.optString("id");
Assert.assertNotNull(document1Id);
// List all documents
documentResource = resource().path("/document/list");
documentResource.addFilter(new CookieAuthenticationFilter(adminAuthenticationToken));
MultivaluedMapImpl getParams = new MultivaluedMapImpl();
getParams.putSingle("sort_column", 3);
getParams.putSingle("asc", false);
response = documentResource.queryParams(getParams).get(ClientResponse.class);
json = response.getEntity(JSONObject.class);
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
JSONArray documents = json.getJSONArray("documents");
Assert.assertTrue(documents.length() == 1);
Assert.assertEquals(document1Id, documents.getJSONObject(0).getString("id"));
// Get a document
documentResource = resource().path("/document");
documentResource.addFilter(new CookieAuthenticationFilter(adminAuthenticationToken));
getParams = new MultivaluedMapImpl();
getParams.putSingle("id", document1Id);
response = documentResource.queryParams(getParams).get(ClientResponse.class);
json = response.getEntity(JSONObject.class);
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
Assert.assertEquals(document1Id, json.getString("id"));
// Update a document
documentResource = resource().path("/document/" + document1Id);
documentResource.addFilter(new CookieAuthenticationFilter(adminAuthenticationToken));
postParams = new MultivaluedMapImpl();
postParams.add("title", "My new super document 1");
postParams.add("description", "My new super description for document 1");
response = documentResource.post(ClientResponse.class, postParams);
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
json = response.getEntity(JSONObject.class);
Assert.assertEquals("ok", json.getString("status"));
// Get a document
documentResource = resource().path("/document");
documentResource.addFilter(new CookieAuthenticationFilter(adminAuthenticationToken));
getParams = new MultivaluedMapImpl();
getParams.putSingle("id", document1Id);
response = documentResource.queryParams(getParams).get(ClientResponse.class);
json = response.getEntity(JSONObject.class);
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
Assert.assertTrue(json.getString("title").contains("new"));
Assert.assertTrue(json.getString("description").contains("new"));
// Deletes a document
documentResource = resource().path("/document/" + document1Id);
documentResource.addFilter(new CookieAuthenticationFilter(adminAuthenticationToken));
response = documentResource.delete(ClientResponse.class);
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
json = response.getEntity(JSONObject.class);
Assert.assertEquals("ok", json.getString("status"));
// Get a document (KO)
documentResource = resource().path("/document");
documentResource.addFilter(new CookieAuthenticationFilter(adminAuthenticationToken));
getParams = new MultivaluedMapImpl();
getParams.putSingle("id", document1Id);
response = documentResource.queryParams(getParams).get(ClientResponse.class);
json = response.getEntity(JSONObject.class);
Assert.assertEquals(Status.BAD_REQUEST, Status.fromStatusCode(response.getStatus()));
}
}

View File

@@ -0,0 +1,105 @@
package com.sismics.docs.rest;
import java.io.BufferedInputStream;
import java.io.InputStream;
import javax.ws.rs.core.MediaType;
import junit.framework.Assert;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.junit.Test;
import com.sismics.docs.rest.filter.CookieAuthenticationFilter;
import com.sismics.util.mime.MimeType;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.ClientResponse.Status;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.core.util.MultivaluedMapImpl;
import com.sun.jersey.multipart.FormDataBodyPart;
import com.sun.jersey.multipart.FormDataMultiPart;
/**
* Exhaustive test of the document resource.
*
* @author bgamard
*/
public class TestFileResource extends BaseJerseyTest {
/**
* Test the document resource.
*
* @throws JSONException
*/
@Test
public void testFileResource() throws JSONException {
// Login admin
String adminAuthenticationToken = clientUtil.login("admin", "admin", false);
// Create a document
WebResource documentResource = resource().path("/document");
documentResource.addFilter(new CookieAuthenticationFilter(adminAuthenticationToken));
MultivaluedMapImpl postParams = new MultivaluedMapImpl();
postParams.add("title", "File test document 1");
ClientResponse response = documentResource.put(ClientResponse.class, postParams);
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
JSONObject json = response.getEntity(JSONObject.class);
String document1Id = json.optString("id");
Assert.assertNotNull(document1Id);
// Add a file
WebResource fileResource = resource().path("/file");
fileResource.addFilter(new CookieAuthenticationFilter(adminAuthenticationToken));
FormDataMultiPart form = new FormDataMultiPart();
InputStream file = this.getClass().getResourceAsStream("/file/PIA00452.jpg");
FormDataBodyPart fdp = new FormDataBodyPart("file",
new BufferedInputStream(file),
MediaType.APPLICATION_OCTET_STREAM_TYPE);
form.bodyPart(fdp);
form.field("id", document1Id);
response = fileResource.type(MediaType.MULTIPART_FORM_DATA).put(ClientResponse.class, form);
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
json = response.getEntity(JSONObject.class);
String file1Id = json.getString("id");
// Get a file
fileResource = resource().path("/file");
fileResource.addFilter(new CookieAuthenticationFilter(adminAuthenticationToken));
MultivaluedMapImpl getParams = new MultivaluedMapImpl();
getParams.putSingle("id", file1Id);
response = fileResource.queryParams(getParams).get(ClientResponse.class);
json = response.getEntity(JSONObject.class);
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
Assert.assertEquals(MimeType.IMAGE_JPEG, json.getString("mimetype"));
Assert.assertEquals(file1Id, json.getString("id"));
// Get all files from a document
fileResource = resource().path("/file/list");
fileResource.addFilter(new CookieAuthenticationFilter(adminAuthenticationToken));
getParams = new MultivaluedMapImpl();
getParams.putSingle("id", document1Id);
response = fileResource.queryParams(getParams).get(ClientResponse.class);
json = response.getEntity(JSONObject.class);
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
JSONArray files = json.getJSONArray("files");
Assert.assertEquals(1, files.length());
// Deletes a file
documentResource = resource().path("/file/" + file1Id);
documentResource.addFilter(new CookieAuthenticationFilter(adminAuthenticationToken));
response = documentResource.delete(ClientResponse.class);
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
json = response.getEntity(JSONObject.class);
Assert.assertEquals("ok", json.getString("status"));
// Get a file (KO)
documentResource = resource().path("/file");
documentResource.addFilter(new CookieAuthenticationFilter(adminAuthenticationToken));
getParams = new MultivaluedMapImpl();
getParams.putSingle("id", file1Id);
response = documentResource.queryParams(getParams).get(ClientResponse.class);
json = response.getEntity(JSONObject.class);
Assert.assertEquals(Status.BAD_REQUEST, Status.fromStatusCode(response.getStatus()));
}
}

View File

@@ -0,0 +1,36 @@
package com.sismics.docs.rest;
import junit.framework.Assert;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.junit.Test;
import com.sismics.docs.rest.BaseJerseyTest;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.ClientResponse.Status;
import com.sun.jersey.api.client.WebResource;
/**
* Test the locale resource.
*
* @author jtremeaux
*/
public class TestLocaleResource extends BaseJerseyTest {
/**
* Test the locale resource.
*
* @throws JSONException
*/
@Test
public void testLocaleResource() throws JSONException {
WebResource localeResource = resource().path("/locale");
ClientResponse response = localeResource.get(ClientResponse.class);
response = localeResource.get(ClientResponse.class);
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
JSONObject json = response.getEntity(JSONObject.class);
JSONArray locale = json.getJSONArray("locales");
Assert.assertTrue(locale.length() > 0);
}
}

View File

@@ -0,0 +1,81 @@
package com.sismics.docs.rest;
import junit.framework.Assert;
import org.apache.commons.lang.StringUtils;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.junit.Test;
import com.sismics.docs.rest.BaseJerseyTest;
import com.sismics.docs.rest.filter.CookieAuthenticationFilter;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.ClientResponse.Status;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.core.util.MultivaluedMapImpl;
/**
* Test of the security layer.
*
* @author jtremeaux
*/
public class TestSecurity extends BaseJerseyTest {
/**
* Test of the security layer.
*
* @throws JSONException
*/
@Test
public void testSecurity() throws JSONException {
// Create a user
clientUtil.createUser("testsecurity");
// Changes a user's email KO : the user is not connected
WebResource userResource = resource().path("/user/update");
MultivaluedMapImpl postParams = new MultivaluedMapImpl();
postParams.add("email", "testsecurity2@docs.com");
ClientResponse response = userResource.post(ClientResponse.class, postParams);
Assert.assertEquals(Status.FORBIDDEN, Status.fromStatusCode(response.getStatus()));
JSONObject json = response.getEntity(JSONObject.class);
Assert.assertEquals("ForbiddenError", json.getString("type"));
Assert.assertEquals("You don't have access to this resource", json.getString("message"));
// User testsecurity logs in
String testSecurityAuthenticationToken = clientUtil.login("testsecurity");
// User testsecurity changes his email OK
userResource = resource().path("/user");
userResource.addFilter(new CookieAuthenticationFilter(testSecurityAuthenticationToken));
postParams = new MultivaluedMapImpl();
postParams.add("email", "testsecurity2@docs.com");
postParams.add("locale", "en");
response = userResource.post(ClientResponse.class, postParams);
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
json = response.getEntity(JSONObject.class);
Assert.assertEquals("ok", json.getString("status"));
// User testsecurity logs out
userResource = resource().path("/user/logout");
userResource.addFilter(new CookieAuthenticationFilter(testSecurityAuthenticationToken));
postParams = new MultivaluedMapImpl();
response = userResource.post(ClientResponse.class, postParams);
json = response.getEntity(JSONObject.class);
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
testSecurityAuthenticationToken = clientUtil.getAuthenticationCookie(response);
Assert.assertTrue(StringUtils.isEmpty(testSecurityAuthenticationToken));
// User testsecurity logs out KO : he is not connected anymore
userResource = resource().path("/user/logout");
userResource.addFilter(new CookieAuthenticationFilter(testSecurityAuthenticationToken));
postParams = new MultivaluedMapImpl();
response = userResource.post(ClientResponse.class, postParams);
json = response.getEntity(JSONObject.class);
Assert.assertEquals(Status.FORBIDDEN, Status.fromStatusCode(response.getStatus()));
// User testsecurity logs in with a long lived session
testSecurityAuthenticationToken = clientUtil.login("testsecurity", "12345678", true);
// User testsecurity logs out
clientUtil.logout(testSecurityAuthenticationToken);
}
}

View File

@@ -0,0 +1,36 @@
package com.sismics.docs.rest;
import junit.framework.Assert;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.junit.Test;
import com.sismics.docs.rest.BaseJerseyTest;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.ClientResponse.Status;
import com.sun.jersey.api.client.WebResource;
/**
* Test the theme resource.
*
* @author jtremeaux
*/
public class TestThemeResource extends BaseJerseyTest {
/**
* Test the theme resource.
*
* @throws JSONException
*/
@Test
public void testThemeResource() throws JSONException {
WebResource themeResource = resource().path("/theme");
ClientResponse response = themeResource.get(ClientResponse.class);
response = themeResource.get(ClientResponse.class);
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
JSONObject json = response.getEntity(JSONObject.class);
JSONArray theme = json.getJSONArray("themes");
Assert.assertTrue(theme.length() > 0);
}
}

View File

@@ -0,0 +1,279 @@
package com.sismics.docs.rest;
import java.util.Locale;
import javax.ws.rs.core.MultivaluedMap;
import junit.framework.Assert;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.junit.Test;
import com.sismics.docs.rest.BaseJerseyTest;
import com.sismics.docs.rest.filter.CookieAuthenticationFilter;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.ClientResponse.Status;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.core.util.MultivaluedMapImpl;
/**
* Exhaustive test of the user resource.
*
* @author jtremeaux
*/
public class TestUserResource extends BaseJerseyTest {
/**
* Test the user resource.
*
* @throws JSONException
*/
@Test
public void testUserResource() throws JSONException {
// Check anonymous user information
WebResource userResource = resource().path("/user");
ClientResponse response = userResource.acceptLanguage(Locale.US).get(ClientResponse.class);
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
JSONObject json = response.getEntity(JSONObject.class);
Assert.assertTrue(json.getBoolean("is_default_password"));
// Create alice user
clientUtil.createUser("alice");
// Login admin
String adminAuthenticationToken = clientUtil.login("admin", "admin", false);
// List all users
userResource = resource().path("/user/list");
userResource.addFilter(new CookieAuthenticationFilter(adminAuthenticationToken));
MultivaluedMapImpl getParams = new MultivaluedMapImpl();
getParams.putSingle("sort_column", 2);
getParams.putSingle("asc", false);
response = userResource.queryParams(getParams).get(ClientResponse.class);
json = response.getEntity(JSONObject.class);
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
JSONArray users = json.getJSONArray("users");
Assert.assertTrue(users.length() > 0);
// Create a user KO (login length validation)
userResource = resource().path("/user");
userResource.addFilter(new CookieAuthenticationFilter(adminAuthenticationToken));
MultivaluedMapImpl postParams = new MultivaluedMapImpl();
postParams.putSingle("username", " bb ");
postParams.putSingle("email", "bob@docs.com");
postParams.putSingle("password", "12345678");
response = userResource.put(ClientResponse.class, postParams);
Assert.assertEquals(Status.BAD_REQUEST, Status.fromStatusCode(response.getStatus()));
json = response.getEntity(JSONObject.class);
Assert.assertEquals("ValidationError", json.getString("type"));
Assert.assertTrue(json.getString("message"), json.getString("message").contains("more than 3"));
// Create a user KO (login format validation)
userResource = resource().path("/user");
userResource.addFilter(new CookieAuthenticationFilter(adminAuthenticationToken));
postParams = new MultivaluedMapImpl();
postParams.putSingle("username", "bob-");
postParams.putSingle("email", " bob@docs.com ");
postParams.putSingle("password", "12345678");
response = userResource.put(ClientResponse.class, postParams);
Assert.assertEquals(Status.BAD_REQUEST, Status.fromStatusCode(response.getStatus()));
json = response.getEntity(JSONObject.class);
Assert.assertEquals("ValidationError", json.getString("type"));
Assert.assertTrue(json.getString("message"), json.getString("message").contains("alphanumeric"));
// Create a user KO (email format validation)
userResource = resource().path("/user");
userResource.addFilter(new CookieAuthenticationFilter(adminAuthenticationToken));
postParams = new MultivaluedMapImpl();
postParams.putSingle("username", "bob");
postParams.putSingle("email", " bobdocs.com ");
postParams.putSingle("password", "12345678");
response = userResource.put(ClientResponse.class, postParams);
Assert.assertEquals(Status.BAD_REQUEST, Status.fromStatusCode(response.getStatus()));
json = response.getEntity(JSONObject.class);
Assert.assertEquals("ValidationError", json.getString("type"));
Assert.assertTrue(json.getString("message"), json.getString("message").contains("must be an email"));
// Create a user bob OK
userResource = resource().path("/user");
userResource.addFilter(new CookieAuthenticationFilter(adminAuthenticationToken));
postParams = new MultivaluedMapImpl();
postParams.putSingle("username", " bob ");
postParams.putSingle("email", " bob@docs.com ");
postParams.putSingle("password", " 12345678 ");
postParams.putSingle("locale", "fr");
response = userResource.put(ClientResponse.class, postParams);
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
// Create a user bob KO : duplicate username
response = userResource.put(ClientResponse.class, postParams);
Assert.assertNotSame(Status.OK, Status.fromStatusCode(response.getStatus()));
json = response.getEntity(JSONObject.class);
Assert.assertEquals("AlreadyExistingUsername", json.getString("type"));
// Check if a username is free : OK
userResource = resource().path("/user/check_username");
MultivaluedMap<String, String> queryParams = new MultivaluedMapImpl();
queryParams.add("username", "carol");
response = userResource.queryParams(queryParams).get(ClientResponse.class);
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
// Check if a username is free : KO
userResource = resource().path("/user/check_username");
queryParams = new MultivaluedMapImpl();
queryParams.add("username", "alice");
response = userResource.queryParams(queryParams).get(ClientResponse.class);
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
json = response.getEntity(JSONObject.class);
Assert.assertEquals("ko", json.getString("status"));
// Login alice with extra whitespaces
userResource = resource().path("/user/login");
postParams = new MultivaluedMapImpl();
postParams.putSingle("username", " alice ");
postParams.putSingle("password", " 12345678 ");
response = userResource.post(ClientResponse.class, postParams);
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
String aliceAuthToken = clientUtil.getAuthenticationCookie(response);
// Login user bob
String bobAuthToken = clientUtil.login("bob");
// Check alice user information
userResource = resource().path("/user");
userResource.addFilter(new CookieAuthenticationFilter(aliceAuthToken));
response = userResource.get(ClientResponse.class);
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
json = response.getEntity(JSONObject.class);
Assert.assertEquals("alice@docs.com", json.getString("email"));
Assert.assertEquals("default.less", json.getString("theme"));
Assert.assertFalse(json.getBoolean("first_connection"));
Assert.assertFalse(json.getBoolean("is_default_password"));
// Check bob user information
userResource = resource().path("/user");
userResource.addFilter(new CookieAuthenticationFilter(bobAuthToken));
response = userResource.get(ClientResponse.class);
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
json = response.getEntity(JSONObject.class);
Assert.assertEquals("bob@docs.com", json.getString("email"));
Assert.assertEquals("fr", json.getString("locale"));
// Test login KO (user not found)
userResource = resource().path("/user/login");
postParams.putSingle("username", "intruder");
postParams.putSingle("password", "12345678");
response = userResource.post(ClientResponse.class, postParams);
Assert.assertEquals(Status.FORBIDDEN, Status.fromStatusCode(response.getStatus()));
// Test login KO (wrong password)
userResource = resource().path("/user/login");
postParams.putSingle("username", "alice");
postParams.putSingle("password", "error");
response = userResource.post(ClientResponse.class, postParams);
Assert.assertEquals(Status.FORBIDDEN, Status.fromStatusCode(response.getStatus()));
// User alice updates her information + changes her email
userResource = resource().path("/user");
userResource.addFilter(new CookieAuthenticationFilter(aliceAuthToken));
postParams = new MultivaluedMapImpl();
postParams.add("email", " alice2@docs.com ");
postParams.add("theme", " default.less ");
postParams.add("locale", " en ");
response = userResource.post(ClientResponse.class, postParams);
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
json = response.getEntity(JSONObject.class);
Assert.assertEquals("ok", json.getString("status"));
// Check the update
userResource = resource().path("/user");
userResource.addFilter(new CookieAuthenticationFilter(aliceAuthToken));
response = userResource.get(ClientResponse.class);
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
json = response.getEntity(JSONObject.class);
Assert.assertEquals("alice2@docs.com", json.getString("email"));
// Delete user alice
userResource = resource().path("/user");
userResource.addFilter(new CookieAuthenticationFilter(aliceAuthToken));
response = userResource.delete(ClientResponse.class);
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
// Check the deletion
userResource = resource().path("/user/login");
postParams.putSingle("username", "alice");
postParams.putSingle("password", "12345678");
response = userResource.post(ClientResponse.class, postParams);
Assert.assertEquals(Status.FORBIDDEN, Status.fromStatusCode(response.getStatus()));
}
/**
* Test the user resource admin functions.
*
* @throws JSONException
*/
@Test
public void testUserResourceAdmin() throws JSONException {
// Create admin_user1 user
clientUtil.createUser("admin_user1");
// Login admin
String adminAuthenticationToken = clientUtil.login("admin", "admin", false);
// Check admin information
WebResource userResource = resource().path("/user");
userResource.addFilter(new CookieAuthenticationFilter(adminAuthenticationToken));
ClientResponse response = userResource.get(ClientResponse.class);
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
JSONObject json = response.getEntity(JSONObject.class);
Assert.assertTrue(json.getBoolean("first_connection"));
Assert.assertTrue(json.getBoolean("is_default_password"));
// User admin updates his information
userResource = resource().path("/user");
userResource.addFilter(new CookieAuthenticationFilter(adminAuthenticationToken));
MultivaluedMapImpl postParams = new MultivaluedMapImpl();
postParams.add("first_connection", false);
response = userResource.post(ClientResponse.class, postParams);
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
json = response.getEntity(JSONObject.class);
Assert.assertEquals("ok", json.getString("status"));
// Check admin information update
userResource = resource().path("/user");
userResource.addFilter(new CookieAuthenticationFilter(adminAuthenticationToken));
response = userResource.get(ClientResponse.class);
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
json = response.getEntity(JSONObject.class);
Assert.assertFalse(json.getBoolean("first_connection"));
// User admin update admin_user1 information
userResource = resource().path("/user");
userResource.addFilter(new CookieAuthenticationFilter(adminAuthenticationToken));
postParams = new MultivaluedMapImpl();
postParams.add("email", " alice2@docs.com ");
postParams.add("theme", " default.less");
postParams.add("locale", " en ");
response = userResource.post(ClientResponse.class, postParams);
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
json = response.getEntity(JSONObject.class);
Assert.assertEquals("ok", json.getString("status"));
// User admin deletes user admin_user1
userResource = resource().path("/user/admin_user1");
userResource.addFilter(new CookieAuthenticationFilter(adminAuthenticationToken));
response = userResource.delete(ClientResponse.class);
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
json = response.getEntity(JSONObject.class);
Assert.assertEquals("ok", json.getString("status"));
// User admin deletes user admin_user1 : KO (user doesn't exist)
userResource = resource().path("/user/admin_user1");
userResource.addFilter(new CookieAuthenticationFilter(adminAuthenticationToken));
response = userResource.delete(ClientResponse.class);
Assert.assertEquals(Status.BAD_REQUEST, Status.fromStatusCode(response.getStatus()));
json = response.getEntity(JSONObject.class);
Assert.assertEquals("UserNotFound", json.getString("type"));
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

View File

@@ -0,0 +1,10 @@
hibernate.connection.driver_class=org.hsqldb.jdbcDriver
hibernate.connection.url=jdbc:hsqldb:mem:docs;get_column_name=false
hibernate.connection.username=sa
hibernate.connection.password=
hibernate.hbm2ddl.auto=none
hibernate.dialect=org.hibernate.dialect.HSQLDialect
hibernate.show_sql=true
hibernate.format_sql=false
hibernate.max_fetch_depth=5
hibernate.cache.use_second_level_cache=false

View File

@@ -0,0 +1,9 @@
log4j.rootCategory=DEBUG, CONSOLE, MEMORY
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{DATE} %p %l %m %n
log4j.appender.MEMORY=com.sismics.util.log4j.MemoryAppender
log4j.appender.MEMORY.size=1000
log4j.logger.com.sismics=DEBUG
log4j.logger.org.hibernate.internal.util.EntityPrinter=INFO