mirror of
https://github.com/sismics/docs.git
synced 2025-12-14 10:16:21 +00:00
Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3aeac8bf4 | ||
|
|
34e3ac5478 | ||
|
|
ae566018d6 | ||
|
|
12c3c4750f | ||
|
|
42f23ed0d7 | ||
|
|
d76b8e32c8 | ||
|
|
6aaecb473f | ||
|
|
03976160bf | ||
|
|
438a38985a | ||
|
|
9802beaf7b | ||
|
|
6963fd9770 | ||
|
|
85aa16afba | ||
|
|
0e99f06310 | ||
|
|
60beb0e2f5 | ||
|
|
77f0368ba5 | ||
|
|
596dd4db13 | ||
|
|
d2ba291287 | ||
|
|
01cb3e611c | ||
|
|
bd9e918a62 | ||
|
|
56f6038c09 | ||
|
|
f29ed3e671 | ||
|
|
726121d8c8 | ||
|
|
c40e7e1cc9 | ||
|
|
b1f9b072f3 | ||
|
|
22cea20a90 | ||
|
|
8eb5b8066e | ||
|
|
e37eab3b7a | ||
|
|
1db54174d4 | ||
|
|
d5fa3a4e3a | ||
|
|
fc53758eb7 | ||
|
|
f5079e83cb | ||
|
|
b399e4081f | ||
|
|
a80bc27582 | ||
|
|
eb57af4029 | ||
|
|
ac3580fb4a | ||
|
|
6e3d2ea972 | ||
|
|
870a44da0d | ||
|
|
62a5840777 | ||
|
|
a858699391 | ||
|
|
fb0aec5fee | ||
|
|
1a495ac948 | ||
|
|
ba083fb57a | ||
|
|
4e22111f38 | ||
|
|
db7a9f0e4a | ||
|
|
906de329ae | ||
|
|
00b00f0d0c | ||
|
|
0bc658a396 | ||
|
|
464d43194b | ||
|
|
1c606ebf25 | ||
|
|
504c4dd815 | ||
|
|
ecb5a7abf6 | ||
|
|
cc6d599293 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,3 +7,4 @@
|
||||
/*/*.iml
|
||||
/out
|
||||
/.idea
|
||||
/.project
|
||||
12
README.md
12
README.md
@@ -1,7 +1,7 @@
|
||||
Sismics Docs
|
||||
============
|
||||
|
||||

|
||||

|
||||
|
||||
What is Docs?
|
||||
---------------
|
||||
@@ -14,10 +14,11 @@ Features
|
||||
--------
|
||||
|
||||
- Responsive user interface
|
||||
- Optical characted recognition
|
||||
- Optical character recognition
|
||||
- Support image and PDF files
|
||||
- Flexible search engine
|
||||
- Full text search in image and PDF
|
||||
- SHA-256 encryption
|
||||
- Tag system
|
||||
- Multi-users
|
||||
- Document sharing
|
||||
@@ -26,13 +27,13 @@ Features
|
||||
License
|
||||
-------
|
||||
|
||||
Reader is released under the terms of the GPL license. See `COPYING` for more
|
||||
Docs is released under the terms of the GPL license. See `COPYING` for more
|
||||
information or see <http://opensource.org/licenses/GPL-2.0>.
|
||||
|
||||
How to build Docs from the sources
|
||||
------------------------------------
|
||||
----------------------------------
|
||||
|
||||
Prerequisites: JDK 7, Maven 3
|
||||
Prerequisites: JDK 7, Maven 3, Tesseract 3.02
|
||||
|
||||
Docs is organized in several Maven modules:
|
||||
|
||||
@@ -48,6 +49,7 @@ or download the sources from GitHub.
|
||||
|
||||
From the `docs-parent` directory:
|
||||
|
||||
mvn -Pinit validate -N
|
||||
mvn clean -DskipTests install
|
||||
|
||||
#### Run a stand-alone version
|
||||
|
||||
4
docs-android/.gitignore
vendored
Normal file
4
docs-android/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea
|
||||
.DS_Store
|
||||
1
docs-android/app/.gitignore
vendored
Normal file
1
docs-android/app/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
76
docs-android/app/app.iml
Normal file
76
docs-android/app/app.iml
Normal file
@@ -0,0 +1,76 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" type="JAVA_MODULE" version="4">
|
||||
<component name="FacetManager">
|
||||
<facet type="android" name="Android">
|
||||
<configuration>
|
||||
<option name="SELECTED_BUILD_VARIANT" value="debug" />
|
||||
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
|
||||
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugJava" />
|
||||
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugTest" />
|
||||
<option name="SOURCE_GEN_TASK_NAME" value="generateDebugSources" />
|
||||
<option name="ALLOW_USER_CONFIGURATION" value="false" />
|
||||
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
|
||||
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
|
||||
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" />
|
||||
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
|
||||
</configuration>
|
||||
</facet>
|
||||
<facet type="android-gradle" name="Android-Gradle">
|
||||
<configuration>
|
||||
<option name="GRADLE_PROJECT_PATH" value=":app" />
|
||||
</configuration>
|
||||
</facet>
|
||||
</component>
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="false">
|
||||
<output url="file://$MODULE_DIR$/build/classes/debug" />
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/source/r/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/source/aidl/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/source/buildConfig/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/source/rs/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/res/rs/debug" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/source/r/test/debug" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/source/aidl/test/debug" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/source/buildConfig/test/debug" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/source/rs/test/debug" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/res/rs/test/debug" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/aidl" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/assets" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/java" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/jni" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/rs" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/res" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/instrumentTest/resources" type="java-test-resource" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/apk" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/assets" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/bundles" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/classes" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/dependency-cache" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/incremental" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/libs" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/manifests" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/res" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/symbols" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Android API 19 Platform" jdkType="Android SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" exported="" name="support-v4-18.0.0" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
|
||||
40
docs-android/app/build.gradle
Normal file
40
docs-android/app/build.gradle
Normal file
@@ -0,0 +1,40 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:0.7.+'
|
||||
}
|
||||
}
|
||||
apply plugin: 'android'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 19
|
||||
buildToolsVersion "19.0.0"
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 19
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_7
|
||||
targetCompatibility JavaVersion.VERSION_1_7
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
runProguard false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'com.android.support:support-v4:18.0.0'
|
||||
}
|
||||
17
docs-android/app/proguard-rules.txt
Normal file
17
docs-android/app/proguard-rules.txt
Normal file
@@ -0,0 +1,17 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in /opt/android-studio/sdk/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the ProGuard
|
||||
# include property in project.properties.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
29
docs-android/app/src/main/AndroidManifest.xml
Normal file
29
docs-android/app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.sismics.docs" >
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme" >
|
||||
<activity
|
||||
android:name="com.sismics.docs.DocListActivity"
|
||||
android:label="@string/app_name" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name="com.sismics.docs.DocDetailActivity"
|
||||
android:label="@string/title_doc_detail"
|
||||
android:parentActivityName="com.sismics.docs.DocListActivity" >
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="com.sismics.docs.DocListActivity" />
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.sismics.docs;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.view.MenuItem;
|
||||
|
||||
/**
|
||||
* An activity representing a single Doc detail screen. This
|
||||
* activity is only used on handset devices. On tablet-size devices,
|
||||
* item details are presented side-by-side with a list of items
|
||||
* in a {@link DocListActivity}.
|
||||
* <p>
|
||||
* This activity is mostly just a 'shell' activity containing nothing
|
||||
* more than a {@link DocDetailFragment}.
|
||||
*/
|
||||
public class DocDetailActivity extends FragmentActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_doc_detail);
|
||||
|
||||
// Show the Up button in the action bar.
|
||||
getActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
// savedInstanceState is non-null when there is fragment state
|
||||
// saved from previous configurations of this activity
|
||||
// (e.g. when rotating the screen from portrait to landscape).
|
||||
// In this case, the fragment will automatically be re-added
|
||||
// to its container so we don't need to manually add it.
|
||||
// For more information, see the Fragments API guide at:
|
||||
//
|
||||
// http://developer.android.com/guide/components/fragments.html
|
||||
//
|
||||
if (savedInstanceState == null) {
|
||||
// Create the detail fragment and add it to the activity
|
||||
// using a fragment transaction.
|
||||
Bundle arguments = new Bundle();
|
||||
arguments.putString(DocDetailFragment.ARG_ITEM_ID,
|
||||
getIntent().getStringExtra(DocDetailFragment.ARG_ITEM_ID));
|
||||
DocDetailFragment fragment = new DocDetailFragment();
|
||||
fragment.setArguments(arguments);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.add(R.id.doc_detail_container, fragment)
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
if (id == android.R.id.home) {
|
||||
// This ID represents the Home or Up button. In the case of this
|
||||
// activity, the Up button is shown. Use NavUtils to allow users
|
||||
// to navigate up one level in the application structure. For
|
||||
// more details, see the Navigation pattern on Android Design:
|
||||
//
|
||||
// http://developer.android.com/design/patterns/navigation.html#up-vs-back
|
||||
//
|
||||
NavUtils.navigateUpTo(this, new Intent(this, DocListActivity.class));
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.sismics.docs;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.sismics.docs.dummy.DummyContent;
|
||||
|
||||
/**
|
||||
* A fragment representing a single Doc detail screen.
|
||||
* This fragment is either contained in a {@link DocListActivity}
|
||||
* in two-pane mode (on tablets) or a {@link DocDetailActivity}
|
||||
* on handsets.
|
||||
*/
|
||||
public class DocDetailFragment extends Fragment {
|
||||
/**
|
||||
* The fragment argument representing the item ID that this fragment
|
||||
* represents.
|
||||
*/
|
||||
public static final String ARG_ITEM_ID = "item_id";
|
||||
|
||||
/**
|
||||
* The dummy content this fragment is presenting.
|
||||
*/
|
||||
private DummyContent.DummyItem mItem;
|
||||
|
||||
/**
|
||||
* Mandatory empty constructor for the fragment manager to instantiate the
|
||||
* fragment (e.g. upon screen orientation changes).
|
||||
*/
|
||||
public DocDetailFragment() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (getArguments().containsKey(ARG_ITEM_ID)) {
|
||||
// Load the dummy content specified by the fragment
|
||||
// arguments. In a real-world scenario, use a Loader
|
||||
// to load content from a content provider.
|
||||
mItem = DummyContent.ITEM_MAP.get(getArguments().getString(ARG_ITEM_ID));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View rootView = inflater.inflate(R.layout.fragment_doc_detail, container, false);
|
||||
|
||||
// Show the dummy content as text in a TextView.
|
||||
if (mItem != null) {
|
||||
((TextView) rootView.findViewById(R.id.doc_detail)).setText(mItem.content);
|
||||
}
|
||||
|
||||
return rootView;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.sismics.docs;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
|
||||
|
||||
/**
|
||||
* An activity representing a list of Docs. This activity
|
||||
* has different presentations for handset and tablet-size devices. On
|
||||
* handsets, the activity presents a list of items, which when touched,
|
||||
* lead to a {@link DocDetailActivity} representing
|
||||
* item details. On tablets, the activity presents the list of items and
|
||||
* item details side-by-side using two vertical panes.
|
||||
* <p>
|
||||
* The activity makes heavy use of fragments. The list of items is a
|
||||
* {@link DocListFragment} and the item details
|
||||
* (if present) is a {@link DocDetailFragment}.
|
||||
* <p>
|
||||
* This activity also implements the required
|
||||
* {@link DocListFragment.Callbacks} interface
|
||||
* to listen for item selections.
|
||||
*/
|
||||
public class DocListActivity extends FragmentActivity
|
||||
implements DocListFragment.Callbacks {
|
||||
|
||||
/**
|
||||
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
|
||||
* device.
|
||||
*/
|
||||
private boolean mTwoPane;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_doc_list);
|
||||
|
||||
if (findViewById(R.id.doc_detail_container) != null) {
|
||||
// The detail container view will be present only in the
|
||||
// large-screen layouts (res/values-large and
|
||||
// res/values-sw600dp). If this view is present, then the
|
||||
// activity should be in two-pane mode.
|
||||
mTwoPane = true;
|
||||
|
||||
// In two-pane mode, list items should be given the
|
||||
// 'activated' state when touched.
|
||||
((DocListFragment) getSupportFragmentManager()
|
||||
.findFragmentById(R.id.doc_list))
|
||||
.setActivateOnItemClick(true);
|
||||
}
|
||||
|
||||
// TODO: If exposing deep links into your app, handle intents here.
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback method from {@link DocListFragment.Callbacks}
|
||||
* indicating that the item with the given ID was selected.
|
||||
*/
|
||||
@Override
|
||||
public void onItemSelected(String id) {
|
||||
if (mTwoPane) {
|
||||
// In two-pane mode, show the detail view in this activity by
|
||||
// adding or replacing the detail fragment using a
|
||||
// fragment transaction.
|
||||
Bundle arguments = new Bundle();
|
||||
arguments.putString(DocDetailFragment.ARG_ITEM_ID, id);
|
||||
DocDetailFragment fragment = new DocDetailFragment();
|
||||
fragment.setArguments(arguments);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.doc_detail_container, fragment)
|
||||
.commit();
|
||||
|
||||
} else {
|
||||
// In single-pane mode, simply start the detail activity
|
||||
// for the selected item ID.
|
||||
Intent detailIntent = new Intent(this, DocDetailActivity.class);
|
||||
detailIntent.putExtra(DocDetailFragment.ARG_ITEM_ID, id);
|
||||
startActivity(detailIntent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
package com.sismics.docs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.ListFragment;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
|
||||
import com.sismics.docs.dummy.DummyContent;
|
||||
|
||||
/**
|
||||
* A list fragment representing a list of Docs. This fragment
|
||||
* also supports tablet devices by allowing list items to be given an
|
||||
* 'activated' state upon selection. This helps indicate which item is
|
||||
* currently being viewed in a {@link DocDetailFragment}.
|
||||
* <p>
|
||||
* Activities containing this fragment MUST implement the {@link Callbacks}
|
||||
* interface.
|
||||
*/
|
||||
public class DocListFragment extends ListFragment {
|
||||
|
||||
/**
|
||||
* The serialization (saved instance state) Bundle key representing the
|
||||
* activated item position. Only used on tablets.
|
||||
*/
|
||||
private static final String STATE_ACTIVATED_POSITION = "activated_position";
|
||||
|
||||
/**
|
||||
* The fragment's current callback object, which is notified of list item
|
||||
* clicks.
|
||||
*/
|
||||
private Callbacks mCallbacks = sDummyCallbacks;
|
||||
|
||||
/**
|
||||
* The current activated item position. Only used on tablets.
|
||||
*/
|
||||
private int mActivatedPosition = ListView.INVALID_POSITION;
|
||||
|
||||
/**
|
||||
* A callback interface that all activities containing this fragment must
|
||||
* implement. This mechanism allows activities to be notified of item
|
||||
* selections.
|
||||
*/
|
||||
public interface Callbacks {
|
||||
/**
|
||||
* Callback for when an item has been selected.
|
||||
*/
|
||||
public void onItemSelected(String id);
|
||||
}
|
||||
|
||||
/**
|
||||
* A dummy implementation of the {@link Callbacks} interface that does
|
||||
* nothing. Used only when this fragment is not attached to an activity.
|
||||
*/
|
||||
private static Callbacks sDummyCallbacks = new Callbacks() {
|
||||
@Override
|
||||
public void onItemSelected(String id) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Mandatory empty constructor for the fragment manager to instantiate the
|
||||
* fragment (e.g. upon screen orientation changes).
|
||||
*/
|
||||
public DocListFragment() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// TODO: replace with a real list adapter.
|
||||
setListAdapter(new ArrayAdapter<DummyContent.DummyItem>(
|
||||
getActivity(),
|
||||
android.R.layout.simple_list_item_activated_1,
|
||||
android.R.id.text1,
|
||||
DummyContent.ITEMS));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
// Restore the previously serialized activated item position.
|
||||
if (savedInstanceState != null
|
||||
&& savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) {
|
||||
setActivatedPosition(savedInstanceState.getInt(STATE_ACTIVATED_POSITION));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
|
||||
// Activities containing this fragment must implement its callbacks.
|
||||
if (!(activity instanceof Callbacks)) {
|
||||
throw new IllegalStateException("Activity must implement fragment's callbacks.");
|
||||
}
|
||||
|
||||
mCallbacks = (Callbacks) activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
|
||||
// Reset the active callbacks interface to the dummy implementation.
|
||||
mCallbacks = sDummyCallbacks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onListItemClick(ListView listView, View view, int position, long id) {
|
||||
super.onListItemClick(listView, view, position, id);
|
||||
|
||||
// Notify the active callbacks interface (the activity, if the
|
||||
// fragment is attached to one) that an item has been selected.
|
||||
mCallbacks.onItemSelected(DummyContent.ITEMS.get(position).id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
if (mActivatedPosition != ListView.INVALID_POSITION) {
|
||||
// Serialize and persist the activated item position.
|
||||
outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns on activate-on-click mode. When this mode is on, list items will be
|
||||
* given the 'activated' state when touched.
|
||||
*/
|
||||
public void setActivateOnItemClick(boolean activateOnItemClick) {
|
||||
// When setting CHOICE_MODE_SINGLE, ListView will automatically
|
||||
// give items the 'activated' state when touched.
|
||||
getListView().setChoiceMode(activateOnItemClick
|
||||
? ListView.CHOICE_MODE_SINGLE
|
||||
: ListView.CHOICE_MODE_NONE);
|
||||
}
|
||||
|
||||
private void setActivatedPosition(int position) {
|
||||
if (position == ListView.INVALID_POSITION) {
|
||||
getListView().setItemChecked(mActivatedPosition, false);
|
||||
} else {
|
||||
getListView().setItemChecked(position, true);
|
||||
}
|
||||
|
||||
mActivatedPosition = position;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.sismics.docs.dummy;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Helper class for providing sample content for user interfaces created by
|
||||
* Android template wizards.
|
||||
* <p>
|
||||
* TODO: Replace all uses of this class before publishing your app.
|
||||
*/
|
||||
public class DummyContent {
|
||||
|
||||
/**
|
||||
* An array of sample (dummy) items.
|
||||
*/
|
||||
public static List<DummyItem> ITEMS = new ArrayList<DummyItem>();
|
||||
|
||||
/**
|
||||
* A map of sample (dummy) items, by ID.
|
||||
*/
|
||||
public static Map<String, DummyItem> ITEM_MAP = new HashMap<String, DummyItem>();
|
||||
|
||||
static {
|
||||
// Add 3 sample items.
|
||||
addItem(new DummyItem("1", "Item 1"));
|
||||
addItem(new DummyItem("2", "Item 2"));
|
||||
addItem(new DummyItem("3", "Item 3"));
|
||||
}
|
||||
|
||||
private static void addItem(DummyItem item) {
|
||||
ITEMS.add(item);
|
||||
ITEM_MAP.put(item.id, item);
|
||||
}
|
||||
|
||||
/**
|
||||
* A dummy item representing a piece of content.
|
||||
*/
|
||||
public static class DummyItem {
|
||||
public String id;
|
||||
public String content;
|
||||
|
||||
public DummyItem(String id, String content) {
|
||||
this.id = id;
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
docs-android/app/src/main/res/drawable-hdpi/ic_launcher.png
Normal file
BIN
docs-android/app/src/main/res/drawable-hdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
BIN
docs-android/app/src/main/res/drawable-mdpi/ic_launcher.png
Normal file
BIN
docs-android/app/src/main/res/drawable-mdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.1 KiB |
BIN
docs-android/app/src/main/res/drawable-xhdpi/ic_launcher.png
Normal file
BIN
docs-android/app/src/main/res/drawable-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
docs-android/app/src/main/res/drawable-xxhdpi/ic_launcher.png
Normal file
BIN
docs-android/app/src/main/res/drawable-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
@@ -0,0 +1,7 @@
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/doc_detail_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="com.sismics.docs.DocDetailActivity"
|
||||
tools:ignore="MergeRootFrame" />
|
||||
10
docs-android/app/src/main/res/layout/activity_doc_list.xml
Normal file
10
docs-android/app/src/main/res/layout/activity_doc_list.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/doc_list"
|
||||
android:name="com.sismics.docs.DocListFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
tools:context="com.sismics.docs.DocListActivity"
|
||||
tools:layout="@android:layout/list_content" />
|
||||
@@ -0,0 +1,38 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:baselineAligned="false"
|
||||
android:divider="?android:attr/dividerHorizontal"
|
||||
android:orientation="horizontal"
|
||||
android:showDividers="middle"
|
||||
tools:context="com.sismics.docs.DocListActivity">
|
||||
|
||||
<!--
|
||||
This layout is a two-pane layout for the Docs
|
||||
master/detail flow. See res/values-large/refs.xml and
|
||||
res/values-sw600dp/refs.xml for an example of layout aliases
|
||||
that replace the single-pane version of the layout with
|
||||
this two-pane version.
|
||||
|
||||
For more on layout aliases, see:
|
||||
http://developer.android.com/training/multiscreen/screensizes.html#TaskUseAliasFilters
|
||||
-->
|
||||
|
||||
<fragment
|
||||
android:id="@+id/doc_list"
|
||||
android:name="com.sismics.docs.DocListFragment"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
tools:layout="@android:layout/list_content" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/doc_detail_container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="3" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,9 @@
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/doc_detail"
|
||||
style="?android:attr/textAppearanceLarge"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="16dp"
|
||||
android:textIsSelectable="true"
|
||||
tools:context="com.sismics.docs.DocDetailFragment" />
|
||||
10
docs-android/app/src/main/res/values-large/refs.xml
Normal file
10
docs-android/app/src/main/res/values-large/refs.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<resources>
|
||||
<!--
|
||||
Layout alias to replace the single-pane version of the layout with a
|
||||
two-pane version on Large screens.
|
||||
|
||||
For more on layout aliases, see:
|
||||
http://developer.android.com/training/multiscreen/screensizes.html#TaskUseAliasFilters
|
||||
-->
|
||||
<item type="layout" name="activity_doc_list">@layout/activity_doc_twopane</item>
|
||||
</resources>
|
||||
10
docs-android/app/src/main/res/values-sw600dp/refs.xml
Normal file
10
docs-android/app/src/main/res/values-sw600dp/refs.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<resources>
|
||||
<!--
|
||||
Layout alias to replace the single-pane version of the layout with a
|
||||
two-pane version on Large screens.
|
||||
|
||||
For more on layout aliases, see:
|
||||
http://developer.android.com/training/multiscreen/screensizes.html#TaskUseAliasFilters
|
||||
-->
|
||||
<item type="layout" name="activity_doc_list">@layout/activity_doc_twopane</item>
|
||||
</resources>
|
||||
7
docs-android/app/src/main/res/values/strings.xml
Normal file
7
docs-android/app/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="app_name">Sismics Docs</string>
|
||||
<string name="title_doc_detail">Doc Detail</string>
|
||||
|
||||
</resources>
|
||||
8
docs-android/app/src/main/res/values/styles.xml
Normal file
8
docs-android/app/src/main/res/values/styles.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="android:Theme.Holo.Light">
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
1
docs-android/build.gradle
Normal file
1
docs-android/build.gradle
Normal file
@@ -0,0 +1 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
18
docs-android/gradle.properties
Normal file
18
docs-android/gradle.properties
Normal file
@@ -0,0 +1,18 @@
|
||||
# Project-wide Gradle settings.
|
||||
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Settings specified in this file will override any Gradle settings
|
||||
# configured through the IDE.
|
||||
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
# Default value: -Xmx10248m -XX:MaxPermSize=256m
|
||||
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
BIN
docs-android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
docs-android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
docs-android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
docs-android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
#Wed Apr 10 15:27:10 PDT 2013
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=http\://services.gradle.org/distributions/gradle-1.9-all.zip
|
||||
164
docs-android/gradlew
vendored
Normal file
164
docs-android/gradlew
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
esac
|
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched.
|
||||
if $cygwin ; then
|
||||
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||
fi
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >&-
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >&-
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
||||
90
docs-android/gradlew.bat
vendored
Normal file
90
docs-android/gradlew.bat
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windowz variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
goto execute
|
||||
|
||||
:4NT_args
|
||||
@rem Get arguments from the 4NT Shell from JP Software
|
||||
set CMD_LINE_ARGS=%$
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
1
docs-android/settings.gradle
Normal file
1
docs-android/settings.gradle
Normal file
@@ -0,0 +1 @@
|
||||
include ':app'
|
||||
@@ -122,6 +122,11 @@
|
||||
<artifactId>pdfbox</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- OCR dependencies -->
|
||||
<dependency>
|
||||
<groupId>jna</groupId>
|
||||
@@ -133,11 +138,6 @@
|
||||
<artifactId>imageio</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>tess4j</groupId>
|
||||
<artifactId>tess4j</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Test dependencies -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
|
||||
@@ -138,14 +138,15 @@ public class DocumentDao {
|
||||
*
|
||||
* @param paginatedList List of documents (updated by side effects)
|
||||
* @param criteria Search criteria
|
||||
* @return List of document
|
||||
* @return List of documents
|
||||
* @throws Exception
|
||||
*/
|
||||
public void findByCriteria(PaginatedList<DocumentDto> paginatedList, DocumentCriteria criteria, SortCriteria sortCriteria) throws Exception {
|
||||
Map<String, Object> parameterMap = new HashMap<String, Object>();
|
||||
List<String> criteriaList = new ArrayList<String>();
|
||||
|
||||
StringBuilder sb = new StringBuilder("select distinct d.DOC_ID_C c0, d.DOC_TITLE_C c1, d.DOC_DESCRIPTION_C c2, d.DOC_CREATEDATE_D c3, d.DOC_LANGUAGE_C c4, s.SHA_ID_C is not null c5 ");
|
||||
StringBuilder sb = new StringBuilder("select distinct d.DOC_ID_C c0, d.DOC_TITLE_C c1, d.DOC_DESCRIPTION_C c2, d.DOC_CREATEDATE_D c3, d.DOC_LANGUAGE_C c4, s.SHA_ID_C is not null c5, ");
|
||||
sb.append(" (select count(f.FIL_ID_C) from T_FILE f where f.FIL_DELETEDATE_D is null and f.FIL_IDDOC_C = d.DOC_ID_C) c6 ");
|
||||
sb.append(" from T_DOCUMENT d ");
|
||||
sb.append(" left join T_SHARE s on s.SHA_IDDOCUMENT_C = d.DOC_ID_C and s.SHA_DELETEDATE_D is null ");
|
||||
|
||||
@@ -211,6 +212,7 @@ public class DocumentDao {
|
||||
documentDto.setCreateTimestamp(((Timestamp) o[i++]).getTime());
|
||||
documentDto.setLanguage((String) o[i++]);
|
||||
documentDto.setShared((Boolean) o[i++]);
|
||||
documentDto.setFileCount(((Number) o[i++]).intValue());
|
||||
documentDtoList.add(documentDto);
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,11 @@ public class DocumentDto {
|
||||
*/
|
||||
private Boolean shared;
|
||||
|
||||
/**
|
||||
* File count.
|
||||
*/
|
||||
private Integer fileCount;
|
||||
|
||||
/**
|
||||
* Getter de id.
|
||||
*
|
||||
@@ -146,4 +151,20 @@ public class DocumentDto {
|
||||
public void setLanguage(String language) {
|
||||
this.language = language;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter of fileCount.
|
||||
* @return fileCount
|
||||
*/
|
||||
public Integer getFileCount() {
|
||||
return fileCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter of fileCount.
|
||||
* @param fileCount fileCount
|
||||
*/
|
||||
public void setFileCount(Integer fileCount) {
|
||||
this.fileCount = fileCount;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import java.util.Set;
|
||||
import org.apache.lucene.document.Field;
|
||||
import org.apache.lucene.document.StringField;
|
||||
import org.apache.lucene.document.TextField;
|
||||
import org.apache.lucene.index.DirectoryReader;
|
||||
import org.apache.lucene.index.IndexWriter;
|
||||
import org.apache.lucene.index.Term;
|
||||
import org.apache.lucene.queries.TermsFilter;
|
||||
@@ -171,12 +172,17 @@ public class LuceneDao {
|
||||
TermsFilter userFilter = new TermsFilter(terms);
|
||||
|
||||
// Search
|
||||
IndexSearcher searcher = new IndexSearcher(AppContext.getInstance().getIndexingService().getDirectoryReader());
|
||||
DirectoryReader directoryReader = AppContext.getInstance().getIndexingService().getDirectoryReader();
|
||||
Set<String> documentIdList = new HashSet<String>();
|
||||
if (directoryReader == null) {
|
||||
// The directory reader is not yet initialized (probably because there is nothing indexed)
|
||||
return documentIdList;
|
||||
}
|
||||
IndexSearcher searcher = new IndexSearcher(directoryReader);
|
||||
TopDocs topDocs = searcher.search(query, userFilter, Integer.MAX_VALUE);
|
||||
ScoreDoc[] docs = topDocs.scoreDocs;
|
||||
|
||||
// Extract document IDs
|
||||
Set<String> documentIdList = new HashSet<String>();
|
||||
for (int i = 0; i < docs.length; i++) {
|
||||
org.apache.lucene.document.Document document = searcher.doc(docs[i].doc);
|
||||
String type = document.get("type");
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.sismics.docs.core.event;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
|
||||
/**
|
||||
* Extract file content event.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class ExtractFileAsyncEvent {
|
||||
@Override
|
||||
public String toString() {
|
||||
return Objects.toStringHelper(this)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.sismics.docs.core.event;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import com.sismics.docs.core.model.jpa.Document;
|
||||
import com.sismics.docs.core.model.jpa.File;
|
||||
@@ -20,6 +22,11 @@ public class FileCreatedAsyncEvent {
|
||||
*/
|
||||
private Document document;
|
||||
|
||||
/**
|
||||
* Unencrypted input stream containing the file.
|
||||
*/
|
||||
private InputStream inputStream;
|
||||
|
||||
/**
|
||||
* Getter of file.
|
||||
*
|
||||
@@ -56,6 +63,24 @@ public class FileCreatedAsyncEvent {
|
||||
this.document = document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter of inputStream.
|
||||
*
|
||||
* @return the inputStream
|
||||
*/
|
||||
public InputStream getInputStream() {
|
||||
return inputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter de inputStream.
|
||||
*
|
||||
* @param inputStream inputStream
|
||||
*/
|
||||
public void setInputStream(InputStream inputStream) {
|
||||
this.inputStream = inputStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Objects.toStringHelper(this)
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
package com.sismics.docs.core.listener.async;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import com.sismics.docs.core.dao.jpa.DocumentDao;
|
||||
import com.sismics.docs.core.dao.jpa.FileDao;
|
||||
import com.sismics.docs.core.event.ExtractFileAsyncEvent;
|
||||
import com.sismics.docs.core.model.jpa.Document;
|
||||
import com.sismics.docs.core.model.jpa.File;
|
||||
import com.sismics.docs.core.util.FileUtil;
|
||||
import com.sismics.docs.core.util.TransactionUtil;
|
||||
|
||||
/**
|
||||
* Listener on extract content from all files.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class ExtractFileAsyncListener {
|
||||
/**
|
||||
* Logger.
|
||||
*/
|
||||
private static final Logger log = LoggerFactory.getLogger(ExtractFileAsyncListener.class);
|
||||
|
||||
/**
|
||||
* Extract content from all files.
|
||||
*
|
||||
* @param extractFileAsyncEvent Extract file content event
|
||||
* @throws Exception
|
||||
*/
|
||||
@Subscribe
|
||||
public void on(final ExtractFileAsyncEvent extractFileAsyncEvent) throws Exception {
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info("Extract file content event: " + extractFileAsyncEvent.toString());
|
||||
}
|
||||
|
||||
TransactionUtil.handle(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
FileDao fileDao = new FileDao();
|
||||
DocumentDao documentDao = new DocumentDao();
|
||||
List<File> fileList = fileDao.findAll();
|
||||
for (File file : fileList) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
Document document = documentDao.getById(file.getDocumentId());
|
||||
file.setContent(FileUtil.extractContent(document, file));
|
||||
TransactionUtil.commit();
|
||||
log.info(MessageFormat.format("File content extracted in {0}ms", System.currentTimeMillis() - startTime));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ public class FileCreatedAsyncListener {
|
||||
// OCR the file
|
||||
final File file = fileCreatedAsyncEvent.getFile();
|
||||
long startTime = System.currentTimeMillis();
|
||||
final String content = FileUtil.extractContent(fileCreatedAsyncEvent.getDocument(), file);
|
||||
final String content = FileUtil.extractContent(fileCreatedAsyncEvent.getDocument(), file, fileCreatedAsyncEvent.getInputStream());
|
||||
log.info(MessageFormat.format("File content extracted in {0}ms", System.currentTimeMillis() - startTime));
|
||||
|
||||
// Store the OCR-ization result in the database
|
||||
|
||||
@@ -16,7 +16,6 @@ import com.sismics.docs.core.listener.async.DocumentDeletedAsyncListener;
|
||||
import com.sismics.docs.core.listener.async.DocumentUpdatedAsyncListener;
|
||||
import com.sismics.docs.core.listener.async.FileCreatedAsyncListener;
|
||||
import com.sismics.docs.core.listener.async.FileDeletedAsyncListener;
|
||||
import com.sismics.docs.core.listener.async.ExtractFileAsyncListener;
|
||||
import com.sismics.docs.core.listener.async.RebuildIndexAsyncListener;
|
||||
import com.sismics.docs.core.listener.sync.DeadEventListener;
|
||||
import com.sismics.docs.core.model.jpa.Config;
|
||||
@@ -82,7 +81,6 @@ public class AppContext {
|
||||
asyncEventBus.register(new DocumentUpdatedAsyncListener());
|
||||
asyncEventBus.register(new DocumentDeletedAsyncListener());
|
||||
asyncEventBus.register(new RebuildIndexAsyncListener());
|
||||
asyncEventBus.register(new ExtractFileAsyncListener());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -50,7 +50,7 @@ public class Tag {
|
||||
/**
|
||||
* Tag name.
|
||||
*/
|
||||
@Column(name = "TAG_COLOR_C", nullable = false, length = 6)
|
||||
@Column(name = "TAG_COLOR_C", nullable = false, length = 7)
|
||||
private String color;
|
||||
|
||||
/**
|
||||
|
||||
@@ -47,6 +47,12 @@ public class User {
|
||||
@Column(name = "USE_PASSWORD_C", nullable = false, length = 100)
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* User's private key.
|
||||
*/
|
||||
@Column(name = "USE_PRIVATEKEY_C", nullable = false, length = 100)
|
||||
private String privateKey;
|
||||
|
||||
/**
|
||||
* Email address.
|
||||
*/
|
||||
@@ -257,6 +263,22 @@ public class User {
|
||||
this.deleteDate = deleteDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter de privateKey.
|
||||
* @return privateKey
|
||||
*/
|
||||
public String getPrivateKey() {
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter de privateKey.
|
||||
* @param privateKey privateKey
|
||||
*/
|
||||
public void setPrivateKey(String privateKey) {
|
||||
this.privateKey = privateKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Objects.toStringHelper(this)
|
||||
|
||||
@@ -128,7 +128,6 @@ public class IndexingService extends AbstractScheduledService {
|
||||
public DirectoryReader getDirectoryReader() {
|
||||
if (directoryReader == null) {
|
||||
if (!DirectoryReader.indexExists(directory)) {
|
||||
log.info("Lucene directory not yet created");
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.sismics.docs.core.util;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.Security;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.CipherInputStream;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.SecretKeyFactory;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
|
||||
/**
|
||||
* Encryption utilities.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class EncryptionUtil {
|
||||
|
||||
/**
|
||||
* Salt.
|
||||
*/
|
||||
private static final String SALT = "LEpxZmm2SMu2PeKzPNrar2rhVAS6LrrgvXKeL9uyXC4vgKHg";
|
||||
|
||||
static {
|
||||
// Initialize Bouncy Castle provider
|
||||
Security.insertProviderAt(new BouncyCastleProvider(), 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a private key.
|
||||
*
|
||||
* @return New random private key
|
||||
* @throws NoSuchAlgorithmException
|
||||
*/
|
||||
public static String generatePrivateKey() throws NoSuchAlgorithmException {
|
||||
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
|
||||
return new BigInteger(176, random).toString(32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt an InputStream using the specified private key.
|
||||
*
|
||||
* @param is InputStream to encrypt
|
||||
* @param privateKey Private key
|
||||
* @return Encrypted stream
|
||||
* @throws Exception
|
||||
*/
|
||||
public static InputStream decryptInputStream(InputStream is, String privateKey) throws Exception {
|
||||
return new CipherInputStream(is, getCipher(privateKey, Cipher.DECRYPT_MODE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an encryption cipher.
|
||||
*
|
||||
* @param privateKey Private key
|
||||
* @return Encryption cipher
|
||||
* @throws Exception
|
||||
*/
|
||||
public static Cipher getEncryptionCipher(String privateKey) throws Exception {
|
||||
if (Strings.isNullOrEmpty(privateKey)) {
|
||||
throw new IllegalArgumentException("The private key is null or empty");
|
||||
}
|
||||
return getCipher(privateKey, Cipher.ENCRYPT_MODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a Cipher.
|
||||
*
|
||||
* @param privateKey Private key
|
||||
* @param mode Mode (encrypt or decrypt)
|
||||
* @return Cipher
|
||||
* @throws Exception
|
||||
*/
|
||||
private static Cipher getCipher(String privateKey, int mode) throws Exception {
|
||||
PBEKeySpec keySpec = new PBEKeySpec(privateKey.toCharArray(), SALT.getBytes(), 2000, 256);
|
||||
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBEWITHSHA256AND256BITAES-CBC-BC");
|
||||
SecretKey desKey = skf.generateSecret(keySpec);
|
||||
Cipher cipher = Cipher.getInstance("AES/CTR/NOPADDING");
|
||||
cipher.init(mode, desKey);
|
||||
return cipher;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,20 @@
|
||||
package com.sismics.docs.core.util;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.CipherInputStream;
|
||||
import javax.crypto.CipherOutputStream;
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import net.sourceforge.tess4j.Tesseract;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.util.PDFTextStripper;
|
||||
@@ -23,6 +26,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.sismics.docs.core.model.jpa.Document;
|
||||
import com.sismics.docs.core.model.jpa.File;
|
||||
import com.sismics.tess4j.Tesseract;
|
||||
import com.sismics.util.ImageUtil;
|
||||
import com.sismics.util.mime.MimeType;
|
||||
|
||||
@@ -42,36 +46,36 @@ public class FileUtil {
|
||||
*
|
||||
* @param document Document linked to the file
|
||||
* @param file File to extract
|
||||
* @param inputStream Unencrypted input stream
|
||||
* @return Content extract
|
||||
*/
|
||||
public static String extractContent(Document document, File file) {
|
||||
public static String extractContent(Document document, File file, InputStream inputStream) {
|
||||
String content = null;
|
||||
|
||||
if (ImageUtil.isImage(file.getMimeType())) {
|
||||
content = ocrFile(document, file);
|
||||
content = ocrFile(inputStream, document);
|
||||
} else if (file.getMimeType().equals(MimeType.APPLICATION_PDF)) {
|
||||
content = extractPdf(file);
|
||||
content = extractPdf(inputStream);
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optical character recognition on a file.
|
||||
* Optical character recognition on a stream.
|
||||
*
|
||||
* @param inputStream Unencrypted input stream
|
||||
* @param document Document linked to the file
|
||||
* @param file File to OCR
|
||||
* @return Content extracted
|
||||
*/
|
||||
private static String ocrFile(Document document, File file) {
|
||||
private static String ocrFile(InputStream inputStream, Document document) {
|
||||
Tesseract instance = Tesseract.getInstance();
|
||||
java.io.File storedfile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file.getId()).toFile();
|
||||
String content = null;
|
||||
BufferedImage image = null;
|
||||
try {
|
||||
image = ImageIO.read(storedfile);
|
||||
image = ImageIO.read(inputStream);
|
||||
} catch (IOException e) {
|
||||
log.error("Error reading the image " + storedfile, e);
|
||||
log.error("Error reading the image", e);
|
||||
}
|
||||
|
||||
// Upscale and grayscale the image
|
||||
@@ -85,7 +89,7 @@ public class FileUtil {
|
||||
instance.setLanguage(document.getLanguage());
|
||||
content = instance.doOCR(image);
|
||||
} catch (Exception e) {
|
||||
log.error("Error while OCR-izing the file " + storedfile, e);
|
||||
log.error("Error while OCR-izing the image", e);
|
||||
}
|
||||
|
||||
return content;
|
||||
@@ -94,19 +98,18 @@ public class FileUtil {
|
||||
/**
|
||||
* Extract text from a PDF.
|
||||
*
|
||||
* @param file File to extract
|
||||
* @param inputStream Unencrypted input stream
|
||||
* @return Content extracted
|
||||
*/
|
||||
private static String extractPdf(File file) {
|
||||
private static String extractPdf(InputStream inputStream) {
|
||||
String content = null;
|
||||
PDDocument pdfDocument = null;
|
||||
java.io.File storedfile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file.getId()).toFile();
|
||||
try {
|
||||
PDFTextStripper stripper = new PDFTextStripper();
|
||||
pdfDocument = PDDocument.load(storedfile.getAbsolutePath(), true);
|
||||
pdfDocument = PDDocument.load(inputStream, true);
|
||||
content = stripper.getText(pdfDocument);
|
||||
} catch (IOException e) {
|
||||
log.error("Error while extracting text from the PDF " + storedfile, e);
|
||||
log.error("Error while extracting text from the PDF", e);
|
||||
} finally {
|
||||
if (pdfDocument != null) {
|
||||
try {
|
||||
@@ -123,39 +126,39 @@ public class FileUtil {
|
||||
/**
|
||||
* Save a file on the storage filesystem.
|
||||
*
|
||||
* @param is InputStream
|
||||
* @param inputStream Unencrypted input stream
|
||||
* @param file File to save
|
||||
* @throws IOException
|
||||
* @param privateKey Private key used for encryption
|
||||
* @throws Exception
|
||||
*/
|
||||
public static void save(InputStream is, File file) throws IOException {
|
||||
public static void save(InputStream inputStream, File file, String privateKey) throws Exception {
|
||||
Cipher cipher = EncryptionUtil.getEncryptionCipher(privateKey);
|
||||
Path path = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file.getId());
|
||||
Files.copy(is, path);
|
||||
Files.copy(new CipherInputStream(inputStream, cipher), path);
|
||||
|
||||
// Generate file variations
|
||||
try {
|
||||
saveVariations(file, path.toFile());
|
||||
} catch (IOException e) {
|
||||
// Don't rethrow Exception from file variations generation
|
||||
log.error("Error creating file variations", e);
|
||||
}
|
||||
inputStream.reset();
|
||||
saveVariations(file, inputStream, cipher);
|
||||
inputStream.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate file variations.
|
||||
*
|
||||
* @param file File from database
|
||||
* @param originalFile Original file
|
||||
* @throws IOException
|
||||
* @param inputStream Unencrypted input stream
|
||||
* @param cipher Cipher to use for encryption
|
||||
* @throws Exception
|
||||
*/
|
||||
public static void saveVariations(File file, java.io.File originalFile) throws IOException {
|
||||
public static void saveVariations(File file, InputStream inputStream, Cipher cipher) throws Exception {
|
||||
BufferedImage image = null;
|
||||
if (ImageUtil.isImage(file.getMimeType())) {
|
||||
image = ImageIO.read(originalFile);
|
||||
image = ImageIO.read(inputStream);
|
||||
} else if(file.getMimeType().equals(MimeType.APPLICATION_PDF)) {
|
||||
// Generate preview from the first page of the PDF
|
||||
PDDocument pdfDocument = null;
|
||||
try {
|
||||
pdfDocument = PDDocument.load(originalFile.getAbsolutePath(), true);
|
||||
pdfDocument = PDDocument.load(inputStream, true);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<PDPage> pageList = pdfDocument.getDocumentCatalog().getAllPages();
|
||||
if (pageList.size() > 0) {
|
||||
@@ -172,8 +175,24 @@ public class FileUtil {
|
||||
BufferedImage web = Scalr.resize(image, Scalr.Method.AUTOMATIC, Scalr.Mode.AUTOMATIC, 1280, Scalr.OP_ANTIALIAS);
|
||||
BufferedImage thumbnail = Scalr.resize(image, Scalr.Method.AUTOMATIC, Scalr.Mode.AUTOMATIC, 256, Scalr.OP_ANTIALIAS);
|
||||
image.flush();
|
||||
ImageUtil.writeJpeg(web, Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file.getId() + "_web").toFile());
|
||||
ImageUtil.writeJpeg(thumbnail, Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file.getId() + "_thumb").toFile());
|
||||
|
||||
// Write "web" encrypted image
|
||||
java.io.File outputFile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file.getId() + "_web").toFile();
|
||||
OutputStream outputStream = new CipherOutputStream(new FileOutputStream(outputFile), cipher);
|
||||
try {
|
||||
ImageUtil.writeJpeg(web, outputStream);
|
||||
} finally {
|
||||
outputStream.close();
|
||||
}
|
||||
|
||||
// Write "thumb" encrypted image
|
||||
outputFile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file.getId() + "_thumb").toFile();
|
||||
outputStream = new CipherOutputStream(new FileOutputStream(outputFile), cipher);
|
||||
try {
|
||||
ImageUtil.writeJpeg(thumbnail, outputStream);
|
||||
} finally {
|
||||
outputStream.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
173
docs-core/src/main/java/com/sismics/tess4j/ImageHelper.java
Normal file
173
docs-core/src/main/java/com/sismics/tess4j/ImageHelper.java
Normal file
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* Copyright @ 2008 Quan Nguyen
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.sismics.tess4j;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.Transparency;
|
||||
import java.awt.datatransfer.Clipboard;
|
||||
import java.awt.datatransfer.DataFlavor;
|
||||
import java.awt.image.*;
|
||||
|
||||
public class ImageHelper {
|
||||
|
||||
/**
|
||||
* Convenience method that returns a scaled instance of the provided
|
||||
* {@code BufferedImage}.
|
||||
*
|
||||
* @param image the original image to be scaled
|
||||
* @param targetWidth the desired width of the scaled instance, in pixels
|
||||
* @param targetHeight the desired height of the scaled instance, in pixels
|
||||
* @return a scaled version of the original {@code BufferedImage}
|
||||
*/
|
||||
public static BufferedImage getScaledInstance(BufferedImage image, int targetWidth, int targetHeight) {
|
||||
int type = (image.getTransparency() == Transparency.OPAQUE)
|
||||
? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
|
||||
BufferedImage tmp = new BufferedImage(targetWidth, targetHeight, type);
|
||||
Graphics2D g2 = tmp.createGraphics();
|
||||
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
|
||||
g2.drawImage(image, 0, 0, targetWidth, targetHeight, null);
|
||||
g2.dispose();
|
||||
return tmp;
|
||||
}
|
||||
|
||||
/**
|
||||
* A replacement for the standard
|
||||
* <code>BufferedImage.getSubimage</code> method.
|
||||
*
|
||||
* @param image
|
||||
* @param x the X coordinate of the upper-left corner of the specified
|
||||
* rectangular region
|
||||
* @param y the Y coordinate of the upper-left corner of the specified
|
||||
* rectangular region
|
||||
* @param width the width of the specified rectangular region
|
||||
* @param height the height of the specified rectangular region
|
||||
* @return a BufferedImage that is the subimage of <code>image</code>.
|
||||
*/
|
||||
public static BufferedImage getSubImage(BufferedImage image, int x, int y, int width, int height) {
|
||||
int type = (image.getTransparency() == Transparency.OPAQUE)
|
||||
? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
|
||||
BufferedImage tmp = new BufferedImage(width, height, type);
|
||||
Graphics2D g2 = tmp.createGraphics();
|
||||
g2.drawImage(image.getSubimage(x, y, width, height), 0, 0, null);
|
||||
g2.dispose();
|
||||
return tmp;
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple method to convert an image to binary or B/W image.
|
||||
*
|
||||
* @param image input image
|
||||
* @return a monochrome image
|
||||
*/
|
||||
public static BufferedImage convertImageToBinary(BufferedImage image) {
|
||||
BufferedImage tmp = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_BYTE_BINARY);
|
||||
Graphics2D g2 = tmp.createGraphics();
|
||||
g2.drawImage(image, 0, 0, null);
|
||||
g2.dispose();
|
||||
return tmp;
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple method to convert an image to binary or B/W image.
|
||||
*
|
||||
* @param image input image
|
||||
* @return a monochrome image
|
||||
* @deprecated As of release 1.1, renamed to {@link #convertImageToBinary(BufferedImage image)}
|
||||
*/
|
||||
@Deprecated
|
||||
public static BufferedImage convertImage2Binary(BufferedImage image) {
|
||||
return convertImageToBinary(image);
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple method to convert an image to gray scale.
|
||||
*
|
||||
* @param image input image
|
||||
* @return a monochrome image
|
||||
*/
|
||||
public static BufferedImage convertImageToGrayscale(BufferedImage image) {
|
||||
BufferedImage tmp = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
|
||||
Graphics2D g2 = tmp.createGraphics();
|
||||
g2.drawImage(image, 0, 0, null);
|
||||
g2.dispose();
|
||||
return tmp;
|
||||
}
|
||||
|
||||
private static final short[] invertTable;
|
||||
|
||||
static {
|
||||
invertTable = new short[256];
|
||||
for (int i = 0; i < 256; i++) {
|
||||
invertTable[i] = (short) (255 - i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inverts image color.
|
||||
*
|
||||
* @param image input image
|
||||
* @return an inverted-color image
|
||||
*/
|
||||
public static BufferedImage invertImageColor(BufferedImage image) {
|
||||
BufferedImage tmp = new BufferedImage(image.getWidth(), image.getHeight(), image.getType());
|
||||
BufferedImageOp invertOp = new LookupOp(new ShortLookupTable(0, invertTable), null);
|
||||
return invertOp.filter(image, tmp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotates an image.
|
||||
*
|
||||
* @param image the original image
|
||||
* @param angle the degree of rotation
|
||||
* @return a rotated image
|
||||
*/
|
||||
public static BufferedImage rotateImage(BufferedImage image, double angle) {
|
||||
double theta = Math.toRadians(angle);
|
||||
double sin = Math.abs(Math.sin(theta));
|
||||
double cos = Math.abs(Math.cos(theta));
|
||||
int w = image.getWidth();
|
||||
int h = image.getHeight();
|
||||
int newW = (int) Math.floor(w * cos + h * sin);
|
||||
int newH = (int) Math.floor(h * cos + w * sin);
|
||||
|
||||
BufferedImage tmp = new BufferedImage(newW, newH, image.getType());
|
||||
Graphics2D g2d = tmp.createGraphics();
|
||||
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
|
||||
RenderingHints.VALUE_INTERPOLATION_BICUBIC);
|
||||
g2d.translate((newW - w) / 2, (newH - h) / 2);
|
||||
g2d.rotate(theta, w / 2, h / 2);
|
||||
g2d.drawImage(image, 0, 0, null);
|
||||
g2d.dispose();
|
||||
return tmp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an image from Clipboard.
|
||||
*
|
||||
* @return image
|
||||
*/
|
||||
public static Image getClipboardImage() {
|
||||
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
||||
try {
|
||||
return (Image) clipboard.getData(DataFlavor.imageFlavor);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
143
docs-core/src/main/java/com/sismics/tess4j/ImageIOHelper.java
Normal file
143
docs-core/src/main/java/com/sismics/tess4j/ImageIOHelper.java
Normal file
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* Copyright @ 2008 Quan Nguyen
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.sismics.tess4j;
|
||||
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBufferByte;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.imageio.IIOImage;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageReadParam;
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.ImageWriteParam;
|
||||
import javax.imageio.ImageWriter;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import com.sun.media.imageio.plugins.tiff.TIFFImageWriteParam;
|
||||
import com.sun.media.imageioimpl.plugins.tiff.TIFFImageReaderSpi;
|
||||
import com.sun.media.imageioimpl.plugins.tiff.TIFFImageWriterSpi;
|
||||
|
||||
public class ImageIOHelper {
|
||||
|
||||
final static String TIFF_FORMAT = "tiff";
|
||||
|
||||
|
||||
/**
|
||||
* Gets pixel data of an
|
||||
* <code>IIOImage</code> object.
|
||||
*
|
||||
* @param image an
|
||||
* <code>IIOImage</code> object
|
||||
* @return a byte buffer of pixel data
|
||||
* @throws Exception
|
||||
*/
|
||||
public static ByteBuffer getImageByteBuffer(IIOImage image) throws IOException {
|
||||
//Set up the writeParam
|
||||
TIFFImageWriteParam tiffWriteParam = new TIFFImageWriteParam(Locale.US);
|
||||
tiffWriteParam.setCompressionMode(ImageWriteParam.MODE_DISABLED);
|
||||
|
||||
//Get tif writer and set output to file
|
||||
ImageWriter writer = new TIFFImageWriterSpi().createWriterInstance();
|
||||
|
||||
//Get the stream metadata
|
||||
IIOMetadata streamMetadata = writer.getDefaultStreamMetadata(tiffWriteParam);
|
||||
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
ImageOutputStream ios = ImageIO.createImageOutputStream(outputStream);
|
||||
writer.setOutput(ios);
|
||||
writer.write(streamMetadata, new IIOImage(image.getRenderedImage(), null, null), tiffWriteParam);
|
||||
writer.dispose();
|
||||
|
||||
// Read the writed image
|
||||
ios.seek(0);
|
||||
ImageReader reader = new TIFFImageReaderSpi().createReaderInstance();
|
||||
ImageReadParam param = reader.getDefaultReadParam();
|
||||
reader.setInput(ios, true, true);
|
||||
BufferedImage bi;
|
||||
try {
|
||||
bi = reader.read(0, param);
|
||||
} finally {
|
||||
reader.dispose();
|
||||
ios.close();
|
||||
}
|
||||
|
||||
return convertImageData(bi);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts <code>BufferedImage</code> to <code>ByteBuffer</code>.
|
||||
*
|
||||
* @param bi Input image
|
||||
* @return pixel data
|
||||
*/
|
||||
public static ByteBuffer convertImageData(BufferedImage bi) {
|
||||
byte[] pixelData = ((DataBufferByte) bi.getRaster().getDataBuffer()).getData();
|
||||
// return ByteBuffer.wrap(pixelData);
|
||||
ByteBuffer buf = ByteBuffer.allocateDirect(pixelData.length);
|
||||
buf.order(ByteOrder.nativeOrder());
|
||||
buf.put(pixelData);
|
||||
buf.flip();
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads image meta data.
|
||||
*
|
||||
* @param oimage
|
||||
* @return a map of meta data
|
||||
*/
|
||||
public static Map<String, String> readImageData(IIOImage oimage) {
|
||||
Map<String, String> dict = new HashMap<String, String>();
|
||||
|
||||
IIOMetadata imageMetadata = oimage.getMetadata();
|
||||
if (imageMetadata != null) {
|
||||
IIOMetadataNode dimNode = (IIOMetadataNode) imageMetadata.getAsTree("javax_imageio_1.0");
|
||||
NodeList nodes = dimNode.getElementsByTagName("HorizontalPixelSize");
|
||||
int dpiX;
|
||||
if (nodes.getLength() > 0) {
|
||||
float dpcWidth = Float.parseFloat(nodes.item(0).getAttributes().item(0).getNodeValue());
|
||||
dpiX = (int) Math.round(25.4f / dpcWidth);
|
||||
} else {
|
||||
dpiX = Toolkit.getDefaultToolkit().getScreenResolution();
|
||||
}
|
||||
dict.put("dpiX", String.valueOf(dpiX));
|
||||
|
||||
nodes = dimNode.getElementsByTagName("VerticalPixelSize");
|
||||
int dpiY;
|
||||
if (nodes.getLength() > 0) {
|
||||
float dpcHeight = Float.parseFloat(nodes.item(0).getAttributes().item(0).getNodeValue());
|
||||
dpiY = (int) Math.round(25.4f / dpcHeight);
|
||||
} else {
|
||||
dpiY = Toolkit.getDefaultToolkit().getScreenResolution();
|
||||
}
|
||||
dict.put("dpiY", String.valueOf(dpiY));
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
}
|
||||
686
docs-core/src/main/java/com/sismics/tess4j/TessAPI.java
Normal file
686
docs-core/src/main/java/com/sismics/tess4j/TessAPI.java
Normal file
@@ -0,0 +1,686 @@
|
||||
/**
|
||||
* Copyright @ 2012 Quan Nguyen
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.sismics.tess4j;
|
||||
|
||||
import com.sun.jna.*;
|
||||
import com.sun.jna.ptr.*;
|
||||
import java.nio.*;
|
||||
|
||||
/**
|
||||
* A Java wrapper for
|
||||
* <code>Tesseract OCR 3.02 API</code> using
|
||||
* <code>JNA Interface Mapping</code>.
|
||||
*/
|
||||
public interface TessAPI extends Library {
|
||||
|
||||
static final boolean WINDOWS = System.getProperty("os.name").toLowerCase().startsWith("windows");
|
||||
/**
|
||||
* Native library name.
|
||||
*/
|
||||
public static final String LIB_NAME = "libtesseract302";
|
||||
public static final String LIB_NAME_NON_WIN = "tesseract";
|
||||
/**
|
||||
* An instance of the class library.
|
||||
*/
|
||||
public static final TessAPI INSTANCE = (TessAPI) Native.loadLibrary(WINDOWS ? LIB_NAME : LIB_NAME_NON_WIN, TessAPI.class);
|
||||
|
||||
/**
|
||||
* When Tesseract/Cube is initialized we can choose to instantiate/load/run
|
||||
* only the Tesseract part, only the Cube part or both along with the
|
||||
* combiner. The preference of which engine to use is stored in
|
||||
* <code>tessedit_ocr_engine_mode</code>.<br /> <br /> ATTENTION: When
|
||||
* modifying this enum, please make sure to make the appropriate changes to
|
||||
* all the enums mirroring it (e.g. OCREngine in
|
||||
* cityblock/workflow/detection/detection_storage.proto). Such enums will
|
||||
* mention the connection to OcrEngineMode in the comments.
|
||||
*/
|
||||
public static interface TessOcrEngineMode {
|
||||
|
||||
public static final int OEM_TESSERACT_ONLY = (int) 0;
|
||||
public static final int OEM_CUBE_ONLY = (int) 1;
|
||||
public static final int OEM_TESSERACT_CUBE_COMBINED = (int) 2;
|
||||
public static final int OEM_DEFAULT = (int) 3;
|
||||
};
|
||||
|
||||
/**
|
||||
* Possible modes for page layout analysis. These *must* be kept in order of
|
||||
* decreasing amount of layout analysis to be done, except for
|
||||
* <code>OSD_ONLY</code>, so that the inequality test macros below work.
|
||||
*/
|
||||
public static interface TessPageSegMode {
|
||||
|
||||
public static final int PSM_OSD_ONLY = (int) 0;
|
||||
public static final int PSM_AUTO_OSD = (int) 1;
|
||||
public static final int PSM_AUTO_ONLY = (int) 2;
|
||||
public static final int PSM_AUTO = (int) 3;
|
||||
public static final int PSM_SINGLE_COLUMN = (int) 4;
|
||||
public static final int PSM_SINGLE_BLOCK_VERT_TEXT = (int) 5;
|
||||
public static final int PSM_SINGLE_BLOCK = (int) 6;
|
||||
public static final int PSM_SINGLE_LINE = (int) 7;
|
||||
public static final int PSM_SINGLE_WORD = (int) 8;
|
||||
public static final int PSM_CIRCLE_WORD = (int) 9;
|
||||
public static final int PSM_SINGLE_CHAR = (int) 10;
|
||||
public static final int PSM_COUNT = (int) 11;
|
||||
};
|
||||
|
||||
/**
|
||||
* Enum of the elements of the page hierarchy, used in
|
||||
* <code>ResultIterator</code> to provide functions that operate on each
|
||||
* level without having to have 5x as many functions.
|
||||
*/
|
||||
public static interface TessPageIteratorLevel {
|
||||
|
||||
public static final int RIL_BLOCK = (int) 0;
|
||||
public static final int RIL_PARA = (int) 1;
|
||||
public static final int RIL_TEXTLINE = (int) 2;
|
||||
public static final int RIL_WORD = (int) 3;
|
||||
public static final int RIL_SYMBOL = (int) 4;
|
||||
};
|
||||
|
||||
public static interface TessPolyBlockType {
|
||||
|
||||
public static final int PT_UNKNOWN = (int) 0;
|
||||
public static final int PT_FLOWING_TEXT = (int) 1;
|
||||
public static final int PT_HEADING_TEXT = (int) 2;
|
||||
public static final int PT_PULLOUT_TEXT = (int) 3;
|
||||
public static final int PT_TABLE = (int) 4;
|
||||
public static final int PT_VERTICAL_TEXT = (int) 5;
|
||||
public static final int PT_CAPTION_TEXT = (int) 6;
|
||||
public static final int PT_FLOWING_IMAGE = (int) 7;
|
||||
public static final int PT_HEADING_IMAGE = (int) 8;
|
||||
public static final int PT_PULLOUT_IMAGE = (int) 9;
|
||||
public static final int PT_HORZ_LINE = (int) 10;
|
||||
public static final int PT_VERT_LINE = (int) 11;
|
||||
public static final int PT_NOISE = (int) 12;
|
||||
public static final int PT_COUNT = (int) 13;
|
||||
};
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* +------------------+
|
||||
* | 1 Aaaa Aaaa Aaaa |
|
||||
* | Aaa aa aaa aa |
|
||||
* | aaaaaa A aa aaa. |
|
||||
* | 2 |
|
||||
* | ####### c c C |
|
||||
* | ####### c c c |
|
||||
* | < ####### c c c |
|
||||
* | < ####### c c |
|
||||
* | < ####### . c |
|
||||
* | 3 ####### c |
|
||||
* +------------------+
|
||||
* </pre>
|
||||
* Orientation Example:<br />
|
||||
* ====================<br />
|
||||
* Above is a
|
||||
* diagram of some (1) English and (2) Chinese text and a (3) photo
|
||||
* credit.<br />
|
||||
* <br />
|
||||
* Upright Latin characters are represented as A and a. '<' represents
|
||||
* a latin character rotated anti-clockwise 90 degrees. Upright
|
||||
* Chinese characters are represented C and c.<br />
|
||||
* <br />
|
||||
* NOTA BENE: enum values here should match goodoc.proto<br />
|
||||
* <br />
|
||||
* If you orient your head so that "up" aligns with Orientation, then
|
||||
* the characters will appear "right side up" and readable.<br />
|
||||
* <br />
|
||||
* In the example above, both the
|
||||
* English and Chinese paragraphs are oriented so their "up" is the top of
|
||||
* the page (page up). The photo credit is read with one's head turned
|
||||
* leftward ("up" is to page left).<br />
|
||||
* <br /> The values of this enum
|
||||
* match the convention of Tesseract's osdetect.h
|
||||
*/
|
||||
public static interface TessOrientation {
|
||||
|
||||
public static final int ORIENTATION_PAGE_UP = (int) 0;
|
||||
public static final int ORIENTATION_PAGE_RIGHT = (int) 1;
|
||||
public static final int ORIENTATION_PAGE_DOWN = (int) 2;
|
||||
public static final int ORIENTATION_PAGE_LEFT = (int) 3;
|
||||
};
|
||||
|
||||
/**
|
||||
* The grapheme clusters within a line of text are laid out logically in
|
||||
* this direction, judged when looking at the text line rotated so that its
|
||||
* Orientation is "page up".<br /> <br /> For English text, the writing
|
||||
* direction is left-to-right. For the Chinese text in the above example,
|
||||
* the writing direction is top-to-bottom.
|
||||
*/
|
||||
public static interface TessWritingDirection {
|
||||
|
||||
public static final int WRITING_DIRECTION_LEFT_TO_RIGHT = (int) 0;
|
||||
public static final int WRITING_DIRECTION_RIGHT_TO_LEFT = (int) 1;
|
||||
public static final int WRITING_DIRECTION_TOP_TO_BOTTOM = (int) 2;
|
||||
};
|
||||
|
||||
/**
|
||||
* The text lines are read in the given sequence.<br /> <br /> In English,
|
||||
* the order is top-to-bottom. In Chinese, vertical text lines are read
|
||||
* right-to-left. Mongolian is written in vertical columns top to bottom
|
||||
* like Chinese, but the lines order left-to right.<br /> <br /> Note that
|
||||
* only some combinations make sense. For example,
|
||||
* <code>WRITING_DIRECTION_LEFT_TO_RIGHT</code> implies
|
||||
* <code>TEXTLINE_ORDER_TOP_TO_BOTTOM</code>.
|
||||
*/
|
||||
public static interface TessTextlineOrder {
|
||||
|
||||
public static final int TEXTLINE_ORDER_LEFT_TO_RIGHT = (int) 0;
|
||||
public static final int TEXTLINE_ORDER_RIGHT_TO_LEFT = (int) 1;
|
||||
public static final int TEXTLINE_ORDER_TOP_TO_BOTTOM = (int) 2;
|
||||
};
|
||||
public static final int TRUE = (int) 1;
|
||||
public static final int FALSE = (int) 0;
|
||||
|
||||
/**
|
||||
* Returns the version identifier.
|
||||
*/
|
||||
String TessVersion();
|
||||
|
||||
void TessDeleteText(Pointer text);
|
||||
|
||||
void TessDeleteTextArray(PointerByReference arr);
|
||||
|
||||
void TessDeleteIntArray(IntBuffer arr);
|
||||
|
||||
/**
|
||||
* Creates an instance of the base class for all Tesseract APIs.
|
||||
*/
|
||||
TessAPI.TessBaseAPI TessBaseAPICreate();
|
||||
|
||||
/**
|
||||
* Disposes the TesseractAPI instance.
|
||||
*/
|
||||
void TessBaseAPIDelete(TessAPI.TessBaseAPI handle);
|
||||
|
||||
/**
|
||||
* Set the name of the input file. Needed only for training and reading a
|
||||
* UNLV zone file.
|
||||
*/
|
||||
void TessBaseAPISetInputName(TessAPI.TessBaseAPI handle, String name);
|
||||
|
||||
/**
|
||||
* Set the name of the bonus output files. Needed only for debugging.
|
||||
*/
|
||||
void TessBaseAPISetOutputName(TessAPI.TessBaseAPI handle, String name);
|
||||
|
||||
/**
|
||||
* Set the value of an internal "parameter." Supply the name of the
|
||||
* parameter and the value as a string, just as you would in a config file.
|
||||
* Returns false if the name lookup failed. E.g.,
|
||||
* <code>SetVariable("tessedit_char_blacklist", "xyz");</code> to ignore x,
|
||||
* y and z. Or
|
||||
* <code>SetVariable("classify_bln_numeric_mode", "1");</code> to set
|
||||
* numeric-only mode.
|
||||
* <code>SetVariable</code> may be used before
|
||||
* <code>Init</code>, but settings will revert to defaults on
|
||||
* <code>End()</code>.<br /> <br /> Note: Must be called after
|
||||
* <code>Init()</code>. Only works for non-init variables (init variables
|
||||
* should be passed to
|
||||
* <code>Init()</code>).
|
||||
*/
|
||||
int TessBaseAPISetVariable(TessAPI.TessBaseAPI handle, String name, String value);
|
||||
|
||||
/**
|
||||
* Returns true (1) if the parameter was found among Tesseract parameters.
|
||||
* Fills in value with the value of the parameter.
|
||||
*/
|
||||
int TessBaseAPIGetIntVariable(TessAPI.TessBaseAPI handle, String name, IntBuffer value);
|
||||
|
||||
int TessBaseAPIGetBoolVariable(TessAPI.TessBaseAPI handle, String name, IntBuffer value);
|
||||
|
||||
int TessBaseAPIGetDoubleVariable(TessAPI.TessBaseAPI handle, String name, DoubleBuffer value);
|
||||
|
||||
String TessBaseAPIGetStringVariable(TessAPI.TessBaseAPI handle, String name);
|
||||
|
||||
/**
|
||||
* Print Tesseract parameters to the given file.<br /> <br /> Note: Must not
|
||||
* be the first method called after instance create.
|
||||
*/
|
||||
void TessBaseAPIPrintVariablesToFile(TessAPI.TessBaseAPI handle, String filename);
|
||||
|
||||
/**
|
||||
* Instances are now mostly thread-safe and totally independent, but some
|
||||
* global parameters remain. Basically it is safe to use multiple
|
||||
* TessBaseAPIs in different threads in parallel, UNLESS: you use
|
||||
* <code>SetVariable</code> on some of the Params in classify and textord.
|
||||
* If you do, then the effect will be to change it for all your
|
||||
* instances.<br /> <br /> Start tesseract. Returns zero on success and -1
|
||||
* on failure. NOTE that the only members that may be called before Init are
|
||||
* those listed above here in the class definition.<br /> <br /> The
|
||||
* <code>datapath</code> must be the name of the parent directory of
|
||||
* tessdata and must end in / . Any name after the last / will be stripped.
|
||||
* The language is (usually) an
|
||||
* <code>ISO 639-3</code> string or
|
||||
* <code>NULL</code> will default to eng. It is entirely safe (and
|
||||
* eventually will be efficient too) to call Init multiple times on the same
|
||||
* instance to change language, or just to reset the classifier. The
|
||||
* language may be a string of the form [~]<lang>[+[~]<lang>]* indicating
|
||||
* that multiple languages are to be loaded. E.g., hin+eng will load Hindi
|
||||
* and English. Languages may specify internally that they want to be loaded
|
||||
* with one or more other languages, so the ~ sign is available to override
|
||||
* that. E.g., if hin were set to load eng by default, then hin+~eng would
|
||||
* force loading only hin. The number of loaded languages is limited only by
|
||||
* memory, with the caveat that loading additional languages will impact
|
||||
* both speed and accuracy, as there is more work to do to decide on the
|
||||
* applicable language, and there is more chance of hallucinating incorrect
|
||||
* words. WARNING: On changing languages, all Tesseract parameters are reset
|
||||
* back to their default values. (Which may vary between languages.) If you
|
||||
* have a rare need to set a Variable that controls initialization for a
|
||||
* second call to
|
||||
* <code>Init</code> you should explicitly call
|
||||
* <code>End()</code> and then use
|
||||
* <code>SetVariable</code> before
|
||||
* <code>Init</code>. This is only a very rare use case, since there are
|
||||
* very few uses that require any parameters to be set before
|
||||
* <code>Init</code>.<br /> <br /> If
|
||||
* <code>set_only_non_debug_params</code> is true, only params that do not
|
||||
* contain "debug" in the name will be set.
|
||||
*/
|
||||
int TessBaseAPIInit1(TessAPI.TessBaseAPI handle, String datapath, String language, int oem, PointerByReference configs, int configs_size);
|
||||
|
||||
int TessBaseAPIInit2(TessAPI.TessBaseAPI handle, String datapath, String language, int oem);
|
||||
|
||||
int TessBaseAPIInit3(TessAPI.TessBaseAPI handle, String datapath, String language);
|
||||
|
||||
/**
|
||||
* Returns the languages string used in the last valid initialization. If
|
||||
* the last initialization specified "deu+hin" then that will be returned.
|
||||
* If hin loaded eng automatically as well, then that will not be included
|
||||
* in this list. To find the languages actually loaded, use
|
||||
* <code>GetLoadedLanguagesAsVector</code>. The returned string should NOT
|
||||
* be deleted.
|
||||
*/
|
||||
String TessBaseAPIGetInitLanguagesAsString(TessAPI.TessBaseAPI handle);
|
||||
|
||||
/**
|
||||
* Returns the loaded languages in the vector of STRINGs. Includes all
|
||||
* languages loaded by the last
|
||||
* <code>Init</code>, including those loaded as dependencies of other loaded
|
||||
* languages.
|
||||
*/
|
||||
PointerByReference TessBaseAPIGetLoadedLanguagesAsVector(TessAPI.TessBaseAPI handle);
|
||||
|
||||
/**
|
||||
* Returns the available languages in the vector of STRINGs.
|
||||
*/
|
||||
PointerByReference TessBaseAPIGetAvailableLanguagesAsVector(TessAPI.TessBaseAPI handle);
|
||||
|
||||
/**
|
||||
* Init only the lang model component of Tesseract. The only functions that
|
||||
* work after this init are
|
||||
* <code>SetVariable</code> and
|
||||
* <code>IsValidWord</code>. WARNING: temporary! This function will be
|
||||
* removed from here and placed in a separate API at some future time.
|
||||
*/
|
||||
int TessBaseAPIInitLangMod(TessAPI.TessBaseAPI handle, String datapath, String language);
|
||||
|
||||
/**
|
||||
* Init only for page layout analysis. Use only for calls to
|
||||
* <code>SetImage</code> and
|
||||
* <code>AnalysePage</code>. Calls that attempt recognition will generate an
|
||||
* error.
|
||||
*/
|
||||
void TessBaseAPIInitForAnalysePage(TessAPI.TessBaseAPI handle);
|
||||
|
||||
/**
|
||||
* Read a "config" file containing a set of param, value pairs. Searches the
|
||||
* standard places:
|
||||
* <code>tessdata/configs</code>,
|
||||
* <code>tessdata/tessconfigs</code> and also accepts a relative or absolute
|
||||
* path name. Note: only non-init params will be set (init params are set by
|
||||
* <code>Init()</code>).
|
||||
*/
|
||||
void TessBaseAPIReadConfigFile(TessAPI.TessBaseAPI handle, String filename, int init_only);
|
||||
|
||||
/**
|
||||
* Set the current page segmentation mode. Defaults to PSM_SINGLE_BLOCK. The
|
||||
* mode is stored as an IntParam so it can also be modified by
|
||||
* <code>ReadConfigFile</code> or
|
||||
* <code>SetVariable("tessedit_pageseg_mode", mode as string)</code>.
|
||||
*/
|
||||
void TessBaseAPISetPageSegMode(TessAPI.TessBaseAPI handle, int mode);
|
||||
|
||||
/**
|
||||
* Return the current page segmentation mode.
|
||||
*/
|
||||
int TessBaseAPIGetPageSegMode(TessAPI.TessBaseAPI handle);
|
||||
|
||||
/**
|
||||
* Recognize a rectangle from an image and return the result as a string.
|
||||
* May be called many times for a single
|
||||
* <code>Init</code>. Currently has no error checking. Greyscale of 8 and
|
||||
* color of 24 or 32 bits per pixel may be given. Palette color images will
|
||||
* not work properly and must be converted to 24 bit. Binary images of 1 bit
|
||||
* per pixel may also be given but they must be byte packed with the MSB of
|
||||
* the first byte being the first pixel, and a 1 represents WHITE. For
|
||||
* binary images set bytes_per_pixel=0. The recognized text is returned as a
|
||||
* char* which is coded as UTF8 and must be freed with the delete []
|
||||
* operator.<br /> <br /> Note that
|
||||
* <code>TesseractRect</code> is the simplified convenience interface. For
|
||||
* advanced uses, use
|
||||
* <code>SetImage</code>, (optionally)
|
||||
* <code>SetRectangle</code>,
|
||||
* <code>Recognize</code>, and one or more of the
|
||||
* <code>Get*Text</code> functions below.
|
||||
*/
|
||||
Pointer TessBaseAPIRect(TessAPI.TessBaseAPI handle, ByteBuffer imagedata, int bytes_per_pixel, int bytes_per_line, int left, int top, int width, int height);
|
||||
|
||||
/**
|
||||
* Call between pages or documents etc to free up memory and forget adaptive
|
||||
* data.
|
||||
*/
|
||||
void TessBaseAPIClearAdaptiveClassifier(TessAPI.TessBaseAPI handle);
|
||||
|
||||
/**
|
||||
* Provide an image for Tesseract to recognize. Format is as TesseractRect
|
||||
* above. Does not copy the image buffer, or take ownership. The source
|
||||
* image may be destroyed after Recognize is called, either explicitly or
|
||||
* implicitly via one of the
|
||||
* <code>Get*Text</code> functions.
|
||||
* <code>SetImage</code> clears all recognition results, and sets the
|
||||
* rectangle to the full image, so it may be followed immediately by a
|
||||
* <code>GetUTF8Text</code>, and it will automatically perform recognition.
|
||||
*/
|
||||
void TessBaseAPISetImage(TessAPI.TessBaseAPI handle, ByteBuffer imagedata, int width, int height, int bytes_per_pixel, int bytes_per_line);
|
||||
|
||||
/**
|
||||
* Set the resolution of the source image in pixels per inch so font size
|
||||
* information can be calculated in results. Call this after SetImage().
|
||||
*/
|
||||
void TessBaseAPISetSourceResolution(TessAPI.TessBaseAPI handle, int ppi);
|
||||
|
||||
/**
|
||||
* Restrict recognition to a sub-rectangle of the image. Call after
|
||||
* <code>SetImage</code>. Each
|
||||
* <code>SetRectangle</code> clears the recognition results so multiple
|
||||
* rectangles can be recognized with the same image.
|
||||
*/
|
||||
void TessBaseAPISetRectangle(TessAPI.TessBaseAPI handle, int left, int top, int width, int height);
|
||||
|
||||
/** Scale factor from original image. */
|
||||
int TessBaseAPIGetThresholdedImageScaleFactor(TessAPI.TessBaseAPI handle);
|
||||
|
||||
/** Dump the internal binary image to a PGM file. */
|
||||
void TessBaseAPIDumpPGM(TessAPI.TessBaseAPI handle, String filename);
|
||||
|
||||
/**
|
||||
* Runs page layout analysis in the mode set by SetPageSegMode. May
|
||||
* optionally be called prior to Recognize to get access to just the page
|
||||
* layout results. Returns an iterator to the results. Returns NULL on
|
||||
* error. The returned iterator must be deleted after use. WARNING! This
|
||||
* class points to data held within the TessBaseAPI class, and therefore can
|
||||
* only be used while the TessBaseAPI class still exists and has not been
|
||||
* subjected to a call of
|
||||
* <code>Init</code>,
|
||||
* <code>SetImage</code>,
|
||||
* <code>Recognize</code>,
|
||||
* <code>Clear</code>,
|
||||
* <code>End</code>, DetectOS, or anything else that changes the internal
|
||||
* PAGE_RES.
|
||||
*/
|
||||
TessAPI.TessPageIterator TessBaseAPIAnalyseLayout(TessAPI.TessBaseAPI handle);
|
||||
|
||||
/**
|
||||
* Recognize the image from SetAndThresholdImage, generating Tesseract
|
||||
* internal structures. Returns 0 on success. Optional. The
|
||||
* <code>Get*Text</code> functions below will call
|
||||
* <code>Recognize</code> if needed. After Recognize, the output is kept
|
||||
* internally until the next
|
||||
* <code>SetImage</code>.
|
||||
*/
|
||||
int TessBaseAPIRecognize(TessAPI.TessBaseAPI handle, TessAPI.ETEXT_DESC monitor);
|
||||
|
||||
/**
|
||||
* Variant on Recognize used for testing chopper.
|
||||
*/
|
||||
int TessBaseAPIRecognizeForChopTest(TessAPI.TessBaseAPI handle, TessAPI.ETEXT_DESC monitor);
|
||||
|
||||
/**
|
||||
* Get a reading-order iterator to the results of LayoutAnalysis and/or
|
||||
* Recognize. The returned iterator must be deleted after use. WARNING! This
|
||||
* class points to data held within the TessBaseAPI class, and therefore can
|
||||
* only be used while the TessBaseAPI class still exists and has not been
|
||||
* subjected to a call of
|
||||
* <code>Init</code>,
|
||||
* <code>SetImage</code>,
|
||||
* <code>Recognize</code>,
|
||||
* <code>Clear</code>,
|
||||
* <code>End</code>, DetectOS, or anything else that changes the internal
|
||||
* PAGE_RES.
|
||||
*/
|
||||
TessAPI.TessResultIterator TessBaseAPIGetIterator(TessAPI.TessBaseAPI handle);
|
||||
|
||||
/**
|
||||
* Get a mutable iterator to the results of LayoutAnalysis and/or Recognize.
|
||||
* The returned iterator must be deleted after use.
|
||||
* WARNING! This class points to data held within the TessBaseAPI class, and
|
||||
* therefore can only be used while the TessBaseAPI class still exists and
|
||||
* has not been subjected to a call of Init, SetImage, Recognize, Clear, End
|
||||
* DetectOS, or anything else that changes the internal PAGE_RES.
|
||||
*/
|
||||
TessAPI.TessMutableIterator TessBaseAPIGetMutableIterator(TessAPI.TessBaseAPI handle);
|
||||
|
||||
/**
|
||||
* Recognizes all the pages in the named file, as a multi-page tiff or list
|
||||
* of filenames, or single image, and gets the appropriate kind of text
|
||||
* according to parameters:
|
||||
* <code>tessedit_create_boxfile</code>,
|
||||
* <code>tessedit_make_boxes_from_boxes</code>,
|
||||
* <code>tessedit_write_unlv</code>,
|
||||
* <code>tessedit_create_hocr</code>. Calls ProcessPage on each page in the
|
||||
* input file, which may be a multi-page tiff, single-page other file
|
||||
* format, or a plain text list of images to read. If tessedit_page_number
|
||||
* is non-negative, processing begins at that page of a multi-page tiff
|
||||
* file, or filelist. The text is returned in text_out. Returns false on
|
||||
* error. If non-zero timeout_millisec terminates processing after the
|
||||
* timeout on a single page. If non-NULL and non-empty, and some page fails
|
||||
* for some reason, the page is reprocessed with the retry_config config
|
||||
* file. Useful for interactively debugging a bad page.
|
||||
*/
|
||||
Pointer TessBaseAPIProcessPages(TessAPI.TessBaseAPI handle, String filename, String retry_config, int timeout_millisec);
|
||||
|
||||
/**
|
||||
* The recognized text is returned as a char* which is coded as UTF-8 and
|
||||
* must be freed with the delete [] operator.
|
||||
*/
|
||||
Pointer TessBaseAPIGetUTF8Text(TessAPI.TessBaseAPI handle);
|
||||
|
||||
/**
|
||||
* Make a HTML-formatted string with hOCR markup from the internal data
|
||||
* structures. page_number is 0-based but will appear in the output as
|
||||
* 1-based.
|
||||
*/
|
||||
Pointer TessBaseAPIGetHOCRText(TessAPI.TessBaseAPI handle, int page_number);
|
||||
|
||||
/**
|
||||
* The recognized text is returned as a char* which is coded in the same
|
||||
* format as a box file used in training. Returned string must be freed with
|
||||
* the delete [] operator. Constructs coordinates in the original image -
|
||||
* not just the rectangle. page_number is a 0-based page index that will
|
||||
* appear in the box file.
|
||||
*/
|
||||
Pointer TessBaseAPIGetBoxText(TessAPI.TessBaseAPI handle, int page_number);
|
||||
|
||||
/**
|
||||
* The recognized text is returned as a char* which is coded as UNLV format
|
||||
* Latin-1 with specific reject and suspect codes and must be freed with the
|
||||
* delete [] operator.
|
||||
*/
|
||||
Pointer TessBaseAPIGetUNLVText(TessAPI.TessBaseAPI handle);
|
||||
|
||||
/**
|
||||
* Returns the (average) confidence value between 0 and 100.
|
||||
*/
|
||||
int TessBaseAPIMeanTextConf(TessAPI.TessBaseAPI handle);
|
||||
|
||||
/**
|
||||
* Returns all word confidences (between 0 and 100) in an array, terminated
|
||||
* by -1. The calling function must delete [] after use. The number of
|
||||
* confidences should correspond to the number of space-delimited words in
|
||||
* GetUTF8Text.
|
||||
*/
|
||||
IntByReference TessBaseAPIAllWordConfidences(TessAPI.TessBaseAPI handle);
|
||||
|
||||
/**
|
||||
* Applies the given word to the adaptive classifier if possible. The word
|
||||
* must be SPACE-DELIMITED UTF-8 - l i k e t h i s , so it can tell the
|
||||
* boundaries of the graphemes. Assumes that SetImage/SetRectangle have been
|
||||
* used to set the image to the given word. The mode arg should be
|
||||
* PSM_SINGLE_WORD or PSM_CIRCLE_WORD, as that will be used to control
|
||||
* layout analysis. The currently set PageSegMode is preserved. Returns
|
||||
* false if adaption was not possible for some reason.
|
||||
*/
|
||||
int TessBaseAPIAdaptToWordStr(TessAPI.TessBaseAPI handle, int mode, String wordstr);
|
||||
|
||||
/**
|
||||
* Free up recognition results and any stored image data, without actually
|
||||
* freeing any recognition data that would be time-consuming to reload.
|
||||
* Afterwards, you must call
|
||||
* <code>SetImage</code> or
|
||||
* <code>TesseractRect</code> before doing any
|
||||
* <code>Recognize</code> or
|
||||
* <code>Get*</code> operation.
|
||||
*/
|
||||
void TessBaseAPIClear(TessAPI.TessBaseAPI handle);
|
||||
|
||||
/**
|
||||
* Close down tesseract and free up all memory.
|
||||
* <code>End()</code> is equivalent to destructing and reconstructing your
|
||||
* TessBaseAPI. Once
|
||||
* <code>End()</code> has been used, none of the other API functions may be
|
||||
* used other than
|
||||
* <code>Init</code> and anything declared above it in the class definition.
|
||||
*/
|
||||
void TessBaseAPIEnd(TessAPI.TessBaseAPI handle);
|
||||
|
||||
/**
|
||||
* Check whether a word is valid according to Tesseract's language model.
|
||||
*
|
||||
* @return 0 if the word is invalid, non-zero if valid. @warning temporary!
|
||||
* This function will be removed from here and placed in a separate API at
|
||||
* some future time.
|
||||
*/
|
||||
int TessBaseAPIIsValidWord(TessAPI.TessBaseAPI handle, String word);
|
||||
|
||||
int TessBaseAPIGetTextDirection(TessAPI.TessBaseAPI handle, IntBuffer out_offset, FloatBuffer out_slope);
|
||||
|
||||
/**
|
||||
* This method returns the string form of the specified unichar.
|
||||
*/
|
||||
String TessBaseAPIGetUnichar(TessAPI.TessBaseAPI handle, int unichar_id);
|
||||
|
||||
/* Page iterator */
|
||||
void TessPageIteratorDelete(TessAPI.TessPageIterator handle);
|
||||
|
||||
TessAPI.TessPageIterator TessPageIteratorCopy(TessAPI.TessPageIterator handle);
|
||||
|
||||
void TessPageIteratorBegin(TessAPI.TessPageIterator handle);
|
||||
|
||||
int TessPageIteratorNext(TessAPI.TessPageIterator handle, int level);
|
||||
|
||||
int TessPageIteratorIsAtBeginningOf(TessAPI.TessPageIterator handle, int level);
|
||||
|
||||
int TessPageIteratorIsAtFinalElement(TessAPI.TessPageIterator handle, int level, int element);
|
||||
|
||||
int TessPageIteratorBoundingBox(TessAPI.TessPageIterator handle, int level, IntBuffer left, IntBuffer top, IntBuffer right, IntBuffer bottom);
|
||||
|
||||
int TessPageIteratorBlockType(TessAPI.TessPageIterator handle);
|
||||
|
||||
int TessPageIteratorBaseline(TessAPI.TessPageIterator handle, int level, IntBuffer x1, IntBuffer y1, IntBuffer x2, IntBuffer y2);
|
||||
|
||||
void TessPageIteratorOrientation(TessAPI.TessPageIterator handle, IntBuffer orientation, IntBuffer writing_direction, IntBuffer textline_order, FloatBuffer deskew_angle);
|
||||
|
||||
/* Result iterator */
|
||||
void TessResultIteratorDelete(TessAPI.TessResultIterator handle);
|
||||
|
||||
TessAPI.TessResultIterator TessResultIteratorCopy(TessAPI.TessResultIterator handle);
|
||||
|
||||
TessAPI.TessPageIterator TessResultIteratorGetPageIterator(TessAPI.TessResultIterator handle);
|
||||
|
||||
TessAPI.TessPageIterator TessResultIteratorGetPageIteratorConst(TessAPI.TessResultIterator handle);
|
||||
|
||||
Pointer TessResultIteratorGetUTF8Text(TessAPI.TessResultIterator handle, int level);
|
||||
|
||||
float TessResultIteratorConfidence(TessAPI.TessResultIterator handle, int level);
|
||||
|
||||
String TessResultIteratorWordFontAttributes(TessAPI.TessResultIterator handle, IntBuffer is_bold, IntBuffer is_italic, IntBuffer is_underlined, IntBuffer is_monospace, IntBuffer is_serif, IntBuffer is_smallcaps, IntBuffer pointsize, IntBuffer font_id);
|
||||
|
||||
int TessResultIteratorWordIsFromDictionary(TessAPI.TessResultIterator handle);
|
||||
|
||||
int TessResultIteratorWordIsNumeric(TessAPI.TessResultIterator handle);
|
||||
|
||||
int TessResultIteratorSymbolIsSuperscript(TessAPI.TessResultIterator handle);
|
||||
|
||||
int TessResultIteratorSymbolIsSubscript(TessAPI.TessResultIterator handle);
|
||||
|
||||
int TessResultIteratorSymbolIsDropcap(TessAPI.TessResultIterator handle);
|
||||
|
||||
public static class TessBaseAPI extends PointerType {
|
||||
|
||||
public TessBaseAPI(Pointer address) {
|
||||
super(address);
|
||||
}
|
||||
|
||||
public TessBaseAPI() {
|
||||
super();
|
||||
}
|
||||
};
|
||||
|
||||
public static class ETEXT_DESC extends PointerType {
|
||||
|
||||
public ETEXT_DESC(Pointer address) {
|
||||
super(address);
|
||||
}
|
||||
|
||||
public ETEXT_DESC() {
|
||||
super();
|
||||
}
|
||||
};
|
||||
|
||||
public static class TessPageIterator extends PointerType {
|
||||
|
||||
public TessPageIterator(Pointer address) {
|
||||
super(address);
|
||||
}
|
||||
|
||||
public TessPageIterator() {
|
||||
super();
|
||||
}
|
||||
};
|
||||
|
||||
public static class TessMutableIterator extends PointerType {
|
||||
|
||||
public TessMutableIterator(Pointer address) {
|
||||
super(address);
|
||||
}
|
||||
|
||||
public TessMutableIterator() {
|
||||
super();
|
||||
}
|
||||
};
|
||||
|
||||
public static class TessResultIterator extends PointerType {
|
||||
|
||||
public TessResultIterator(Pointer address) {
|
||||
super(address);
|
||||
}
|
||||
|
||||
public TessResultIterator() {
|
||||
super();
|
||||
}
|
||||
};
|
||||
}
|
||||
256
docs-core/src/main/java/com/sismics/tess4j/Tesseract.java
Normal file
256
docs-core/src/main/java/com/sismics/tess4j/Tesseract.java
Normal file
@@ -0,0 +1,256 @@
|
||||
/**
|
||||
* Copyright @ 2012 Quan Nguyen
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package com.sismics.tess4j;
|
||||
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.RenderedImage;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.imageio.IIOImage;
|
||||
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
/**
|
||||
* An object layer on top of
|
||||
* <code>TessAPI</code>, provides character recognition support for common image
|
||||
* formats, and multi-page TIFF images beyond the uncompressed, binary TIFF
|
||||
* format supported by Tesseract OCR engine. The extended capabilities are
|
||||
* provided by the
|
||||
* <code>Java Advanced Imaging Image I/O Tools</code>. <br /><br /> Support for
|
||||
* PDF documents is available through
|
||||
* <code>Ghost4J</code>, a
|
||||
* <code>JNA</code> wrapper for
|
||||
* <code>GPL Ghostscript</code>, which should be installed and included in
|
||||
* system path. <br /><br /> Any program that uses the library will need to
|
||||
* ensure that the required libraries (the
|
||||
* <code>.jar</code> files for
|
||||
* <code>jna</code>,
|
||||
* <code>jai-imageio</code>, and
|
||||
* <code>ghost4j</code>) are in its compile and run-time
|
||||
* <code>classpath</code>.
|
||||
*/
|
||||
public class Tesseract {
|
||||
|
||||
private static Tesseract instance;
|
||||
private final static Rectangle EMPTY_RECTANGLE = new Rectangle();
|
||||
private String language = "eng";
|
||||
private String datapath = "tessdata";
|
||||
private int psm = TessAPI.TessPageSegMode.PSM_AUTO;
|
||||
private boolean hocr;
|
||||
private int pageNum;
|
||||
private int ocrEngineMode = TessAPI.TessOcrEngineMode.OEM_DEFAULT;
|
||||
private Properties prop = new Properties();
|
||||
public final static String htmlBeginTag =
|
||||
"<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\""
|
||||
+ " \"http://www.w3.org/TR/html4/loose.dtd\">\n"
|
||||
+ "<html>\n<head>\n<title></title>\n"
|
||||
+ "<meta http-equiv=\"Content-Type\" content=\"text/html;"
|
||||
+ "charset=utf-8\" />\n<meta name='ocr-system' content='tesseract'/>\n"
|
||||
+ "</head>\n<body>\n";
|
||||
public final static String htmlEndTag = "</body>\n</html>\n";
|
||||
|
||||
/**
|
||||
* Private constructor.
|
||||
*/
|
||||
private Tesseract() {
|
||||
System.setProperty("jna.encoding", "UTF8");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an instance of the class library.
|
||||
*
|
||||
* @return instance
|
||||
*/
|
||||
public static synchronized Tesseract getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new Tesseract();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets tessdata path.
|
||||
*
|
||||
* @param datapath the tessdata path to set
|
||||
*/
|
||||
public void setDatapath(String datapath) {
|
||||
this.datapath = datapath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets language for OCR.
|
||||
*
|
||||
* @param language the language code, which follows ISO 639-3 standard.
|
||||
*/
|
||||
public void setLanguage(String language) {
|
||||
this.language = language;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets OCR engine mode.
|
||||
*
|
||||
* @param ocrEngineMode the OcrEngineMode to set
|
||||
*/
|
||||
public void setOcrEngineMode(int ocrEngineMode) {
|
||||
this.ocrEngineMode = ocrEngineMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets page segmentation mode.
|
||||
*
|
||||
* @param mode the page segmentation mode to set
|
||||
*/
|
||||
public void setPageSegMode(int mode) {
|
||||
this.psm = mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables hocr output.
|
||||
*
|
||||
* @param hocr to enable or disable hocr output
|
||||
*/
|
||||
public void setHocr(boolean hocr) {
|
||||
this.hocr = hocr;
|
||||
prop.setProperty("tessedit_create_hocr", hocr ? "1" : "0");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of Tesseract's internal parameter.
|
||||
*
|
||||
* @param key variable name, e.g.,
|
||||
* <code>tessedit_create_hocr</code>,
|
||||
* <code>tessedit_char_whitelist</code>, etc.
|
||||
* @param value value for corresponding variable, e.g., "1", "0",
|
||||
* "0123456789", etc.
|
||||
*/
|
||||
public void setTessVariable(String key, String value) {
|
||||
prop.setProperty(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs OCR operation.
|
||||
*
|
||||
* @param bi a buffered image
|
||||
* @return the recognized text
|
||||
* @throws TesseractException
|
||||
*/
|
||||
public String doOCR(BufferedImage bi) throws TesseractException {
|
||||
return doOCR(bi, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs OCR operation.
|
||||
*
|
||||
* @param bi a buffered image
|
||||
* @param rect the bounding rectangle defines the region of the image to be
|
||||
* recognized. A rectangle of zero dimension or
|
||||
* <code>null</code> indicates the whole image.
|
||||
* @return the recognized text
|
||||
* @throws TesseractException
|
||||
*/
|
||||
public String doOCR(BufferedImage bi, Rectangle rect) throws TesseractException {
|
||||
IIOImage oimage = new IIOImage(bi, null, null);
|
||||
List<IIOImage> imageList = new ArrayList<IIOImage>();
|
||||
imageList.add(oimage);
|
||||
return doOCR(imageList, rect);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs OCR operation.
|
||||
*
|
||||
* @param imageList a list of
|
||||
* <code>IIOImage</code> objects
|
||||
* @param rect the bounding rectangle defines the region of the image to be
|
||||
* recognized. A rectangle of zero dimension or
|
||||
* <code>null</code> indicates the whole image.
|
||||
* @return the recognized text
|
||||
* @throws TesseractException
|
||||
*/
|
||||
public String doOCR(List<IIOImage> imageList, Rectangle rect) throws TesseractException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
pageNum = 0;
|
||||
|
||||
for (IIOImage oimage : imageList) {
|
||||
pageNum++;
|
||||
try {
|
||||
ByteBuffer buf = ImageIOHelper.getImageByteBuffer(oimage);
|
||||
RenderedImage ri = oimage.getRenderedImage();
|
||||
String pageText = doOCR(ri.getWidth(), ri.getHeight(), buf, rect, ri.getColorModel().getPixelSize());
|
||||
sb.append(pageText);
|
||||
} catch (IOException ioe) {
|
||||
//skip the problematic image
|
||||
System.err.println(ioe.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (hocr) {
|
||||
sb.insert(0, htmlBeginTag).append(htmlEndTag);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs OCR operation. Use
|
||||
* <code>SetImage</code>, (optionally)
|
||||
* <code>SetRectangle</code>, and one or more of the
|
||||
* <code>Get*Text</code> functions.
|
||||
*
|
||||
* @param xsize width of image
|
||||
* @param ysize height of image
|
||||
* @param buf pixel data
|
||||
* @param rect the bounding rectangle defines the region of the image to be
|
||||
* recognized. A rectangle of zero dimension or
|
||||
* <code>null</code> indicates the whole image.
|
||||
* @param bpp bits per pixel, represents the bit depth of the image, with 1
|
||||
* for binary bitmap, 8 for gray, and 24 for color RGB.
|
||||
* @return the recognized text
|
||||
* @throws TesseractException
|
||||
*/
|
||||
public String doOCR(int xsize, int ysize, ByteBuffer buf, Rectangle rect, int bpp) throws TesseractException {
|
||||
TessAPI api = TessAPI.INSTANCE;
|
||||
TessAPI.TessBaseAPI handle = api.TessBaseAPICreate();
|
||||
api.TessBaseAPIInit2(handle, datapath, language, ocrEngineMode);
|
||||
api.TessBaseAPISetPageSegMode(handle, psm);
|
||||
|
||||
Enumeration<?> em = prop.propertyNames();
|
||||
while (em.hasMoreElements()) {
|
||||
String key = (String) em.nextElement();
|
||||
api.TessBaseAPISetVariable(handle, key, prop.getProperty(key));
|
||||
}
|
||||
|
||||
int bytespp = bpp / 8;
|
||||
int bytespl = (int) Math.ceil(xsize * bpp / 8.0);
|
||||
api.TessBaseAPISetImage(handle, buf, xsize, ysize, bytespp, bytespl);
|
||||
|
||||
if (rect != null && !rect.equals(EMPTY_RECTANGLE)) {
|
||||
api.TessBaseAPISetRectangle(handle, rect.x, rect.y, rect.width, rect.height);
|
||||
}
|
||||
|
||||
Pointer utf8Text = hocr ? api.TessBaseAPIGetHOCRText(handle, pageNum - 1) : api.TessBaseAPIGetUTF8Text(handle);
|
||||
String str = utf8Text.getString(0);
|
||||
api.TessDeleteText(utf8Text);
|
||||
api.TessBaseAPIDelete(handle);
|
||||
|
||||
return str;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright @ 2010 Quan Nguyen
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.sismics.tess4j;
|
||||
|
||||
public class TesseractException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public TesseractException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public TesseractException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public TesseractException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public TesseractException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,17 @@
|
||||
package com.sismics.util;
|
||||
|
||||
import com.sismics.util.mime.MimeType;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Iterator;
|
||||
|
||||
import javax.imageio.IIOImage;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageWriteParam;
|
||||
import javax.imageio.ImageWriter;
|
||||
import javax.imageio.stream.FileImageOutputStream;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
|
||||
import com.sismics.util.mime.MimeType;
|
||||
|
||||
/**
|
||||
* Image processing utilities.
|
||||
@@ -23,26 +24,26 @@ public class ImageUtil {
|
||||
* Write a high quality JPEG.
|
||||
*
|
||||
* @param image
|
||||
* @param file
|
||||
* @param outputStream Output stream
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void writeJpeg(BufferedImage image, File file) throws IOException {
|
||||
public static void writeJpeg(BufferedImage image, OutputStream outputStream) throws IOException {
|
||||
Iterator<ImageWriter> iter = ImageIO.getImageWritersByFormatName("jpeg");
|
||||
ImageWriter writer = null;
|
||||
FileImageOutputStream output = null;
|
||||
ImageOutputStream imageOutputStream = null;
|
||||
try {
|
||||
writer = (ImageWriter) iter.next();
|
||||
ImageWriteParam iwp = writer.getDefaultWriteParam();
|
||||
iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
|
||||
iwp.setCompressionQuality(1.f);
|
||||
output = new FileImageOutputStream(file);
|
||||
writer.setOutput(output);
|
||||
imageOutputStream = ImageIO.createImageOutputStream(outputStream);
|
||||
writer.setOutput(imageOutputStream);
|
||||
IIOImage iioImage = new IIOImage(image, null, null);
|
||||
writer.write(null, iioImage, iwp);
|
||||
} finally {
|
||||
if (output != null) {
|
||||
if (imageOutputStream != null) {
|
||||
try {
|
||||
output.close();
|
||||
imageOutputStream.close();
|
||||
} catch (Exception inner) {
|
||||
// NOP
|
||||
}
|
||||
|
||||
@@ -54,28 +54,33 @@ public class ResourceUtil {
|
||||
// Extract the JAR path
|
||||
String jarPath = dirUrl.getPath().substring(5, dirUrl.getPath().indexOf("!"));
|
||||
JarFile jar = new JarFile(URLDecoder.decode(jarPath, "UTF-8"));
|
||||
|
||||
Enumeration<JarEntry> entries = jar.entries();
|
||||
Set<String> fileSet = new HashSet<String>();
|
||||
while (entries.hasMoreElements()) {
|
||||
// Filter according to the path
|
||||
String entryName = entries.nextElement().getName();
|
||||
if (!entryName.startsWith(path)) {
|
||||
continue;
|
||||
}
|
||||
String name = entryName.substring(path.length());
|
||||
if (!"".equals(name)) {
|
||||
// If it is a subdirectory, just return the directory name
|
||||
int checkSubdir = name.indexOf("/");
|
||||
if (checkSubdir >= 0) {
|
||||
name = name.substring(0, checkSubdir);
|
||||
}
|
||||
|
||||
if (filter == null || filter.accept(null, name)) {
|
||||
fileSet.add(name);
|
||||
try {
|
||||
Enumeration<JarEntry> entries = jar.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
// Filter according to the path
|
||||
String entryName = entries.nextElement().getName();
|
||||
if (!entryName.startsWith(path)) {
|
||||
continue;
|
||||
}
|
||||
String name = entryName.substring(path.length());
|
||||
if (!"".equals(name)) {
|
||||
// If it is a subdirectory, just return the directory name
|
||||
int checkSubdir = name.indexOf("/");
|
||||
if (checkSubdir >= 0) {
|
||||
name = name.substring(0, checkSubdir);
|
||||
}
|
||||
|
||||
if (filter == null || filter.accept(null, name)) {
|
||||
fileSet.add(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
jar.close();
|
||||
}
|
||||
|
||||
return Lists.newArrayList(fileSet);
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,6 @@ public class MemoryAppender extends AppenderSkeleton {
|
||||
|
||||
@Override
|
||||
public synchronized void append(LoggingEvent event) {
|
||||
// TODO Don't use size()
|
||||
while (logQueue.size() > size) {
|
||||
logQueue.remove();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.sismics.util.mime;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
/**
|
||||
* Utility to check MIME types.
|
||||
@@ -24,6 +25,18 @@ public class MimeTypeUtil {
|
||||
if (readCount <= 0) {
|
||||
throw new Exception("Cannot read input file");
|
||||
}
|
||||
|
||||
return guessMimeType(headerBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to guess the MIME type of a file by its magic number (header).
|
||||
*
|
||||
* @param headerBytes File header (first bytes)
|
||||
* @return MIME type
|
||||
* @throws UnsupportedEncodingException
|
||||
*/
|
||||
public static String guessMimeType(byte[] headerBytes) throws UnsupportedEncodingException {
|
||||
String header = new String(headerBytes, "US-ASCII");
|
||||
|
||||
if (header.startsWith("PK")) {
|
||||
@@ -43,4 +56,29 @@ public class MimeTypeUtil {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a file extension linked to a MIME type.
|
||||
*
|
||||
* @param mimeType MIME type
|
||||
* @return File extension
|
||||
*/
|
||||
public static String getFileExtension(String mimeType) {
|
||||
switch (mimeType) {
|
||||
case MimeType.APPLICATION_ZIP:
|
||||
return "zip";
|
||||
case MimeType.IMAGE_GIF:
|
||||
return "gif";
|
||||
case MimeType.IMAGE_JPEG:
|
||||
return "jpg";
|
||||
case MimeType.IMAGE_PNG:
|
||||
return "png";
|
||||
case MimeType.IMAGE_X_ICON:
|
||||
return "ico";
|
||||
case MimeType.APPLICATION_PDF:
|
||||
return "pdf";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
db.version=5
|
||||
db.version=6
|
||||
@@ -0,0 +1,3 @@
|
||||
alter table T_USER add column USE_PRIVATEKEY_C varchar(100) default '' not null;
|
||||
update T_USER set USE_PRIVATEKEY_C = 'AdminPk' where USE_ID_C = 'admin';
|
||||
update T_CONFIG set CFG_VALUE_C='6' where CFG_ID_C='DB_VERSION';
|
||||
@@ -21,6 +21,7 @@ public class TestJpa extends BaseTransactionalTest {
|
||||
user.setEmail("toto@docs.com");
|
||||
user.setLocaleId("fr");
|
||||
user.setRoleId("admin");
|
||||
user.setPrivateKey("AwesomePrivateKey");
|
||||
String id = userDao.create(user);
|
||||
|
||||
TransactionUtil.commit();
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.sismics.docs.core.util;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.CipherInputStream;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
/**
|
||||
* Test of the encryption utilities.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class TestEncryptUtil {
|
||||
|
||||
/**
|
||||
* Test private key.
|
||||
*/
|
||||
String pk = "OnceUponATime";
|
||||
|
||||
@Test
|
||||
public void generatePrivateKeyTest() throws Exception {
|
||||
String key = EncryptionUtil.generatePrivateKey();
|
||||
System.out.println(key);
|
||||
Assert.assertFalse(Strings.isNullOrEmpty(key));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encryptStreamTest() throws Exception {
|
||||
try {
|
||||
EncryptionUtil.getEncryptionCipher("");
|
||||
Assert.fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
// NOP
|
||||
}
|
||||
Cipher cipher = EncryptionUtil.getEncryptionCipher(pk);
|
||||
InputStream inputStream = new CipherInputStream(this.getClass().getResourceAsStream("/file/udhr.pdf"), cipher);
|
||||
byte[] encryptedData = ByteStreams.toByteArray(inputStream);
|
||||
byte[] assertData = ByteStreams.toByteArray(this.getClass().getResourceAsStream("/file/udhr_encrypted.pdf"));
|
||||
Assert.assertTrue(ByteStreams.equal(
|
||||
ByteStreams.newInputStreamSupplier(encryptedData),
|
||||
ByteStreams.newInputStreamSupplier(assertData)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decryptStreamTest() throws Exception {
|
||||
InputStream inputStream = EncryptionUtil.decryptInputStream(this.getClass().getResourceAsStream("/file/udhr_encrypted.pdf"), pk);
|
||||
byte[] encryptedData = ByteStreams.toByteArray(inputStream);
|
||||
byte[] assertData = ByteStreams.toByteArray(this.getClass().getResourceAsStream("/file/udhr.pdf"));
|
||||
Assert.assertTrue(ByteStreams.equal(
|
||||
ByteStreams.newInputStreamSupplier(encryptedData),
|
||||
ByteStreams.newInputStreamSupplier(assertData)));
|
||||
}
|
||||
}
|
||||
BIN
docs-core/src/test/resources/file/udhr.pdf
Normal file
BIN
docs-core/src/test/resources/file/udhr.pdf
Normal file
Binary file not shown.
BIN
docs-core/src/test/resources/file/udhr_encrypted.pdf
Normal file
BIN
docs-core/src/test/resources/file/udhr_encrypted.pdf
Normal file
Binary file not shown.
@@ -0,0 +1,2 @@
|
||||
- Automatic backup system using Quartz (server)
|
||||
- Handle error while uploading a file
|
||||
|
||||
Binary file not shown.
@@ -63,6 +63,7 @@
|
||||
<org.vafer.jdeb.version>1.0.1</org.vafer.jdeb.version>
|
||||
<com.samaxes.maven.minify-maven-plugin.version>1.7</com.samaxes.maven.minify-maven-plugin.version>
|
||||
<org.apache.pdfbox.pdfbox.version>1.8.2</org.apache.pdfbox.pdfbox.version>
|
||||
<org.bouncycastle.bcprov-jdk15on.version>1.49</org.bouncycastle.bcprov-jdk15on.version>
|
||||
</properties>
|
||||
|
||||
<scm>
|
||||
@@ -443,6 +444,12 @@
|
||||
<version>${org.apache.pdfbox.pdfbox.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
<version>${org.bouncycastle.bcprov-jdk15on.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- OCR dependencies -->
|
||||
<dependency>
|
||||
<groupId>jna</groupId>
|
||||
@@ -456,11 +463,6 @@
|
||||
<version>1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>tess4j</groupId>
|
||||
<artifactId>tess4j</artifactId>
|
||||
<version>1.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
@@ -524,23 +526,6 @@
|
||||
</goals>
|
||||
</execution>
|
||||
|
||||
<execution>
|
||||
<id>install-tess4j</id>
|
||||
<phase>validate</phase>
|
||||
<configuration>
|
||||
<file>${project.basedir}/lib/tess4j.jar</file>
|
||||
<repositoryLayout>default</repositoryLayout>
|
||||
<groupId>tess4j</groupId>
|
||||
<artifactId>tess4j</artifactId>
|
||||
<version>1.0</version>
|
||||
<packaging>jar</packaging>
|
||||
<generatePom>true</generatePom>
|
||||
</configuration>
|
||||
<goals>
|
||||
<goal>install-file</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
||||
@@ -102,7 +102,6 @@ public class ValidationUtil {
|
||||
* @throws JSONException
|
||||
*/
|
||||
public static void validateHexColor(String s, String name, boolean nullable) throws JSONException {
|
||||
// TODO Do a real check
|
||||
ValidationUtil.validateLength(s, "name", 7, 7, nullable);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ public class JerseyTestWebAppDescriptorFactory {
|
||||
.addFilter(TokenBasedSecurityFilter.class, "tokenBasedSecurityFilter")
|
||||
.initParam("com.sun.jersey.spi.container.ContainerRequestFilters", "com.sun.jersey.api.container.filter.LoggingFilter")
|
||||
.initParam("com.sun.jersey.spi.container.ContainerResponseFilters", "com.sun.jersey.api.container.filter.LoggingFilter")
|
||||
.initParam("com.sun.jersey.config.feature.logging.DisableEntitylogging", "true")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,14 +264,44 @@
|
||||
|
||||
<plugins>
|
||||
|
||||
<!-- Generation of the war -->
|
||||
<!-- Launch NPM & Grunt -->
|
||||
<plugin>
|
||||
<artifactId>maven-antrun-plugin</artifactId>
|
||||
<version>1.7</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>generate-sources</phase>
|
||||
<configuration>
|
||||
<target name="building">
|
||||
<!-- npm install -->
|
||||
<exec executable="cmd" dir="${project.basedir}/src/main/webapp" osfamily="windows" failonerror="true">
|
||||
<arg line="/c npm install" />
|
||||
</exec>
|
||||
<exec executable="npm" dir="${project.basedir}/src/main/webapp" osfamily="unix" failonerror="true">
|
||||
<arg line="install" />
|
||||
</exec>
|
||||
<!-- grunt -->
|
||||
<exec executable="cmd" dir="${project.basedir}/src/main/webapp" osfamily="windows">
|
||||
<arg line="/c grunt --apiurl=api" />
|
||||
</exec>
|
||||
<exec executable="grunt" dir="${project.basedir}/src/main/webapp" osfamily="unix">
|
||||
<arg line="--apiurl=api" />
|
||||
</exec>
|
||||
</target>
|
||||
</configuration>
|
||||
<goals>
|
||||
<goal>run</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- WAR generation -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-war-plugin</artifactId>
|
||||
<configuration>
|
||||
<warSourceExcludes>
|
||||
sismicsdocs/**,
|
||||
</warSourceExcludes>
|
||||
<warSourceDirectory>${basedir}/src/main/webapp/dist</warSourceDirectory>
|
||||
<webXml>src\main\webapp\WEB-INF\web.xml</webXml>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
api.current_version=${project.version}
|
||||
api.min_version=1.0
|
||||
db.version=5
|
||||
db.version=6
|
||||
@@ -1,7 +1,5 @@
|
||||
package com.sismics.docs.rest.resource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -26,12 +24,10 @@ import com.sismics.docs.core.dao.jpa.DocumentDao;
|
||||
import com.sismics.docs.core.dao.jpa.FileDao;
|
||||
import com.sismics.docs.core.dao.jpa.criteria.DocumentCriteria;
|
||||
import com.sismics.docs.core.dao.jpa.dto.DocumentDto;
|
||||
import com.sismics.docs.core.event.ExtractFileAsyncEvent;
|
||||
import com.sismics.docs.core.model.context.AppContext;
|
||||
import com.sismics.docs.core.model.jpa.File;
|
||||
import com.sismics.docs.core.util.ConfigUtil;
|
||||
import com.sismics.docs.core.util.DirectoryUtil;
|
||||
import com.sismics.docs.core.util.FileUtil;
|
||||
import com.sismics.docs.core.util.jpa.PaginatedList;
|
||||
import com.sismics.docs.core.util.jpa.PaginatedLists;
|
||||
import com.sismics.docs.core.util.jpa.SortCriteria;
|
||||
@@ -113,7 +109,7 @@ public class AppResource extends BaseResource {
|
||||
if (!authenticate()) {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
checkBaseFunction(BaseFunction.ADMIN);
|
||||
// TODO Change level by minLevel (returns all logs above)
|
||||
|
||||
// Get the memory appender
|
||||
Logger logger = Logger.getRootLogger();
|
||||
@@ -147,29 +143,6 @@ public class AppResource extends BaseResource {
|
||||
return Response.ok().entity(response).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract content from all files again.
|
||||
*
|
||||
* @return Response
|
||||
* @throws JSONException
|
||||
*/
|
||||
@POST
|
||||
@Path("batch/extract")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Response batchExtract() throws JSONException {
|
||||
if (!authenticate()) {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
checkBaseFunction(BaseFunction.ADMIN);
|
||||
|
||||
// Raise an extract file content event
|
||||
AppContext.getInstance().getAsyncEventBus().post(new ExtractFileAsyncEvent());
|
||||
|
||||
JSONObject response = new JSONObject();
|
||||
response.put("status", "ok");
|
||||
return Response.ok().entity(response).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy and rebuild Lucene index.
|
||||
*
|
||||
@@ -197,7 +170,7 @@ public class AppResource extends BaseResource {
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy and rebuild Lucene index.
|
||||
* Clean storage.
|
||||
*
|
||||
* @return Response
|
||||
* @throws JSONException
|
||||
@@ -233,38 +206,4 @@ public class AppResource extends BaseResource {
|
||||
response.put("status", "ok");
|
||||
return Response.ok().entity(response).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Regenerate file variations.
|
||||
*
|
||||
* @return Response
|
||||
* @throws JSONException
|
||||
*/
|
||||
@POST
|
||||
@Path("batch/file_variations")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Response batchFileVariations() throws JSONException {
|
||||
if (!authenticate()) {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
checkBaseFunction(BaseFunction.ADMIN);
|
||||
|
||||
// Get all files
|
||||
FileDao fileDao = new FileDao();
|
||||
List<File> fileList = fileDao.findAll();
|
||||
|
||||
// Generate variations for each file
|
||||
for (File file : fileList) {
|
||||
java.io.File originalFile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file.getId()).toFile();
|
||||
try {
|
||||
FileUtil.saveVariations(file, originalFile);
|
||||
} catch (IOException e) {
|
||||
throw new ServerException("FileError", "Error generating file variations", e);
|
||||
}
|
||||
}
|
||||
|
||||
JSONObject response = new JSONObject();
|
||||
response.put("status", "ok");
|
||||
return Response.ok().entity(response).build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Strings;
|
||||
import com.sismics.docs.core.constant.Constants;
|
||||
import com.sismics.docs.core.dao.jpa.DocumentDao;
|
||||
import com.sismics.docs.core.dao.jpa.FileDao;
|
||||
import com.sismics.docs.core.dao.jpa.ShareDao;
|
||||
import com.sismics.docs.core.dao.jpa.TagDao;
|
||||
import com.sismics.docs.core.dao.jpa.criteria.DocumentCriteria;
|
||||
@@ -42,8 +43,10 @@ import com.sismics.docs.core.dao.jpa.dto.TagDto;
|
||||
import com.sismics.docs.core.event.DocumentCreatedAsyncEvent;
|
||||
import com.sismics.docs.core.event.DocumentDeletedAsyncEvent;
|
||||
import com.sismics.docs.core.event.DocumentUpdatedAsyncEvent;
|
||||
import com.sismics.docs.core.event.FileDeletedAsyncEvent;
|
||||
import com.sismics.docs.core.model.context.AppContext;
|
||||
import com.sismics.docs.core.model.jpa.Document;
|
||||
import com.sismics.docs.core.model.jpa.File;
|
||||
import com.sismics.docs.core.model.jpa.Share;
|
||||
import com.sismics.docs.core.model.jpa.Tag;
|
||||
import com.sismics.docs.core.util.jpa.PaginatedList;
|
||||
@@ -168,6 +171,7 @@ public class DocumentResource extends BaseResource {
|
||||
document.put("create_date", documentDto.getCreateTimestamp());
|
||||
document.put("shared", documentDto.getShared());
|
||||
document.put("language", documentDto.getLanguage());
|
||||
document.put("file_count", documentDto.getFileCount());
|
||||
|
||||
// Get tags
|
||||
List<TagDto> tagDtoList = tagDao.getByDocumentId(documentDto.getId());
|
||||
@@ -243,7 +247,9 @@ public class DocumentResource extends BaseResource {
|
||||
if (params[0].equals("before")) documentCriteria.setCreateDateMax(date.toDate());
|
||||
else documentCriteria.setCreateDateMin(date.toDate());
|
||||
} catch (IllegalArgumentException e) {
|
||||
// NOP
|
||||
// Invalid date, returns no documents
|
||||
if (params[0].equals("before")) documentCriteria.setCreateDateMax(new Date(0));
|
||||
else documentCriteria.setCreateDateMin(new Date(Long.MAX_VALUE / 2));
|
||||
}
|
||||
} else if (params[0].equals("at")) {
|
||||
// New specific date criteria
|
||||
@@ -262,7 +268,9 @@ public class DocumentResource extends BaseResource {
|
||||
documentCriteria.setCreateDateMax(date.plusYears(1).minusSeconds(1).toDate());
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
// NOP
|
||||
// Invalid date, returns no documents
|
||||
documentCriteria.setCreateDateMin(new Date(0));
|
||||
documentCriteria.setCreateDateMax(new Date(0));
|
||||
}
|
||||
} else if (params[0].equals("shared")) {
|
||||
// New shared state criteria
|
||||
@@ -292,6 +300,9 @@ public class DocumentResource extends BaseResource {
|
||||
*
|
||||
* @param title Title
|
||||
* @param description Description
|
||||
* @param tags Tags
|
||||
* @param language Language
|
||||
* @param createDateStr Creation date
|
||||
* @return Response
|
||||
* @throws JSONException
|
||||
*/
|
||||
@@ -455,21 +466,31 @@ public class DocumentResource extends BaseResource {
|
||||
|
||||
// Get the document
|
||||
DocumentDao documentDao = new DocumentDao();
|
||||
FileDao fileDao = new FileDao();
|
||||
Document document;
|
||||
List<File> fileList;
|
||||
try {
|
||||
document = documentDao.getDocument(id, principal.getId());
|
||||
fileList = fileDao.getByDocumentId(id);
|
||||
} catch (NoResultException e) {
|
||||
throw new ClientException("DocumentNotFound", MessageFormat.format("Document not found: {0}", id));
|
||||
}
|
||||
|
||||
// Delete the document
|
||||
documentDao.delete(document.getId());
|
||||
|
||||
// Raise file deleted events
|
||||
for (File file : fileList) {
|
||||
FileDeletedAsyncEvent fileDeletedAsyncEvent = new FileDeletedAsyncEvent();
|
||||
fileDeletedAsyncEvent.setFile(file);
|
||||
AppContext.getInstance().getAsyncEventBus().post(fileDeletedAsyncEvent);
|
||||
}
|
||||
|
||||
// Raise a document deleted event
|
||||
DocumentDeletedAsyncEvent documentDeletedAsyncEvent = new DocumentDeletedAsyncEvent();
|
||||
documentDeletedAsyncEvent.setDocument(document);
|
||||
AppContext.getInstance().getAsyncEventBus().post(documentDeletedAsyncEvent);
|
||||
|
||||
// Delete the document
|
||||
documentDao.delete(document.getId());
|
||||
|
||||
// Always return ok
|
||||
JSONObject response = new JSONObject();
|
||||
response.put("status", "ok");
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
package com.sismics.docs.rest.resource;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Paths;
|
||||
import java.text.MessageFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import javax.persistence.NoResultException;
|
||||
import javax.ws.rs.Consumes;
|
||||
@@ -20,22 +25,28 @@ import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.StreamingOutput;
|
||||
|
||||
import org.codehaus.jettison.json.JSONException;
|
||||
import org.codehaus.jettison.json.JSONObject;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.sismics.docs.core.dao.jpa.DocumentDao;
|
||||
import com.sismics.docs.core.dao.jpa.FileDao;
|
||||
import com.sismics.docs.core.dao.jpa.ShareDao;
|
||||
import com.sismics.docs.core.dao.jpa.UserDao;
|
||||
import com.sismics.docs.core.event.FileCreatedAsyncEvent;
|
||||
import com.sismics.docs.core.event.FileDeletedAsyncEvent;
|
||||
import com.sismics.docs.core.model.context.AppContext;
|
||||
import com.sismics.docs.core.model.jpa.Document;
|
||||
import com.sismics.docs.core.model.jpa.File;
|
||||
import com.sismics.docs.core.model.jpa.User;
|
||||
import com.sismics.docs.core.util.DirectoryUtil;
|
||||
import com.sismics.docs.core.util.EncryptionUtil;
|
||||
import com.sismics.docs.core.util.FileUtil;
|
||||
import com.sismics.rest.exception.ClientException;
|
||||
import com.sismics.rest.exception.ForbiddenClientException;
|
||||
@@ -77,20 +88,30 @@ public class FileResource extends BaseResource {
|
||||
|
||||
// Get the document
|
||||
DocumentDao documentDao = new DocumentDao();
|
||||
FileDao fileDao = new FileDao();
|
||||
UserDao userDao = new UserDao();
|
||||
Document document;
|
||||
User user;
|
||||
try {
|
||||
document = documentDao.getDocument(documentId, principal.getId());
|
||||
user = userDao.getById(principal.getId());
|
||||
} catch (NoResultException e) {
|
||||
throw new ClientException("DocumentNotFound", MessageFormat.format("Document not found: {0}", documentId));
|
||||
}
|
||||
|
||||
FileDao fileDao = new FileDao();
|
||||
// Keep unencrypted data in memory, because we will need it two times
|
||||
byte[] fileData;
|
||||
try {
|
||||
fileData = ByteStreams.toByteArray(fileBodyPart.getValueAs(InputStream.class));
|
||||
} catch (IOException e) {
|
||||
throw new ServerException("StreamError", "Error reading the input file", e);
|
||||
}
|
||||
InputStream fileInputStream = new ByteArrayInputStream(fileData);
|
||||
|
||||
// Validate mime type
|
||||
InputStream is = new BufferedInputStream(fileBodyPart.getValueAs(InputStream.class));
|
||||
String mimeType;
|
||||
try {
|
||||
mimeType = MimeTypeUtil.guessMimeType(is);
|
||||
mimeType = MimeTypeUtil.guessMimeType(fileInputStream);
|
||||
} catch (Exception e) {
|
||||
throw new ServerException("ErrorGuessMime", "Error guessing mime type", e);
|
||||
}
|
||||
@@ -113,12 +134,13 @@ public class FileResource extends BaseResource {
|
||||
String fileId = fileDao.create(file);
|
||||
|
||||
// Save the file
|
||||
FileUtil.save(is, file);
|
||||
FileUtil.save(fileInputStream, file, user.getPrivateKey());
|
||||
|
||||
// Raise a new file created event
|
||||
FileCreatedAsyncEvent fileCreatedAsyncEvent = new FileCreatedAsyncEvent();
|
||||
fileCreatedAsyncEvent.setDocument(document);
|
||||
fileCreatedAsyncEvent.setFile(file);
|
||||
fileCreatedAsyncEvent.setInputStream(fileInputStream);
|
||||
AppContext.getInstance().getAsyncEventBus().post(fileCreatedAsyncEvent);
|
||||
|
||||
// Always return ok
|
||||
@@ -249,14 +271,14 @@ public class FileResource extends BaseResource {
|
||||
throw new ClientException("FileNotFound", MessageFormat.format("File not found: {0}", id));
|
||||
}
|
||||
|
||||
// Delete the file
|
||||
fileDao.delete(file.getId());
|
||||
|
||||
// Raise a new file deleted event
|
||||
FileDeletedAsyncEvent fileDeletedAsyncEvent = new FileDeletedAsyncEvent();
|
||||
fileDeletedAsyncEvent.setFile(file);
|
||||
AppContext.getInstance().getAsyncEventBus().post(fileDeletedAsyncEvent);
|
||||
|
||||
// Delete the file
|
||||
fileDao.delete(file.getId());
|
||||
|
||||
// Always return ok
|
||||
JSONObject response = new JSONObject();
|
||||
response.put("status", "ok");
|
||||
@@ -288,10 +310,12 @@ public class FileResource extends BaseResource {
|
||||
// Get the file
|
||||
FileDao fileDao = new FileDao();
|
||||
DocumentDao documentDao = new DocumentDao();
|
||||
UserDao userDao = new UserDao();
|
||||
File file;
|
||||
Document document;
|
||||
try {
|
||||
file = fileDao.getFile(fileId);
|
||||
Document document = documentDao.getDocument(file.getDocumentId());
|
||||
document = documentDao.getDocument(file.getDocumentId());
|
||||
|
||||
// Check document visibility
|
||||
ShareDao shareDao = new ShareDao();
|
||||
@@ -306,21 +330,119 @@ public class FileResource extends BaseResource {
|
||||
// Get the stored file
|
||||
java.io.File storedfile;
|
||||
String mimeType;
|
||||
boolean decrypt = false;
|
||||
if (size != null) {
|
||||
storedfile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), fileId + "_" + size).toFile();
|
||||
mimeType = MimeType.IMAGE_JPEG; // Thumbnails are JPEG
|
||||
decrypt = true; // Thumbnails are encrypted
|
||||
if (!storedfile.exists()) {
|
||||
storedfile = new java.io.File(getClass().getResource("/image/file.png").getFile());
|
||||
mimeType = MimeType.IMAGE_PNG;
|
||||
decrypt = false;
|
||||
}
|
||||
} else {
|
||||
storedfile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), fileId).toFile();
|
||||
mimeType = file.getMimeType();
|
||||
decrypt = true; // Original files are encrypted
|
||||
}
|
||||
|
||||
return Response.ok(storedfile)
|
||||
// Stream the output and decrypt it if necessary
|
||||
StreamingOutput stream;
|
||||
User user = userDao.getById(document.getUserId());
|
||||
try {
|
||||
InputStream fileInputStream = new FileInputStream(storedfile);
|
||||
final InputStream responseInputStream = decrypt ?
|
||||
EncryptionUtil.decryptInputStream(fileInputStream, user.getPrivateKey()) : fileInputStream;
|
||||
|
||||
stream = new StreamingOutput() {
|
||||
@Override
|
||||
public void write(OutputStream outputStream) throws IOException, WebApplicationException {
|
||||
try {
|
||||
ByteStreams.copy(responseInputStream, outputStream);
|
||||
} finally {
|
||||
responseInputStream.close();
|
||||
outputStream.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
} catch (Exception e) {
|
||||
throw new ServerException("FileError", "Error while reading the file", e);
|
||||
}
|
||||
|
||||
return Response.ok(stream)
|
||||
.header("Content-Type", mimeType)
|
||||
.header("Expires", new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z").format(new Date().getTime() + 3600000 * 24 * 7))
|
||||
.header("Expires", new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z").format(new Date().getTime() + 3600000 * 24))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all files from a document, zipped.
|
||||
*
|
||||
* @param documentId Document ID
|
||||
* @return Response
|
||||
* @throws JSONException
|
||||
*/
|
||||
@GET
|
||||
@Path("zip")
|
||||
@Produces(MediaType.APPLICATION_OCTET_STREAM)
|
||||
public Response zip(
|
||||
@QueryParam("id") String documentId,
|
||||
@QueryParam("share") String shareId) throws JSONException {
|
||||
authenticate();
|
||||
|
||||
// Get the document
|
||||
DocumentDao documentDao = new DocumentDao();
|
||||
Document document;
|
||||
try {
|
||||
document = documentDao.getDocument(documentId);
|
||||
|
||||
// Check document visibility
|
||||
ShareDao shareDao = new ShareDao();
|
||||
if (!shareDao.checkVisibility(document, principal.getId(), shareId)) {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
} catch (NoResultException e) {
|
||||
throw new ClientException("DocumentNotFound", MessageFormat.format("Document not found: {0}", documentId));
|
||||
}
|
||||
|
||||
// Get files and user associated with this document
|
||||
FileDao fileDao = new FileDao();
|
||||
UserDao userDao = new UserDao();
|
||||
final List<File> fileList = fileDao.getByDocumentId(documentId);
|
||||
final User user = userDao.getById(document.getUserId());
|
||||
|
||||
// Create the ZIP stream
|
||||
StreamingOutput stream = new StreamingOutput() {
|
||||
@Override
|
||||
public void write(OutputStream outputStream) throws IOException, WebApplicationException {
|
||||
try (ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream)) {
|
||||
// Add each file to the ZIP stream
|
||||
int index = 0;
|
||||
for (File file : fileList) {
|
||||
java.io.File storedfile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file.getId()).toFile();
|
||||
InputStream fileInputStream = new FileInputStream(storedfile);
|
||||
|
||||
// Add the decrypted file to the ZIP stream
|
||||
try (InputStream decryptedStream = EncryptionUtil.decryptInputStream(fileInputStream, user.getPrivateKey())) {
|
||||
ZipEntry zipEntry = new ZipEntry(index + "." + MimeTypeUtil.getFileExtension(file.getMimeType()));
|
||||
zipOutputStream.putNextEntry(zipEntry);
|
||||
ByteStreams.copy(decryptedStream, zipOutputStream);
|
||||
zipOutputStream.closeEntry();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new WebApplicationException(e);
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
outputStream.close();
|
||||
}
|
||||
};
|
||||
|
||||
// Write to the output
|
||||
return Response.ok(stream)
|
||||
.header("Content-Type", "application/zip")
|
||||
.header("Content-Disposition", "attachment; filename=\"" + document.getTitle().replaceAll("\\W+", "_") + ".zip\"")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ 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.EncryptionUtil;
|
||||
import com.sismics.docs.core.util.jpa.PaginatedList;
|
||||
import com.sismics.docs.core.util.jpa.PaginatedLists;
|
||||
import com.sismics.docs.core.util.jpa.SortCriteria;
|
||||
@@ -28,6 +29,8 @@ import javax.ws.rs.*;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.NewCookie;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
@@ -76,6 +79,11 @@ public class UserResource extends BaseResource {
|
||||
user.setUsername(username);
|
||||
user.setPassword(password);
|
||||
user.setEmail(email);
|
||||
try {
|
||||
user.setPrivateKey(EncryptionUtil.generatePrivateKey());
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new ServerException("PrivateKeyError", "Error while generating a private key", e);
|
||||
}
|
||||
user.setCreateDate(new Date());
|
||||
|
||||
if (localeId == null) {
|
||||
|
||||
5
docs-web/src/main/webapp/.gitignore
vendored
5
docs-web/src/main/webapp/.gitignore
vendored
@@ -1 +1,4 @@
|
||||
/sismicsdocs
|
||||
sismicsdocs
|
||||
node_modules
|
||||
dist
|
||||
.idea
|
||||
122
docs-web/src/main/webapp/Gruntfile.js
Normal file
122
docs-web/src/main/webapp/Gruntfile.js
Normal file
@@ -0,0 +1,122 @@
|
||||
module.exports = function(grunt) {
|
||||
|
||||
// Project configuration.
|
||||
grunt.initConfig({
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
clean: {
|
||||
dist: {
|
||||
src: ['dist']
|
||||
}
|
||||
},
|
||||
ngmin: {
|
||||
dist: {
|
||||
expand: true,
|
||||
cwd: 'src',
|
||||
src: ['app/**/*.js'],
|
||||
dest: 'dist'
|
||||
}
|
||||
},
|
||||
concat: {
|
||||
docs: {
|
||||
options: {
|
||||
separator: ';'
|
||||
},
|
||||
src: ['src/lib/jquery.js','src/lib/jquery.ui.js','src/lib/underscore.js','src/lib/colorpicker.js', 'src/lib/angular.js', 'src/lib/angular.*.js',
|
||||
'dist/app/docs/app.js', 'dist/app/docs/controller/*.js', 'dist/app/docs/directive/*.js', 'dist/app/docs/filter/*.js', 'dist/app/docs/service/*.js'],
|
||||
dest: 'dist/docs.js'
|
||||
},
|
||||
share: {
|
||||
options: {
|
||||
separator: ';'
|
||||
},
|
||||
src: ['src/lib/jquery.js','src/lib/jquery.ui.js','src/lib/underscore.js','src/lib/colorpicker.js', 'src/lib/angular.js', 'src/lib/angular.*.js',
|
||||
'dist/app/share/app.js', 'dist/app/share/controller/*.js', 'dist/app/share/directive/*.js', 'dist/app/share/filter/*.js', 'dist/app/share/service/*.js'],
|
||||
dest: 'dist/share.js'
|
||||
},
|
||||
css: {
|
||||
src: ['src/style/*.css', 'dist/less.css'],
|
||||
dest: 'dist/style.css'
|
||||
}
|
||||
},
|
||||
less: {
|
||||
dist: {
|
||||
src: ['src/style/*.less'],
|
||||
dest: 'dist/less.css'
|
||||
}
|
||||
},
|
||||
cssmin: {
|
||||
dist: {
|
||||
src: 'dist/style.css',
|
||||
dest: 'dist/style/style.min.css'
|
||||
}
|
||||
},
|
||||
uglify: {
|
||||
docs: {
|
||||
src: 'dist/docs.js',
|
||||
dest: 'dist/docs.min.js'
|
||||
},
|
||||
share: {
|
||||
src: 'dist/share.js',
|
||||
dest: 'dist/share.min.js'
|
||||
}
|
||||
},
|
||||
copy: {
|
||||
dist: {
|
||||
expand: true,
|
||||
cwd: 'src/',
|
||||
src: ['**', '!**/*.js', '!*.html', '!**/*.less', '!**/*.css'],
|
||||
dest: 'dist/'
|
||||
}
|
||||
},
|
||||
htmlrefs: {
|
||||
index: {
|
||||
src: 'src/index.html',
|
||||
dest: 'dist/index.html'
|
||||
},
|
||||
share: {
|
||||
src: 'src/share.html',
|
||||
dest: 'dist/share.html'
|
||||
}
|
||||
},
|
||||
remove: {
|
||||
dist: {
|
||||
fileList: ['dist/style.css', 'dist/docs.js', 'dist/share.js', 'dist/less.css'],
|
||||
dirList: ['dist/app']
|
||||
}
|
||||
},
|
||||
cleanempty: {
|
||||
options: {
|
||||
files: false,
|
||||
folders: true
|
||||
},
|
||||
src: ['dist/**']
|
||||
},
|
||||
replace: {
|
||||
dist: {
|
||||
src: ['dist/docs.min.js', 'dist/share.min.js', 'dist/**/*.html'],
|
||||
overwrite: true,
|
||||
replacements: [{
|
||||
from: '../api',
|
||||
to: grunt.option('apiurl') || '../api'
|
||||
}]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
grunt.loadNpmTasks('grunt-contrib-clean');
|
||||
grunt.loadNpmTasks('grunt-contrib-concat');
|
||||
grunt.loadNpmTasks('grunt-contrib-uglify');
|
||||
grunt.loadNpmTasks('grunt-contrib-copy');
|
||||
grunt.loadNpmTasks('grunt-cleanempty');
|
||||
grunt.loadNpmTasks('grunt-htmlrefs');
|
||||
grunt.loadNpmTasks('grunt-css');
|
||||
grunt.loadNpmTasks('grunt-contrib-less');
|
||||
grunt.loadNpmTasks('grunt-remove');
|
||||
grunt.loadNpmTasks('grunt-ngmin');
|
||||
grunt.loadNpmTasks('grunt-text-replace');
|
||||
|
||||
// Default tasks.
|
||||
grunt.registerTask('default', ['clean', 'ngmin', 'concat:docs', 'concat:share', 'less', 'concat:css', 'cssmin',
|
||||
'uglify:docs', 'uglify:share', 'copy', 'remove', 'cleanempty', 'htmlrefs:index', 'htmlrefs:share', 'replace']);
|
||||
|
||||
};
|
||||
@@ -1,9 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Document default controller.
|
||||
*/
|
||||
App.controller('DocumentDefault', function($scope, $state, Restangular) {
|
||||
// Load app data
|
||||
$scope.app = Restangular.one('app').get();
|
||||
});
|
||||
@@ -1,83 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* File view controller.
|
||||
*/
|
||||
App.controller('FileView', function($dialog, $state, $stateParams) {
|
||||
var dialog = $dialog.dialog({
|
||||
keyboard: true,
|
||||
templateUrl: 'partial/docs/file.view.html',
|
||||
controller: function($scope, $state, $stateParams, Restangular, dialog) {
|
||||
// Load files
|
||||
Restangular.one('file').getList('list', { id: $stateParams.id }).then(function(data) {
|
||||
$scope.files = data.files;
|
||||
|
||||
// Search current file
|
||||
_.each($scope.files, function(value) {
|
||||
if (value.id == $stateParams.fileId) {
|
||||
$scope.file = value;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Navigate to the next file.
|
||||
*/
|
||||
$scope.nextFile = function() {
|
||||
_.each($scope.files, function(value, key) {
|
||||
if (value.id == $stateParams.fileId) {
|
||||
var next = $scope.files[key + 1];
|
||||
if (next) {
|
||||
dialog.close({});
|
||||
$state.transitionTo('document.view.file', { id: $stateParams.id, fileId: next.id });
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigate to the previous file.
|
||||
*/
|
||||
$scope.previousFile = function() {
|
||||
_.each($scope.files, function(value, key) {
|
||||
if (value.id == $stateParams.fileId) {
|
||||
var previous = $scope.files[key - 1];
|
||||
if (previous) {
|
||||
dialog.close({});
|
||||
$state.transitionTo('document.view.file', { id: $stateParams.id, fileId: previous.id });
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Open the file in a new window.
|
||||
*/
|
||||
$scope.openFile = function() {
|
||||
window.open('api/file/' + $stateParams.fileId + '/data');
|
||||
};
|
||||
|
||||
/**
|
||||
* Close the file preview.
|
||||
*/
|
||||
$scope.closeFile = function () {
|
||||
dialog.close();
|
||||
};
|
||||
|
||||
// Close the dialog when the user exits this state
|
||||
var off = $scope.$on('$stateChangeStart', function(event, toState){
|
||||
if (dialog.isOpen()) {
|
||||
dialog.close(toState.name == 'document.view.file' ? {} : null);
|
||||
}
|
||||
off();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Returns to document view on file close
|
||||
dialog.open().then(function(result) {
|
||||
if (result == null) {
|
||||
$state.transitionTo('document.view', { id: $stateParams.id });
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,23 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Navigation controller.
|
||||
*/
|
||||
App.controller('Navigation', function($scope, $http, $state, $rootScope, User, Restangular) {
|
||||
$rootScope.userInfo = User.userInfo();
|
||||
|
||||
/**
|
||||
* User logout.
|
||||
*/
|
||||
$scope.logout = function($event) {
|
||||
User.logout().then(function() {
|
||||
$rootScope.userInfo = User.userInfo(true);
|
||||
$state.transitionTo('main');
|
||||
});
|
||||
$event.preventDefault();
|
||||
};
|
||||
|
||||
$scope.isLoading = function() {
|
||||
return $http.pendingRequests.length > 0;
|
||||
};
|
||||
});
|
||||
@@ -1,11 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Settings controller.
|
||||
*/
|
||||
App.controller('Settings', function($scope, Restangular) {
|
||||
// Flag if the user is admin
|
||||
$scope.userInfo.then(function (data) {
|
||||
$scope.isAdmin = data.base_functions.indexOf('ADMIN') != -1;
|
||||
});
|
||||
});
|
||||
@@ -1,7 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Settings default page controller.
|
||||
*/
|
||||
App.controller('SettingsDefault', function($scope, Restangular) {
|
||||
});
|
||||
@@ -1,83 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* File view controller.
|
||||
*/
|
||||
App.controller('FileView', function($dialog, $state, $stateParams) {
|
||||
var dialog = $dialog.dialog({
|
||||
keyboard: true,
|
||||
templateUrl: 'partial/share/file.view.html',
|
||||
controller: function($scope, $state, $stateParams, Restangular, dialog) {
|
||||
// Load files
|
||||
Restangular.one('file').getList('list', { id: $stateParams.documentId, share: $stateParams.shareId }).then(function(data) {
|
||||
$scope.files = data.files;
|
||||
|
||||
// Search current file
|
||||
_.each($scope.files, function(value) {
|
||||
if (value.id == $stateParams.fileId) {
|
||||
$scope.file = value;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Navigate to the next file.
|
||||
*/
|
||||
$scope.nextFile = function() {
|
||||
_.each($scope.files, function(value, key) {
|
||||
if (value.id == $stateParams.fileId) {
|
||||
var next = $scope.files[key + 1];
|
||||
if (next) {
|
||||
dialog.close({});
|
||||
$state.transitionTo('share.file', { documentId: $stateParams.documentId, shareId: $stateParams.shareId, fileId: next.id });
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigate to the previous file.
|
||||
*/
|
||||
$scope.previousFile = function() {
|
||||
_.each($scope.files, function(value, key) {
|
||||
if (value.id == $stateParams.fileId) {
|
||||
var previous = $scope.files[key - 1];
|
||||
if (previous) {
|
||||
dialog.close({});
|
||||
$state.transitionTo('share.file', { documentId: $stateParams.documentId, shareId: $stateParams.shareId, fileId: previous.id });
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Open the file in a new window.
|
||||
*/
|
||||
$scope.openFile = function() {
|
||||
window.open('api/file/' + $stateParams.fileId + '/data?share=' + $stateParams.shareId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Close the file preview.
|
||||
*/
|
||||
$scope.closeFile = function () {
|
||||
dialog.close();
|
||||
};
|
||||
|
||||
// Close the dialog when the user exits this state
|
||||
var off = $scope.$on('$stateChangeStart', function(event, toState){
|
||||
if (dialog.isOpen()) {
|
||||
dialog.close(toState.name == 'share.file' ? {} : null);
|
||||
}
|
||||
off();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Returns to share view on file close
|
||||
dialog.open().then(function(result) {
|
||||
if (result == null) {
|
||||
$state.transitionTo('share', { documentId: $stateParams.documentId, shareId: $stateParams.shareId });
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,7 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Main controller.
|
||||
*/
|
||||
App.controller('Main', function() {
|
||||
});
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.0 KiB |
@@ -1,782 +0,0 @@
|
||||
'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
File diff suppressed because it is too large
Load Diff
@@ -1,185 +0,0 @@
|
||||
/**
|
||||
* @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);
|
||||
@@ -1,7 +0,0 @@
|
||||
/*
|
||||
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);
|
||||
@@ -1,304 +0,0 @@
|
||||
/**
|
||||
* @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;
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
/*
|
||||
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);
|
||||
@@ -1,460 +0,0 @@
|
||||
/**
|
||||
* @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);
|
||||
@@ -1,11 +0,0 @@
|
||||
/*
|
||||
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);
|
||||
1886
docs-web/src/main/webapp/lib/angular/angular-mocks.js
vendored
1886
docs-web/src/main/webapp/lib/angular/angular-mocks.js
vendored
File diff suppressed because it is too large
Load Diff
@@ -1,537 +0,0 @@
|
||||
/**
|
||||
* @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);
|
||||
@@ -1,11 +0,0 @@
|
||||
/*
|
||||
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);
|
||||
@@ -1,558 +0,0 @@
|
||||
/**
|
||||
* @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><div ng-bind-html="snippet"><br/></div></pre>
|
||||
</td>
|
||||
<td>
|
||||
<div ng-bind-html="snippet"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="escaped-html">
|
||||
<td>no filter</td>
|
||||
<td><pre><div ng-bind="snippet"><br/></div></pre></td>
|
||||
<td><div ng-bind="snippet"></div></td>
|
||||
</tr>
|
||||
<tr id="html-unsafe-filter">
|
||||
<td>unsafe html filter</td>
|
||||
<td><pre><div ng-bind-html-unsafe="snippet"><br/></div></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("<p style=\"color:blue\">an html\n" +
|
||||
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
|
||||
"snippet</p>");
|
||||
});
|
||||
|
||||
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 <b>text</b>");
|
||||
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,"<");
|
||||
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, '&').
|
||||
replace(NON_ALPHANUMERIC_REGEXP, function(value){
|
||||
return '&#' + value.charCodeAt(0) + ';';
|
||||
}).
|
||||
replace(/</g, '<').
|
||||
replace(/>/g, '>');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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><div ng-bind-html="snippet | linky"><br></div></pre>
|
||||
</td>
|
||||
<td>
|
||||
<div ng-bind-html="snippet | linky"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="linky-target">
|
||||
<td>linky target</td>
|
||||
<td>
|
||||
<pre><div ng-bind-html="snippetWithTarget | linky:'_blank'"><br></div></pre>
|
||||
</td>
|
||||
<td>
|
||||
<div ng-bind-html="snippetWithTarget | linky:'_blank'"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="escaped-html">
|
||||
<td>no filter</td>
|
||||
<td><pre><div ng-bind="snippet"><br></div></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: ' +
|
||||
'<a href="http://angularjs.org/">http://angularjs.org/</a>, ' +
|
||||
'<a href="mailto:us@somewhere.org">us@somewhere.org</a>, ' +
|
||||
'<a href="mailto:another@somewhere.org">another@somewhere.org</a>, ' +
|
||||
'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);
|
||||
@@ -1,13 +0,0 @@
|
||||
/*
|
||||
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,"<");return l.innerText||l.textContent||""}function t(a){return a.replace(/&/g,"&").replace(F,function(a){return"&#"+a.charCodeAt(0)+";"}).replace(/</g,"<").replace(/>/g,">")}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);
|
||||
28463
docs-web/src/main/webapp/lib/angular/angular-scenario.js
vendored
28463
docs-web/src/main/webapp/lib/angular/angular-scenario.js
vendored
File diff suppressed because it is too large
Load Diff
16884
docs-web/src/main/webapp/lib/angular/angular.js
vendored
16884
docs-web/src/main/webapp/lib/angular/angular.js
vendored
File diff suppressed because it is too large
Load Diff
178
docs-web/src/main/webapp/lib/angular/angular.min.js
vendored
178
docs-web/src/main/webapp/lib/angular/angular.min.js
vendored
@@ -1,178 +0,0 @@
|
||||
/*
|
||||
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> </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>');
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user