diff --git a/README.md b/README.md
index 64e71255..6652a14a 100644
--- a/README.md
+++ b/README.md
@@ -24,13 +24,14 @@ Features
- Support image and PDF files
- Flexible search engine
- Full text search in image and PDF
-- SHA-256 encryption
+- 256-bit AES encryption
- Tag system
- Multi-users ACL system
- Audit log
- Document sharing by URL
- RESTful Web API
- Fully featured Android client
+- Tested to 100k documents
Download
--------
diff --git a/docs-android/app/app.iml b/docs-android/app/app.iml
index d5ce36f5..afd2300c 100644
--- a/docs-android/app/app.iml
+++ b/docs-android/app/app.iml
@@ -12,10 +12,12 @@
-
-
+
+ generateDebugAndroidTestSources
+ generateDebugSources
+
@@ -24,7 +26,7 @@
-
+
@@ -34,13 +36,13 @@
-
+
-
+
@@ -75,9 +77,9 @@
-
-
-
+
+
+
@@ -106,16 +108,16 @@
-
+
+
-
-
-
+
+
\ No newline at end of file
diff --git a/docs-android/app/build.gradle b/docs-android/app/build.gradle
index c9195ab8..e162b17a 100644
--- a/docs-android/app/build.gradle
+++ b/docs-android/app/build.gradle
@@ -3,7 +3,7 @@ buildscript {
mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:1.2.3'
+ classpath 'com.android.tools.build:gradle:1.3.0'
}
}
apply plugin: 'com.android.application'
@@ -14,7 +14,7 @@ repositories {
android {
compileSdkVersion 22
- buildToolsVersion "22.0.1"
+ buildToolsVersion "23.0.2"
defaultConfig {
minSdkVersion 14
@@ -50,8 +50,8 @@ android {
dependencies {
compile fileTree(dir: 'libs', include: '*.jar')
- compile 'com.android.support:appcompat-v7:22.1.1'
- compile 'com.android.support:recyclerview-v7:22.0.0'
+ compile 'com.android.support:appcompat-v7:22.+'
+ compile 'com.android.support:recyclerview-v7:22.+'
compile 'com.loopj.android:android-async-http:1.4.6'
compile 'it.sephiroth.android.library.imagezoom:imagezoom:1.0.5'
compile 'de.greenrobot:eventbus:2.4.0'
diff --git a/docs-android/app/src/main/java/com/sismics/docs/activity/DocumentViewActivity.java b/docs-android/app/src/main/java/com/sismics/docs/activity/DocumentViewActivity.java
index 5ecc3dee..ea2e8cbe 100644
--- a/docs-android/app/src/main/java/com/sismics/docs/activity/DocumentViewActivity.java
+++ b/docs-android/app/src/main/java/com/sismics/docs/activity/DocumentViewActivity.java
@@ -168,7 +168,7 @@ public class DocumentViewActivity extends AppCompatActivity {
createdDateTextView.setText(date);
TextView descriptionTextView = (TextView) findViewById(R.id.descriptionTextView);
- if (description == null || description.isEmpty()) {
+ if (description == null || description.isEmpty() || description.equals(JSONObject.NULL.toString())) {
descriptionTextView.setVisibility(View.GONE);
} else {
descriptionTextView.setVisibility(View.VISIBLE);
diff --git a/docs-android/app/src/main/java/com/sismics/docs/listener/RecyclerItemClickListener.java b/docs-android/app/src/main/java/com/sismics/docs/listener/RecyclerItemClickListener.java
index 084a20e3..4287396b 100644
--- a/docs-android/app/src/main/java/com/sismics/docs/listener/RecyclerItemClickListener.java
+++ b/docs-android/app/src/main/java/com/sismics/docs/listener/RecyclerItemClickListener.java
@@ -11,7 +11,7 @@ public class RecyclerItemClickListener implements RecyclerView.OnItemTouchListen
private OnItemClickListener mListener;
public interface OnItemClickListener {
- public void onItemClick(View view, int position);
+ void onItemClick(View view, int position);
}
GestureDetector mGestureDetector;
@@ -25,7 +25,8 @@ public class RecyclerItemClickListener implements RecyclerView.OnItemTouchListen
});
}
- @Override public boolean onInterceptTouchEvent(RecyclerView view, MotionEvent e) {
+ @Override
+ public boolean onInterceptTouchEvent(RecyclerView view, MotionEvent e) {
View childView = view.findChildViewUnder(e.getX(), e.getY());
if (childView != null && mListener != null && mGestureDetector.onTouchEvent(e)) {
mListener.onItemClick(childView, view.getChildPosition(childView));
@@ -33,5 +34,9 @@ public class RecyclerItemClickListener implements RecyclerView.OnItemTouchListen
return false;
}
- @Override public void onTouchEvent(RecyclerView view, MotionEvent motionEvent) { }
+ @Override
+ public void onTouchEvent(RecyclerView view, MotionEvent motionEvent) { }
+
+ @Override
+ public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { }
}
\ No newline at end of file
diff --git a/docs-core/pom.xml b/docs-core/pom.xml
index 69acf4c0..cf4d3719 100644
--- a/docs-core/pom.xml
+++ b/docs-core/pom.xml
@@ -113,6 +113,11 @@
bcprov-jdk15on
+
+ com.levigo.jbig2
+ levigo-jbig2-imageio
+
+
jna
diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/TagDao.java b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/TagDao.java
index da108dd9..dbff4438 100644
--- a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/TagDao.java
+++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/TagDao.java
@@ -103,7 +103,7 @@ public class TagDao {
@SuppressWarnings("unchecked")
public List getByDocumentId(String documentId, String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
- StringBuilder sb = new StringBuilder("select t.TAG_ID_C, t.TAG_NAME_C, t.TAG_COLOR_C from T_DOCUMENT_TAG dt ");
+ StringBuilder sb = new StringBuilder("select t.TAG_ID_C, t.TAG_NAME_C, t.TAG_COLOR_C, t.TAG_IDPARENT_C from T_DOCUMENT_TAG dt ");
sb.append(" join T_TAG t on t.TAG_ID_C = dt.DOT_IDTAG_C ");
sb.append(" where dt.DOT_IDDOCUMENT_C = :documentId and t.TAG_DELETEDATE_D is null ");
sb.append(" and t.TAG_IDUSER_C = :userId and dt.DOT_DELETEDATE_D is null ");
@@ -123,6 +123,7 @@ public class TagDao {
tagDto.setId((String) o[i++]);
tagDto.setName((String) o[i++]);
tagDto.setColor((String) o[i++]);
+ tagDto.setParentId((String) o[i++]);
tagDtoList.add(tagDto);
}
return tagDtoList;
@@ -137,7 +138,7 @@ public class TagDao {
@SuppressWarnings("unchecked")
public List getStats(String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
- StringBuilder sb = new StringBuilder("select t.TAG_ID_C, t.TAG_NAME_C, t.TAG_COLOR_C, count(d.DOC_ID_C) ");
+ StringBuilder sb = new StringBuilder("select t.TAG_ID_C, t.TAG_NAME_C, t.TAG_COLOR_C, t.TAG_IDPARENT_C, count(d.DOC_ID_C) ");
sb.append(" from T_TAG t ");
sb.append(" left join T_DOCUMENT_TAG dt on t.TAG_ID_C = dt.DOT_IDTAG_C and dt.DOT_DELETEDATE_D is null ");
sb.append(" left join T_DOCUMENT d on d.DOC_ID_C = dt.DOT_IDDOCUMENT_C and d.DOC_DELETEDATE_D is null and d.DOC_IDUSER_C = :userId ");
@@ -158,6 +159,7 @@ public class TagDao {
tagDto.setId((String) o[i++]);
tagDto.setName((String) o[i++]);
tagDto.setColor((String) o[i++]);
+ tagDto.setParentId((String) o[i++]);
tagDto.setCount(((Number) o[i++]).intValue());
tagStatDtoList.add(tagDto);
}
@@ -281,6 +283,7 @@ public class TagDao {
// Update the tag
tagFromDb.setName(tag.getName());
tagFromDb.setColor(tag.getColor());
+ tagFromDb.setParentId(tag.getParentId());
// Create audit log
AuditLogUtil.create(tagFromDb, AuditLogType.UPDATE);
diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/dto/TagDto.java b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/dto/TagDto.java
index 343acd90..6666015d 100644
--- a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/dto/TagDto.java
+++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/dto/TagDto.java
@@ -23,6 +23,11 @@ public class TagDto {
* Color.
*/
private String color;
+
+ /**
+ * Parent ID.
+ */
+ private String parentId;
/**
* Getter of id.
@@ -77,4 +82,22 @@ public class TagDto {
public void setColor(String color) {
this.color = color;
}
+
+ /**
+ * Getter of parentId.
+ *
+ * @return the parentId
+ */
+ public String getParentId() {
+ return parentId;
+ }
+
+ /**
+ * Setter of parentId.
+ *
+ * @param color parentId
+ */
+ public void setParentId(String parentId) {
+ this.parentId = parentId;
+ }
}
diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/dto/TagStatDto.java b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/dto/TagStatDto.java
index cb54a7d7..03b0e76c 100644
--- a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/dto/TagStatDto.java
+++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/dto/TagStatDto.java
@@ -2,7 +2,7 @@ package com.sismics.docs.core.dao.jpa.dto;
/**
- * Tag DTO.
+ * Tag stat DTO.
*
* @author bgamard
*/
diff --git a/docs-core/src/main/java/com/sismics/docs/core/model/jpa/Tag.java b/docs-core/src/main/java/com/sismics/docs/core/model/jpa/Tag.java
index 70fcba4c..e556c7d2 100644
--- a/docs-core/src/main/java/com/sismics/docs/core/model/jpa/Tag.java
+++ b/docs-core/src/main/java/com/sismics/docs/core/model/jpa/Tag.java
@@ -39,6 +39,12 @@ public class Tag implements Loggable {
@Column(name = "TAG_IDUSER_C", nullable = false, length = 36)
private String userId;
+ /**
+ * User ID.
+ */
+ @Column(name = "TAG_IDPARENT_C", length = 36)
+ private String parentId;
+
/**
* Creation date.
*/
@@ -165,12 +171,31 @@ public class Tag implements Loggable {
public void setDeleteDate(Date deleteDate) {
this.deleteDate = deleteDate;
}
+
+ /**
+ * Getter of parentId.
+ *
+ * @return parentId
+ */
+ public String getParentId() {
+ return parentId;
+ }
+
+ /**
+ * Setter of parentId.
+ *
+ * @param parentId parentId
+ */
+ public void setParentId(String parentId) {
+ this.parentId = parentId;
+ }
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("id", id)
.add("name", name)
+ .add("parentId", parentId)
.toString();
}
diff --git a/docs-core/src/main/java/com/sismics/docs/core/util/FileUtil.java b/docs-core/src/main/java/com/sismics/docs/core/util/FileUtil.java
index 702160b3..40e04e15 100644
--- a/docs-core/src/main/java/com/sismics/docs/core/util/FileUtil.java
+++ b/docs-core/src/main/java/com/sismics/docs/core/util/FileUtil.java
@@ -105,7 +105,7 @@ public class FileUtil {
PDDocument pdfDocument = null;
try {
PDFTextStripper stripper = new PDFTextStripper();
- pdfDocument = PDDocument.load(inputStream, true);
+ pdfDocument = PDDocument.load(inputStream);
content = stripper.getText(pdfDocument);
} catch (IOException e) {
log.error("Error while extracting text from the PDF", e);
@@ -157,7 +157,7 @@ public class FileUtil {
// Generate preview from the first page of the PDF
PDDocument pdfDocument = null;
try {
- pdfDocument = PDDocument.load(inputStream, true);
+ pdfDocument = PDDocument.load(inputStream);
PDFRenderer renderer = new PDFRenderer(pdfDocument);
image = renderer.renderImage(0);
} finally {
diff --git a/docs-core/src/main/resources/config.properties b/docs-core/src/main/resources/config.properties
index 9484a82d..d8b90e8b 100644
--- a/docs-core/src/main/resources/config.properties
+++ b/docs-core/src/main/resources/config.properties
@@ -1 +1 @@
-db.version=1
\ No newline at end of file
+db.version=2
\ No newline at end of file
diff --git a/docs-core/src/main/resources/db/update/dbupdate-002-0.sql b/docs-core/src/main/resources/db/update/dbupdate-002-0.sql
new file mode 100644
index 00000000..14551cf3
--- /dev/null
+++ b/docs-core/src/main/resources/db/update/dbupdate-002-0.sql
@@ -0,0 +1,2 @@
+alter table T_TAG add column TAG_IDPARENT_C varchar(36);
+update T_CONFIG set CFG_VALUE_C = '2' where CFG_ID_C = 'DB_VERSION';
diff --git a/docs-parent/pom.xml b/docs-parent/pom.xml
index 44d9d50a..5f3882e1 100644
--- a/docs-parent/pom.xml
+++ b/docs-parent/pom.xml
@@ -35,6 +35,7 @@
2.8.2
4.1.0.Final
3.1.0
+ 1.6.3
9.2.13.v20150730
9.2.13.v20150730
@@ -66,6 +67,12 @@
true
+
+
+ jbig2.googlecode
+ JBIG2 ImageIO-Plugin repository at googlecode.com
+ http://jbig2-imageio.googlecode.com/svn/maven-repository
+
@@ -252,11 +259,11 @@
- org.glassfish.jersey.test-framework.providers
- jersey-test-framework-provider-bundle
- pom
- ${org.glassfish.jersey.version}
-
+ org.glassfish.jersey.test-framework.providers
+ jersey-test-framework-provider-bundle
+ pom
+ ${org.glassfish.jersey.version}
+
org.glassfish.jersey.test-framework.providers
@@ -295,10 +302,10 @@
- org.hibernate
- hibernate-validator
- ${org.hibernate.hibernate.version}
-
+ org.hibernate
+ hibernate-validator
+ ${org.hibernate.hibernate.version}
+
commons-dbcp
@@ -354,6 +361,13 @@
${org.bouncycastle.bcprov-jdk15on.version}
+
+
+ com.levigo.jbig2
+ levigo-jbig2-imageio
+ ${com.levigo.jbig2.levigo-jbig2-imageio.version}
+
+
jna
diff --git a/docs-web/src/dev/resources/config.properties b/docs-web/src/dev/resources/config.properties
index f2362d2f..87577f48 100644
--- a/docs-web/src/dev/resources/config.properties
+++ b/docs-web/src/dev/resources/config.properties
@@ -1,3 +1,3 @@
api.current_version=${project.version}
api.min_version=1.0
-db.version=1
\ No newline at end of file
+db.version=2
\ No newline at end of file
diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/AuditLogResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/AuditLogResource.java
index 8c128e06..a1ad4e72 100644
--- a/docs-web/src/main/java/com/sismics/docs/rest/resource/AuditLogResource.java
+++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/AuditLogResource.java
@@ -19,6 +19,7 @@ import com.sismics.docs.core.util.jpa.PaginatedLists;
import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.rest.exception.ForbiddenClientException;
import com.sismics.rest.exception.ServerException;
+import com.sismics.rest.util.JsonUtil;
/**
* Audit log REST resources.
@@ -70,7 +71,7 @@ public class AuditLogResource extends BaseResource {
.add("target", auditLogDto.getEntityId())
.add("class", auditLogDto.getEntityClass())
.add("type", auditLogDto.getType().name())
- .add("message", auditLogDto.getMessage())
+ .add("message", JsonUtil.nullable(auditLogDto.getMessage()))
.add("create_date", auditLogDto.getCreateTimestamp()));
}
diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java
index 05b8cc4b..11883aab 100644
--- a/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java
+++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java
@@ -22,6 +22,7 @@ import com.sismics.docs.core.dao.jpa.dto.TagStatDto;
import com.sismics.docs.core.model.jpa.Tag;
import com.sismics.rest.exception.ClientException;
import com.sismics.rest.exception.ForbiddenClientException;
+import com.sismics.rest.util.JsonUtil;
import com.sismics.rest.util.ValidationUtil;
/**
@@ -50,7 +51,8 @@ public class TagResource extends BaseResource {
items.add(Json.createObjectBuilder()
.add("id", tag.getId())
.add("name", tag.getName())
- .add("color", tag.getColor()));
+ .add("color", tag.getColor())
+ .add("parent", JsonUtil.nullable(tag.getParentId())));
}
JsonObjectBuilder response = Json.createObjectBuilder()
@@ -96,7 +98,8 @@ public class TagResource extends BaseResource {
@PUT
public Response add(
@FormParam("name") String name,
- @FormParam("color") String color) {
+ @FormParam("color") String color,
+ @FormParam("parent") String parentId) {
if (!authenticate()) {
throw new ForbiddenClientException();
}
@@ -117,11 +120,22 @@ public class TagResource extends BaseResource {
throw new ClientException("AlreadyExistingTag", MessageFormat.format("Tag already exists: {0}", name));
}
+ // Check the parent
+ if (StringUtils.isEmpty(parentId)) {
+ parentId = null;
+ } else {
+ Tag parentTag = tagDao.getByTagId(principal.getId(), parentId);
+ if (parentTag == null) {
+ throw new ClientException("ParentNotFound", MessageFormat.format("Parent not found: {0}", parentId));
+ }
+ }
+
// Create the tag
tag = new Tag();
tag.setName(name);
tag.setColor(color);
tag.setUserId(principal.getId());
+ tag.setParentId(parentId);
String id = tagDao.create(tag);
JsonObjectBuilder response = Json.createObjectBuilder()
@@ -140,7 +154,8 @@ public class TagResource extends BaseResource {
public Response update(
@PathParam("id") String id,
@FormParam("name") String name,
- @FormParam("color") String color) {
+ @FormParam("color") String color,
+ @FormParam("parent") String parentId) {
if (!authenticate()) {
throw new ForbiddenClientException();
}
@@ -161,6 +176,16 @@ public class TagResource extends BaseResource {
throw new ClientException("TagNotFound", MessageFormat.format("Tag not found: {0}", id));
}
+ // Check the parent
+ if (StringUtils.isEmpty(parentId)) {
+ parentId = null;
+ } else {
+ Tag parentTag = tagDao.getByTagId(principal.getId(), parentId);
+ if (parentTag == null) {
+ throw new ClientException("ParentNotFound", MessageFormat.format("Parent not found: {0}", parentId));
+ }
+ }
+
// Check for name duplicate
Tag tagDuplicate = tagDao.getByName(principal.getId(), name);
if (tagDuplicate != null && !tagDuplicate.getId().equals(id)) {
@@ -174,6 +199,8 @@ public class TagResource extends BaseResource {
if (!StringUtils.isEmpty(color)) {
tag.setColor(color);
}
+ // Parent tag is always updated to have the possibility to delete it
+ tag.setParentId(parentId);
tagDao.update(tag);
diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/UserResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/UserResource.java
index 501a4e9b..b1fe41eb 100644
--- a/docs-web/src/main/java/com/sismics/docs/rest/resource/UserResource.java
+++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/UserResource.java
@@ -40,6 +40,7 @@ import com.sismics.docs.rest.constant.BaseFunction;
import com.sismics.rest.exception.ClientException;
import com.sismics.rest.exception.ForbiddenClientException;
import com.sismics.rest.exception.ServerException;
+import com.sismics.rest.util.JsonUtil;
import com.sismics.rest.util.ValidationUtil;
import com.sismics.security.UserPrincipal;
import com.sismics.util.filter.TokenBasedSecurityFilter;
@@ -513,8 +514,8 @@ public class UserResource extends BaseResource {
for (AuthenticationToken authenticationToken : authenticationTokenDao.getByUserId(principal.getId())) {
JsonObjectBuilder session = Json.createObjectBuilder()
.add("create_date", authenticationToken.getCreationDate().getTime())
- .add("ip", authenticationToken.getIp())
- .add("user_agent", authenticationToken.getUserAgent());
+ .add("ip", JsonUtil.nullable(authenticationToken.getIp()))
+ .add("user_agent", JsonUtil.nullable(authenticationToken.getUserAgent()));
if (authenticationToken.getLastConnectionDate() != null) {
session.add("last_connection_date", authenticationToken.getLastConnectionDate().getTime());
}
diff --git a/docs-web/src/main/webapp/src/app/docs/app.js b/docs-web/src/main/webapp/src/app/docs/app.js
index 4a88388a..5a1860c3 100644
--- a/docs-web/src/main/webapp/src/app/docs/app.js
+++ b/docs-web/src/main/webapp/src/app/docs/app.js
@@ -125,6 +125,9 @@ angular.module('docs',
}
}
})
+ .state('document.default.search', {
+ url: '/search/:search'
+ })
.state('document.default.file', {
url: '/file/:fileId',
views: {
@@ -153,6 +156,7 @@ angular.module('docs',
})
.state('document.view', {
url: '/view/:id',
+ redirectTo: 'document.view.content',
views: {
'document': {
templateUrl: 'partial/docs/document.view.html',
@@ -160,6 +164,33 @@ angular.module('docs',
}
}
})
+ .state('document.view.content', {
+ url: '/content',
+ views: {
+ 'tab': {
+ templateUrl: 'partial/docs/document.view.content.html',
+ controller: 'DocumentViewContent'
+ }
+ }
+ })
+ .state('document.view.permissions', {
+ url: '/permissions',
+ views: {
+ 'tab': {
+ templateUrl: 'partial/docs/document.view.permissions.html',
+ controller: 'DocumentViewPermissions'
+ }
+ }
+ })
+ .state('document.view.activity', {
+ url: '/activity',
+ views: {
+ 'tab': {
+ templateUrl: 'partial/docs/document.view.activity.html',
+ controller: 'DocumentViewActivity'
+ }
+ }
+ })
.state('document.view.file', {
url: '/file/:fileId',
views: {
@@ -228,4 +259,18 @@ angular.module('docs',
$rootScope.$state = $state;
$rootScope.$stateParams = $stateParams;
$rootScope.pageTitle = 'Sismics Docs';
+})
+/**
+ * Redirection support for ui-router.
+ * Thanks to https://github.com/acollard
+ * See https://github.com/angular-ui/ui-router/issues/1584#issuecomment-76993045
+ */
+.run(function($rootScope, $state){
+ $rootScope.$on('$stateChangeStart', function(event, toState, toParams) {
+ var redirect = toState.redirectTo;
+ if (redirect) {
+ event.preventDefault();
+ $state.go(redirect, toParams);
+ }
+ });
});
\ No newline at end of file
diff --git a/docs-web/src/main/webapp/src/app/docs/controller/Document.js b/docs-web/src/main/webapp/src/app/docs/controller/Document.js
index 6af0d944..4497d41a 100644
--- a/docs-web/src/main/webapp/src/app/docs/controller/Document.js
+++ b/docs-web/src/main/webapp/src/app/docs/controller/Document.js
@@ -12,7 +12,8 @@ angular.module('docs').controller('Document', function($scope, $timeout, $state,
$scope.offset = 0;
$scope.currentPage = 1;
$scope.limit = _.isUndefined(localStorage.documentsPageSize) ? 10 : localStorage.documentsPageSize;
- $scope.search = '';
+ $scope.search = $state.params.search ? $state.params.search : '';
+ $scope.setSearch = function(search) { $scope.search = search };
// A timeout promise is used to slow down search requests to the server
// We keep track of it for cancellation purpose
@@ -65,6 +66,17 @@ angular.module('docs').controller('Document', function($scope, $timeout, $state,
$timeout.cancel(timeoutPromise);
}
+ if ($state.current.name == 'document.default'
+ || $state.current.name == 'document.default.search') {
+ $state.go($scope.search == '' ?
+ 'document.default' : 'document.default.search', {
+ search: $scope.search
+ }, {
+ location: 'replace',
+ notify: false
+ });
+ }
+
// Call API later
timeoutPromise = $timeout(function () {
$scope.loadDocuments();
@@ -99,6 +111,22 @@ angular.module('docs').controller('Document', function($scope, $timeout, $state,
* Display a document.
*/
$scope.viewDocument = function(id) {
- $state.transitionTo('document.view', { id: id });
+ $state.go('document.view', { id: id });
+ };
+
+ // Load tags
+ var tags = [];
+ Restangular.one('tag/list').getList().then(function(data) {
+ tags = data.tags;
+ });
+
+ /**
+ * Find children tags.
+ * @param parent
+ */
+ $scope.getChildrenTags = function(parent) {
+ return _.filter(tags, function(tag) {
+ return tag.parent == parent;
+ });
};
});
\ No newline at end of file
diff --git a/docs-web/src/main/webapp/src/app/docs/controller/DocumentDefault.js b/docs-web/src/main/webapp/src/app/docs/controller/DocumentDefault.js
index e5433e09..361ef241 100644
--- a/docs-web/src/main/webapp/src/app/docs/controller/DocumentDefault.js
+++ b/docs-web/src/main/webapp/src/app/docs/controller/DocumentDefault.js
@@ -81,7 +81,7 @@ angular.module('docs').controller('DocumentDefault', function($scope, $state, Re
* Navigate to the selected file.
*/
$scope.openFile = function (file) {
- $state.transitionTo('document.default.file', { fileId: file.id })
+ $state.go('document.default.file', { fileId: file.id })
};
/**
@@ -107,6 +107,6 @@ angular.module('docs').controller('DocumentDefault', function($scope, $state, Re
* Add a document with checked files.
*/
$scope.addDocument = function() {
- $state.transitionTo('document.add', { files: _.pluck($scope.checkedFiles(), 'id') });
+ $state.go('document.add', { files: _.pluck($scope.checkedFiles(), 'id') });
};
});
\ No newline at end of file
diff --git a/docs-web/src/main/webapp/src/app/docs/controller/DocumentEdit.js b/docs-web/src/main/webapp/src/app/docs/controller/DocumentEdit.js
index c0d7d4b3..c9141ec7 100644
--- a/docs-web/src/main/webapp/src/app/docs/controller/DocumentEdit.js
+++ b/docs-web/src/main/webapp/src/app/docs/controller/DocumentEdit.js
@@ -95,7 +95,7 @@ angular.module('docs').controller('DocumentEdit', function($rootScope, $scope, $
if ($scope.isEdit()) {
// Go back to the edited document
$scope.pageDocuments();
- $state.transitionTo('document.view', { id: $stateParams.id });
+ $state.go('document.view', { id: $stateParams.id });
} else {
// Reset the scope and stay here
var fileUploadCount = _.size($scope.newFiles) + resolve.length;
@@ -188,9 +188,9 @@ angular.module('docs').controller('DocumentEdit', function($rootScope, $scope, $
*/
$scope.cancel = function() {
if ($scope.isEdit()) {
- $state.transitionTo('document.view', { id: $stateParams.id });
+ $state.go('document.view', { id: $stateParams.id });
} else {
- $state.transitionTo('document.default');
+ $state.go('document.default');
}
};
diff --git a/docs-web/src/main/webapp/src/app/docs/controller/DocumentView.js b/docs-web/src/main/webapp/src/app/docs/controller/DocumentView.js
index 8962c884..bf71478d 100644
--- a/docs-web/src/main/webapp/src/app/docs/controller/DocumentView.js
+++ b/docs-web/src/main/webapp/src/app/docs/controller/DocumentView.js
@@ -3,7 +3,7 @@
/**
* Document view controller.
*/
-angular.module('docs').controller('DocumentView', function ($scope, $state, $stateParams, $location, $dialog, $modal, Restangular, $upload, $q) {
+angular.module('docs').controller('DocumentView', function ($scope, $state, $stateParams, $location, $dialog, $modal, Restangular, $timeout) {
// Load document data from server
Restangular.one('document', $stateParams.id).get().then(function(data) {
$scope.document = data;
@@ -11,60 +11,6 @@ angular.module('docs').controller('DocumentView', function ($scope, $state, $sta
$scope.error = response;
});
- // Load audit log data from server
- Restangular.one('auditlog').get({
- document: $stateParams.id
- }).then(function(data) {
- $scope.logs = data.logs;
- });
-
- // Watch for ACLs change and group them for easy displaying
- $scope.$watch('document.acls', function(acls) {
- $scope.acls = _.groupBy(acls, function(acl) {
- return acl.id;
- });
- });
-
- // Initialize add ACL
- $scope.acl = { perm: 'READ' };
-
- /**
- * Configuration for file sorting.
- */
- $scope.fileSortableOptions = {
- forceHelperSize: true,
- forcePlaceholderSize: true,
- tolerance: 'pointer',
- handle: '.handle',
- stop: function () {
- // Send new positions to server
- $scope.$apply(function () {
- Restangular.one('file').post('reorder', {
- id: $stateParams.id,
- order: _.pluck($scope.files, 'id')
- });
- });
- }
- };
-
- /**
- * Load files from server.
- */
- $scope.loadFiles = function () {
- Restangular.one('file').getList('list', { id: $stateParams.id }).then(function (data) {
- $scope.files = data.files;
- // TODO Keep currently uploading files
- });
- };
- $scope.loadFiles();
-
- /**
- * Navigate to the selected file.
- */
- $scope.openFile = function (file) {
- $state.transitionTo('document.view.file', { id: $stateParams.id, fileId: file.id })
- };
-
/**
* Delete a document.
*/
@@ -80,27 +26,7 @@ angular.module('docs').controller('DocumentView', function ($scope, $state, $sta
if (result == 'ok') {
Restangular.one('document', document.id).remove().then(function () {
$scope.loadDocuments();
- $state.transitionTo('document.default');
- });
- }
- });
- };
-
- /**
- * Delete a file.
- */
- $scope.deleteFile = function (file) {
- var title = 'Delete file';
- var msg = 'Do you really want to delete this file?';
- var btns = [
- {result: 'cancel', label: 'Cancel'},
- {result: 'ok', label: 'OK', cssClass: 'btn-primary'}
- ];
-
- $dialog.messageBox(title, msg, btns, function (result) {
- if (result == 'ok') {
- Restangular.one('file', file.id).remove().then(function () {
- $scope.loadFiles();
+ $state.go('document.default');
});
}
});
@@ -157,125 +83,4 @@ angular.module('docs').controller('DocumentView', function ($scope, $state, $sta
}
});
};
-
- /**
- * File has been drag & dropped.
- */
- $scope.fileDropped = function(files) {
- if (!$scope.document.writable) {
- return;
- }
-
- if (files && files.length) {
- // Adding files to the UI
- var newfiles = [];
- _.each(files, function(file) {
- var newfile = {
- progress: 0,
- name: file.name,
- create_date: new Date().getTime(),
- mimetype: file.type,
- status: 'Pending...'
- };
- $scope.files.push(newfile);
- newfiles.push(newfile);
- });
-
- // Uploading files sequentially
- var key = 0;
- var then = function() {
- if (files[key]) {
- $scope.uploadFile(files[key], newfiles[key++]).then(then);
- }
- };
- then();
- }
- };
-
- /**
- * Upload a file.
- */
- $scope.uploadFile = function(file, newfile) {
- // Upload the file
- newfile.status = 'Uploading...';
- return $upload.upload({
- method: 'PUT',
- url: '../api/file',
- file: file,
- fields: {
- id: $stateParams.id
- }
- })
- .progress(function (e) {
- newfile.progress = parseInt(100.0 * e.loaded / e.total);
- })
- .success(function (data) {
- newfile.id = data.id;
- });
- };
-
- /**
- * Delete an ACL.
- */
- $scope.deleteAcl = function(acl) {
- Restangular.one('acl/' + $stateParams.id + '/' + acl.perm + '/' + acl.id, null).remove().then(function () {
- $scope.document.acls = _.reject($scope.document.acls, function(s) {
- return angular.equals(acl, s);
- });
- });
- };
-
- /**
- * Add an ACL.
- */
- $scope.addAcl = function() {
- // Compute ACLs to add
- $scope.acl.source = $stateParams.id;
- var acls = [];
- if ($scope.acl.perm == 'READWRITE') {
- acls = [{
- source: $stateParams.id,
- username: $scope.acl.username,
- perm: 'READ'
- }, {
- source: $stateParams.id,
- username: $scope.acl.username,
- perm: 'WRITE'
- }];
- } else {
- acls = [{
- source: $stateParams.id,
- username: $scope.acl.username,
- perm: $scope.acl.perm
- }];
- }
-
- // Add ACLs
- _.each(acls, function(acl) {
- Restangular.one('acl').put(acl).then(function(acl) {
- if (_.isUndefined(acl.id)) {
- return;
- }
- $scope.document.acls.push(acl);
- $scope.document.acls = angular.copy($scope.document.acls);
- });
- });
-
- // Reset form
- $scope.acl = { perm: 'READ' };
- };
-
- /**
- * Auto-complete on ACL target.
- */
- $scope.getTargetAclTypeahead = function($viewValue) {
- var deferred = $q.defer();
- Restangular.one('acl/target/search')
- .get({
- search: $viewValue
- }).then(function(data) {
- deferred.resolve(_.pluck(data.users, 'username'), true);
- });
- return deferred.promise;
- };
});
\ No newline at end of file
diff --git a/docs-web/src/main/webapp/src/app/docs/controller/DocumentViewActivity.js b/docs-web/src/main/webapp/src/app/docs/controller/DocumentViewActivity.js
new file mode 100644
index 00000000..09e702fb
--- /dev/null
+++ b/docs-web/src/main/webapp/src/app/docs/controller/DocumentViewActivity.js
@@ -0,0 +1,13 @@
+'use strict';
+
+/**
+ * Document view activity controller.
+ */
+angular.module('docs').controller('DocumentViewActivity', function ($scope, $stateParams, Restangular) {
+ // Load audit log data from server
+ Restangular.one('auditlog').get({
+ document: $stateParams.id
+ }).then(function(data) {
+ $scope.logs = data.logs;
+ });
+});
\ No newline at end of file
diff --git a/docs-web/src/main/webapp/src/app/docs/controller/DocumentViewContent.js b/docs-web/src/main/webapp/src/app/docs/controller/DocumentViewContent.js
new file mode 100644
index 00000000..bab5b48d
--- /dev/null
+++ b/docs-web/src/main/webapp/src/app/docs/controller/DocumentViewContent.js
@@ -0,0 +1,119 @@
+'use strict';
+
+/**
+ * Document view content controller.
+ */
+angular.module('docs').controller('DocumentViewContent', function ($scope, $stateParams, Restangular, $dialog, $state, $upload) {
+ /**
+ * Configuration for file sorting.
+ */
+ $scope.fileSortableOptions = {
+ forceHelperSize: true,
+ forcePlaceholderSize: true,
+ tolerance: 'pointer',
+ handle: '.handle',
+ stop: function () {
+ // Send new positions to server
+ $scope.$apply(function () {
+ Restangular.one('file').post('reorder', {
+ id: $stateParams.id,
+ order: _.pluck($scope.files, 'id')
+ });
+ });
+ }
+ };
+
+ /**
+ * Load files from server.
+ */
+ $scope.loadFiles = function () {
+ Restangular.one('file').getList('list', { id: $stateParams.id }).then(function (data) {
+ $scope.files = data.files;
+ // TODO Keep currently uploading files
+ });
+ };
+ $scope.loadFiles();
+
+ /**
+ * Navigate to the selected file.
+ */
+ $scope.openFile = function (file) {
+ $state.go('document.view.file', { id: $stateParams.id, fileId: file.id })
+ };
+
+ /**
+ * Delete a file.
+ */
+ $scope.deleteFile = function (file) {
+ var title = 'Delete file';
+ var msg = 'Do you really want to delete this file?';
+ var btns = [
+ {result: 'cancel', label: 'Cancel'},
+ {result: 'ok', label: 'OK', cssClass: 'btn-primary'}
+ ];
+
+ $dialog.messageBox(title, msg, btns, function (result) {
+ if (result == 'ok') {
+ Restangular.one('file', file.id).remove().then(function () {
+ $scope.loadFiles();
+ });
+ }
+ });
+ };
+
+ /**
+ * File has been drag & dropped.
+ */
+ $scope.fileDropped = function(files) {
+ if (!$scope.document.writable) {
+ return;
+ }
+
+ if (files && files.length) {
+ // Adding files to the UI
+ var newfiles = [];
+ _.each(files, function(file) {
+ var newfile = {
+ progress: 0,
+ name: file.name,
+ create_date: new Date().getTime(),
+ mimetype: file.type,
+ status: 'Pending...'
+ };
+ $scope.files.push(newfile);
+ newfiles.push(newfile);
+ });
+
+ // Uploading files sequentially
+ var key = 0;
+ var then = function() {
+ if (files[key]) {
+ $scope.uploadFile(files[key], newfiles[key++]).then(then);
+ }
+ };
+ then();
+ }
+ };
+
+ /**
+ * Upload a file.
+ */
+ $scope.uploadFile = function(file, newfile) {
+ // Upload the file
+ newfile.status = 'Uploading...';
+ return $upload.upload({
+ method: 'PUT',
+ url: '../api/file',
+ file: file,
+ fields: {
+ id: $stateParams.id
+ }
+ })
+ .progress(function (e) {
+ newfile.progress = parseInt(100.0 * e.loaded / e.total);
+ })
+ .success(function (data) {
+ newfile.id = data.id;
+ });
+ };
+});
\ No newline at end of file
diff --git a/docs-web/src/main/webapp/src/app/docs/controller/DocumentViewPermissions.js b/docs-web/src/main/webapp/src/app/docs/controller/DocumentViewPermissions.js
new file mode 100644
index 00000000..2c6ecb5f
--- /dev/null
+++ b/docs-web/src/main/webapp/src/app/docs/controller/DocumentViewPermissions.js
@@ -0,0 +1,81 @@
+'use strict';
+
+/**
+ * Document view permissions controller.
+ */
+angular.module('docs').controller('DocumentViewPermissions', function ($scope, $stateParams, Restangular, $q) {
+ // Watch for ACLs change and group them for easy displaying
+ $scope.$watch('document.acls', function(acls) {
+ $scope.acls = _.groupBy(acls, function(acl) {
+ return acl.id;
+ });
+ });
+
+ // Initialize add ACL
+ $scope.acl = { perm: 'READ' };
+
+ /**
+ * Delete an ACL.
+ */
+ $scope.deleteAcl = function(acl) {
+ Restangular.one('acl/' + $stateParams.id + '/' + acl.perm + '/' + acl.id, null).remove().then(function () {
+ $scope.document.acls = _.reject($scope.document.acls, function(s) {
+ return angular.equals(acl, s);
+ });
+ });
+ };
+
+ /**
+ * Add an ACL.
+ */
+ $scope.addAcl = function() {
+ // Compute ACLs to add
+ $scope.acl.source = $stateParams.id;
+ var acls = [];
+ if ($scope.acl.perm == 'READWRITE') {
+ acls = [{
+ source: $stateParams.id,
+ username: $scope.acl.username,
+ perm: 'READ'
+ }, {
+ source: $stateParams.id,
+ username: $scope.acl.username,
+ perm: 'WRITE'
+ }];
+ } else {
+ acls = [{
+ source: $stateParams.id,
+ username: $scope.acl.username,
+ perm: $scope.acl.perm
+ }];
+ }
+
+ // Add ACLs
+ _.each(acls, function(acl) {
+ Restangular.one('acl').put(acl).then(function(acl) {
+ if (_.isUndefined(acl.id)) {
+ return;
+ }
+ $scope.document.acls.push(acl);
+ $scope.document.acls = angular.copy($scope.document.acls);
+ });
+ });
+
+ // Reset form
+ $scope.acl = { perm: 'READ' };
+ };
+
+ /**
+ * Auto-complete on ACL target.
+ */
+ $scope.getTargetAclTypeahead = function($viewValue) {
+ var deferred = $q.defer();
+ Restangular.one('acl/target/search')
+ .get({
+ search: $viewValue
+ }).then(function(data) {
+ deferred.resolve(_.pluck(data.users, 'username'), true);
+ });
+ return deferred.promise;
+ };
+});
\ No newline at end of file
diff --git a/docs-web/src/main/webapp/src/app/docs/controller/Login.js b/docs-web/src/main/webapp/src/app/docs/controller/Login.js
index 14340858..9c695aa3 100644
--- a/docs-web/src/main/webapp/src/app/docs/controller/Login.js
+++ b/docs-web/src/main/webapp/src/app/docs/controller/Login.js
@@ -9,7 +9,7 @@ angular.module('docs').controller('Login', function($scope, $rootScope, $state,
User.userInfo(true).then(function(data) {
$rootScope.userInfo = data;
});
- $state.transitionTo('document.default');
+ $state.go('document.default');
}, function() {
var title = 'Login failed';
var msg = 'Username or password invalid';
diff --git a/docs-web/src/main/webapp/src/app/docs/controller/Main.js b/docs-web/src/main/webapp/src/app/docs/controller/Main.js
index bb56d24e..5dc8ad0d 100644
--- a/docs-web/src/main/webapp/src/app/docs/controller/Main.js
+++ b/docs-web/src/main/webapp/src/app/docs/controller/Main.js
@@ -6,9 +6,13 @@
angular.module('docs').controller('Main', function($scope, $rootScope, $state, User) {
User.userInfo().then(function(data) {
if (data.anonymous) {
- $state.transitionTo('login');
+ $state.go('login', {}, {
+ location: 'replace'
+ });
} else {
- $state.transitionTo('document.default');
+ $state.go('document.default', {}, {
+ location: 'replace'
+ });
}
});
});
\ No newline at end of file
diff --git a/docs-web/src/main/webapp/src/app/docs/controller/Navigation.js b/docs-web/src/main/webapp/src/app/docs/controller/Navigation.js
index 64417aa9..d21bbb09 100644
--- a/docs-web/src/main/webapp/src/app/docs/controller/Navigation.js
+++ b/docs-web/src/main/webapp/src/app/docs/controller/Navigation.js
@@ -41,7 +41,7 @@ angular.module('docs').controller('Navigation', function($scope, $http, $state,
*/
$scope.openLogs = function() {
$scope.errorNumber = 0;
- $state.transitionTo('settings.log');
+ $state.go('settings.log');
};
/**
@@ -52,7 +52,7 @@ angular.module('docs').controller('Navigation', function($scope, $http, $state,
User.userInfo(true).then(function(data) {
$rootScope.userInfo = data;
});
- $state.transitionTo('main');
+ $state.go('main');
});
$event.preventDefault();
};
diff --git a/docs-web/src/main/webapp/src/app/docs/controller/SettingsUser.js b/docs-web/src/main/webapp/src/app/docs/controller/SettingsUser.js
index f65160aa..8a499264 100644
--- a/docs-web/src/main/webapp/src/app/docs/controller/SettingsUser.js
+++ b/docs-web/src/main/webapp/src/app/docs/controller/SettingsUser.js
@@ -19,6 +19,6 @@ angular.module('docs').controller('SettingsUser', function($scope, $state, Resta
* Edit a user.
*/
$scope.editUser = function(user) {
- $state.transitionTo('settings.user.edit', { username: user.username });
+ $state.go('settings.user.edit', { username: user.username });
};
});
\ No newline at end of file
diff --git a/docs-web/src/main/webapp/src/app/docs/controller/SettingsUserEdit.js b/docs-web/src/main/webapp/src/app/docs/controller/SettingsUserEdit.js
index ecd4d027..91537989 100644
--- a/docs-web/src/main/webapp/src/app/docs/controller/SettingsUserEdit.js
+++ b/docs-web/src/main/webapp/src/app/docs/controller/SettingsUserEdit.js
@@ -38,7 +38,7 @@ angular.module('docs').controller('SettingsUserEdit', function($scope, $dialog,
promise.then(function() {
$scope.loadUsers();
- $state.transitionTo('settings.user');
+ $state.go('settings.user');
});
};
@@ -54,9 +54,9 @@ angular.module('docs').controller('SettingsUserEdit', function($scope, $dialog,
if (result == 'ok') {
Restangular.one('user', $stateParams.username).remove().then(function() {
$scope.loadUsers();
- $state.transitionTo('settings.user');
+ $state.go('settings.user');
}, function () {
- $state.transitionTo('settings.user');
+ $state.go('settings.user');
});
}
});
diff --git a/docs-web/src/main/webapp/src/app/share/controller/FileModalView.js b/docs-web/src/main/webapp/src/app/share/controller/FileModalView.js
index 8426022d..f705378c 100644
--- a/docs-web/src/main/webapp/src/app/share/controller/FileModalView.js
+++ b/docs-web/src/main/webapp/src/app/share/controller/FileModalView.js
@@ -24,7 +24,7 @@ angular.module('share').controller('FileModalView', function($rootScope, $modalI
if (value.id == $stateParams.fileId) {
var next = $scope.files[key + 1];
if (next) {
- $state.transitionTo('share.file', { documentId: $stateParams.documentId, shareId: $stateParams.shareId, fileId: next.id });
+ $state.go('share.file', { documentId: $stateParams.documentId, shareId: $stateParams.shareId, fileId: next.id });
}
}
});
@@ -38,7 +38,7 @@ angular.module('share').controller('FileModalView', function($rootScope, $modalI
if (value.id == $stateParams.fileId) {
var previous = $scope.files[key - 1];
if (previous) {
- $state.transitionTo('share.file', { documentId: $stateParams.documentId, shareId: $stateParams.shareId, fileId: previous.id });
+ $state.go('share.file', { documentId: $stateParams.documentId, shareId: $stateParams.shareId, fileId: previous.id });
}
}
});
diff --git a/docs-web/src/main/webapp/src/app/share/controller/FileView.js b/docs-web/src/main/webapp/src/app/share/controller/FileView.js
index 3ea83514..07e45b23 100644
--- a/docs-web/src/main/webapp/src/app/share/controller/FileView.js
+++ b/docs-web/src/main/webapp/src/app/share/controller/FileView.js
@@ -16,6 +16,6 @@ angular.module('share').controller('FileView', function($modal, $state, $statePa
modal.closed = true;
},function(result) {
modal.closed = true;
- $state.transitionTo('share', { documentId: $stateParams.documentId, shareId: $stateParams.shareId });
+ $state.go('share', { documentId: $stateParams.documentId, shareId: $stateParams.shareId });
});
});
\ No newline at end of file
diff --git a/docs-web/src/main/webapp/src/app/share/controller/Share.js b/docs-web/src/main/webapp/src/app/share/controller/Share.js
index f3ddda15..c1e15ae6 100644
--- a/docs-web/src/main/webapp/src/app/share/controller/Share.js
+++ b/docs-web/src/main/webapp/src/app/share/controller/Share.js
@@ -10,7 +10,7 @@ angular.module('share').controller('Share', function($scope, $state, $stateParam
$scope.document = data;
}, function (response) {
if (response.status == 403) {
- $state.transitionTo('403');
+ $state.go('403');
}
});
@@ -24,6 +24,6 @@ angular.module('share').controller('Share', function($scope, $state, $stateParam
* Navigate to the selected file.
*/
$scope.openFile = function (file) {
- $state.transitionTo('share.file', { documentId: $stateParams.documentId, shareId: $stateParams.shareId, fileId: file.id })
+ $state.go('share.file', { documentId: $stateParams.documentId, shareId: $stateParams.shareId, fileId: file.id })
};
});
\ No newline at end of file
diff --git a/docs-web/src/main/webapp/src/index.html b/docs-web/src/main/webapp/src/index.html
index 6247f46b..9fb47b64 100644
--- a/docs-web/src/main/webapp/src/index.html
+++ b/docs-web/src/main/webapp/src/index.html
@@ -43,6 +43,9 @@
+
+
+
diff --git a/docs-web/src/main/webapp/src/partial/docs/document.html b/docs-web/src/main/webapp/src/partial/docs/document.html
index e4b91b11..207e4374 100644
--- a/docs-web/src/main/webapp/src/partial/docs/document.html
+++ b/docs-web/src/main/webapp/src/partial/docs/document.html
@@ -5,49 +5,61 @@
Add a document
-
\ No newline at end of file
+
+
+
\ No newline at end of file
diff --git a/docs-web/src/main/webapp/src/partial/docs/document.view.activity.html b/docs-web/src/main/webapp/src/partial/docs/document.view.activity.html
new file mode 100644
index 00000000..6726c5ed
--- /dev/null
+++ b/docs-web/src/main/webapp/src/partial/docs/document.view.activity.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs-web/src/main/webapp/src/partial/docs/document.view.content.html b/docs-web/src/main/webapp/src/partial/docs/document.view.content.html
new file mode 100644
index 00000000..9e1a3021
--- /dev/null
+++ b/docs-web/src/main/webapp/src/partial/docs/document.view.content.html
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+ {{ file.status }}
+
+
+
+
+
+
+
+ Drag & drop files here to upload
+
+
+
\ No newline at end of file
diff --git a/docs-web/src/main/webapp/src/partial/docs/document.view.html b/docs-web/src/main/webapp/src/partial/docs/document.view.html
index 2643b426..82a608f4 100644
--- a/docs-web/src/main/webapp/src/partial/docs/document.view.html
+++ b/docs-web/src/main/webapp/src/partial/docs/document.view.html
@@ -38,119 +38,24 @@
-
-
-
+
-
-
-
-
-
-
-
-
-
-
- {{ file.status }}
-
-
-
-
-
-
-
- Drag & drop files here to upload
-
-
-
-
-
-
-
+
+
+
+
Permissions
-
-
-
-
- For
- Permission
-
-
-
- {{ acl[0].type == 'SHARE' ? 'Shared' : 'User' }} {{ acl[0].name }}
-
-
- {{ a.perm }}
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
Activity
-
-
-
-
-
+
+
+
+
\ No newline at end of file
diff --git a/docs-web/src/main/webapp/src/partial/docs/document.view.permissions.html b/docs-web/src/main/webapp/src/partial/docs/document.view.permissions.html
new file mode 100644
index 00000000..c8921061
--- /dev/null
+++ b/docs-web/src/main/webapp/src/partial/docs/document.view.permissions.html
@@ -0,0 +1,53 @@
+
+
+ For
+ Permission
+
+
+
+ {{ acl[0].type == 'SHARE' ? 'Shared' : 'User' }} {{ acl[0].name }}
+
+
+ {{ a.perm }}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs-web/src/main/webapp/src/partial/docs/tag.html b/docs-web/src/main/webapp/src/partial/docs/tag.html
index c8f02165..37a9e7c9 100644
--- a/docs-web/src/main/webapp/src/partial/docs/tag.html
+++ b/docs-web/src/main/webapp/src/partial/docs/tag.html
@@ -21,6 +21,15 @@
+
+
+
+ Parent: {{ tag0.name }}
+
+
diff --git a/docs-web/src/main/webapp/src/style/main.less b/docs-web/src/main/webapp/src/style/main.less
index 1ba0630a..ec55a44b 100644
--- a/docs-web/src/main/webapp/src/style/main.less
+++ b/docs-web/src/main/webapp/src/style/main.less
@@ -183,4 +183,17 @@ input[readonly].share-link {
.tab-pane {
margin-top: 20px;
+}
+
+// Tag tree
+.tag-tree-dropdown {
+ padding-left: 0;
+
+ .tag-tree {
+ li {
+ margin-left: 20px;
+ margin-top: 8px;
+ margin-bottom: 8px;
+ }
+ }
}
\ No newline at end of file
diff --git a/docs-web/src/prod/resources/config.properties b/docs-web/src/prod/resources/config.properties
index f2362d2f..87577f48 100644
--- a/docs-web/src/prod/resources/config.properties
+++ b/docs-web/src/prod/resources/config.properties
@@ -1,3 +1,3 @@
api.current_version=${project.version}
api.min_version=1.0
-db.version=1
\ No newline at end of file
+db.version=2
\ No newline at end of file
diff --git a/docs-web/src/stress/resources/config.properties b/docs-web/src/stress/resources/config.properties
index f2362d2f..87577f48 100644
--- a/docs-web/src/stress/resources/config.properties
+++ b/docs-web/src/stress/resources/config.properties
@@ -1,3 +1,3 @@
api.current_version=${project.version}
api.min_version=1.0
-db.version=1
\ No newline at end of file
+db.version=2
\ No newline at end of file
diff --git a/docs-web/src/test/java/com/sismics/docs/rest/TestTagResource.java b/docs-web/src/test/java/com/sismics/docs/rest/TestTagResource.java
index b9fa0532..3094879a 100644
--- a/docs-web/src/test/java/com/sismics/docs/rest/TestTagResource.java
+++ b/docs-web/src/test/java/com/sismics/docs/rest/TestTagResource.java
@@ -2,6 +2,7 @@ package com.sismics.docs.rest;
import javax.json.JsonArray;
import javax.json.JsonObject;
+import javax.json.JsonValue;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Form;
import javax.ws.rs.core.Response;
@@ -44,7 +45,8 @@ public class TestTagResource extends BaseJerseyTest {
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, tag1Token)
.put(Entity.form(new Form()
.param("name", "Tag4")
- .param("color", "#00ff00")), JsonObject.class);
+ .param("color", "#00ff00")
+ .param("parent", tag3Id)), JsonObject.class);
String tag4Id = json.getString("id");
Assert.assertNotNull(tag4Id);
@@ -129,6 +131,7 @@ public class TestTagResource extends BaseJerseyTest {
Assert.assertTrue(tags.size() > 0);
Assert.assertEquals("Tag4", tags.getJsonObject(1).getString("name"));
Assert.assertEquals("#00ff00", tags.getJsonObject(1).getString("color"));
+ Assert.assertEquals(tag3Id, tags.getJsonObject(1).getString("parent"));
// Update a tag
json = target().path("/tag/" + tag4Id).request()
@@ -146,6 +149,7 @@ public class TestTagResource extends BaseJerseyTest {
Assert.assertTrue(tags.size() > 0);
Assert.assertEquals("UpdatedName", tags.getJsonObject(1).getString("name"));
Assert.assertEquals("#0000ff", tags.getJsonObject(1).getString("color"));
+ Assert.assertEquals(JsonValue.NULL, tags.getJsonObject(1).get("parent"));
// Deletes a tag
target().path("/tag/" + tag4Id).request()