mirror of
https://github.com/sismics/docs.git
synced 2024-12-23 03:33:49 +01:00
commit
f01d78a9ea
@ -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
|
||||
--------
|
||||
|
@ -12,10 +12,12 @@
|
||||
<option name="SELECTED_TEST_ARTIFACT" value="_android_test_" />
|
||||
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
|
||||
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
|
||||
<option name="SOURCE_GEN_TASK_NAME" value="generateDebugSources" />
|
||||
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugAndroidTest" />
|
||||
<option name="COMPILE_JAVA_TEST_TASK_NAME" value="compileDebugAndroidTestSources" />
|
||||
<option name="TEST_SOURCE_GEN_TASK_NAME" value="generateDebugAndroidTestSources" />
|
||||
<afterSyncTasks>
|
||||
<task>generateDebugAndroidTestSources</task>
|
||||
<task>generateDebugSources</task>
|
||||
</afterSyncTasks>
|
||||
<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" />
|
||||
@ -24,7 +26,7 @@
|
||||
</configuration>
|
||||
</facet>
|
||||
</component>
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="false">
|
||||
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="false">
|
||||
<output url="file://$MODULE_DIR$/build/intermediates/classes/debug" />
|
||||
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/androidTest/debug" />
|
||||
<exclude-output />
|
||||
@ -34,13 +36,13 @@
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/generated/debug" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/debug" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/androidTest/debug" isTestSource="true" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/androidTest/debug" isTestSource="true" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/debug" isTestSource="true" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/debug" isTestSource="true" generated="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/debug" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/generated/androidTest/debug" type="java-test-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/androidTest/debug" type="java-test-resource" />
|
||||
<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/debug/assets" type="java-resource" />
|
||||
@ -75,9 +77,9 @@
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex-cache" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/appcompat-v7/22.1.1/jars" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/recyclerview-v7/22.0.0/jars" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-v4/22.1.1/jars" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/appcompat-v7/22.2.1/jars" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/recyclerview-v7/22.2.1/jars" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-v4/22.2.1/jars" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.shamanland/fab/0.0.6/jars" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/it.sephiroth.android.library.easing/android-easing/1.0.3/jars" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/it.sephiroth.android.library.imagezoom/imagezoom/1.0.5/jars" />
|
||||
@ -106,16 +108,16 @@
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Android API 22 Platform" jdkType="Android SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" exported="" name="appcompat-v7-22.1.1" level="project" />
|
||||
<orderEntry type="library" exported="" name="fab-0.0.6" level="project" />
|
||||
<orderEntry type="library" exported="" name="android-easing-1.0.3" level="project" />
|
||||
<orderEntry type="library" exported="" name="imagezoom-1.0.5" level="project" />
|
||||
<orderEntry type="library" exported="" name="support-v4-22.2.1" level="project" />
|
||||
<orderEntry type="library" exported="" name="eventbus-2.4.0" level="project" />
|
||||
<orderEntry type="library" exported="" name="recyclerview-v7-22.2.1" level="project" />
|
||||
<orderEntry type="library" exported="" name="android-query.0.26.8" level="project" />
|
||||
<orderEntry type="library" exported="" name="tokenautocomplete-1.2.1" level="project" />
|
||||
<orderEntry type="library" exported="" name="support-v4-22.1.1" level="project" />
|
||||
<orderEntry type="library" exported="" name="support-annotations-22.1.1" level="project" />
|
||||
<orderEntry type="library" exported="" name="recyclerview-v7-22.0.0" level="project" />
|
||||
<orderEntry type="library" exported="" name="support-annotations-22.2.1" level="project" />
|
||||
<orderEntry type="library" exported="" name="appcompat-v7-22.2.1" level="project" />
|
||||
<orderEntry type="library" exported="" name="android-async-http-1.4.6" level="project" />
|
||||
</component>
|
||||
</module>
|
@ -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'
|
||||
|
@ -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);
|
||||
|
@ -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) { }
|
||||
}
|
@ -113,6 +113,11 @@
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.levigo.jbig2</groupId>
|
||||
<artifactId>levigo-jbig2-imageio</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- OCR dependencies -->
|
||||
<dependency>
|
||||
<groupId>jna</groupId>
|
||||
|
@ -103,7 +103,7 @@ public class TagDao {
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<TagDto> 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<TagStatDto> 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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ package com.sismics.docs.core.dao.jpa.dto;
|
||||
|
||||
|
||||
/**
|
||||
* Tag DTO.
|
||||
* Tag stat DTO.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -1 +1 @@
|
||||
db.version=1
|
||||
db.version=2
|
@ -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';
|
@ -35,6 +35,7 @@
|
||||
<joda-time.joda-time.version>2.8.2</joda-time.joda-time.version>
|
||||
<org.hibernate.hibernate.version>4.1.0.Final</org.hibernate.hibernate.version>
|
||||
<javax.servlet.javax.servlet-api.version>3.1.0</javax.servlet.javax.servlet-api.version>
|
||||
<com.levigo.jbig2.levigo-jbig2-imageio.version>1.6.3</com.levigo.jbig2.levigo-jbig2-imageio.version>
|
||||
|
||||
<org.eclipse.jetty.jetty-server.version>9.2.13.v20150730</org.eclipse.jetty.jetty-server.version>
|
||||
<org.eclipse.jetty.jetty-webapp.version>9.2.13.v20150730</org.eclipse.jetty.jetty-webapp.version>
|
||||
@ -66,6 +67,12 @@
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
|
||||
<repository>
|
||||
<id>jbig2.googlecode</id>
|
||||
<name>JBIG2 ImageIO-Plugin repository at googlecode.com</name>
|
||||
<url>http://jbig2-imageio.googlecode.com/svn/maven-repository</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
|
||||
@ -252,11 +259,11 @@
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
|
||||
<artifactId>jersey-test-framework-provider-bundle</artifactId>
|
||||
<type>pom</type>
|
||||
<version>${org.glassfish.jersey.version}</version>
|
||||
</dependency>
|
||||
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
|
||||
<artifactId>jersey-test-framework-provider-bundle</artifactId>
|
||||
<type>pom</type>
|
||||
<version>${org.glassfish.jersey.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
|
||||
@ -295,10 +302,10 @@
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
<version>${org.hibernate.hibernate.version}</version>
|
||||
</dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
<version>${org.hibernate.hibernate.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-dbcp</groupId>
|
||||
@ -354,6 +361,13 @@
|
||||
<version>${org.bouncycastle.bcprov-jdk15on.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Used to read JBIG2 images. See https://github.com/sismics/docs/issues/38 -->
|
||||
<dependency>
|
||||
<groupId>com.levigo.jbig2</groupId>
|
||||
<artifactId>levigo-jbig2-imageio</artifactId>
|
||||
<version>${com.levigo.jbig2.levigo-jbig2-imageio.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- OCR dependencies -->
|
||||
<dependency>
|
||||
<groupId>jna</groupId>
|
||||
|
@ -1,3 +1,3 @@
|
||||
api.current_version=${project.version}
|
||||
api.min_version=1.0
|
||||
db.version=1
|
||||
db.version=2
|
@ -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()));
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
@ -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;
|
||||
});
|
||||
};
|
||||
});
|
@ -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') });
|
||||
};
|
||||
});
|
@ -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');
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
});
|
@ -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;
|
||||
});
|
||||
});
|
@ -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;
|
||||
});
|
||||
};
|
||||
});
|
@ -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;
|
||||
};
|
||||
});
|
@ -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';
|
||||
|
@ -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'
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
@ -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();
|
||||
};
|
||||
|
@ -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 });
|
||||
};
|
||||
});
|
@ -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');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -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 });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -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 });
|
||||
});
|
||||
});
|
@ -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 })
|
||||
};
|
||||
});
|
@ -43,6 +43,9 @@
|
||||
<script src="app/docs/controller/DocumentDefault.js" type="text/javascript"></script>
|
||||
<script src="app/docs/controller/DocumentEdit.js" type="text/javascript"></script>
|
||||
<script src="app/docs/controller/DocumentView.js" type="text/javascript"></script>
|
||||
<script src="app/docs/controller/DocumentViewContent.js" type="text/javascript"></script>
|
||||
<script src="app/docs/controller/DocumentViewPermissions.js" type="text/javascript"></script>
|
||||
<script src="app/docs/controller/DocumentViewActivity.js" type="text/javascript"></script>
|
||||
<script src="app/docs/controller/DocumentModalShare.js" type="text/javascript"></script>
|
||||
<script src="app/docs/controller/FileView.js" type="text/javascript"></script>
|
||||
<script src="app/docs/controller/FileModalView.js" type="text/javascript"></script>
|
||||
|
@ -5,49 +5,61 @@
|
||||
<a href="#/document/add" class="btn btn-primary"><span class="glyphicon glyphicon-plus"></span> Add a document</a>
|
||||
</p>
|
||||
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon">
|
||||
<span class="glyphicon glyphicon glyphicon-info-sign"
|
||||
tooltip-placement="bottom"
|
||||
tooltip-html-unsafe="before:2012-05<br/>
|
||||
after:2012-05<br/>
|
||||
at:2012-05<br/>
|
||||
tag:car<br/>
|
||||
full:led<br/>
|
||||
shared:yes<br/>
|
||||
lang:fra"></span>
|
||||
</span>
|
||||
<input type="search" class="form-control" placeholder="Search" ng-model="search" />
|
||||
<span class="input-group-addon">
|
||||
<span class="glyphicon glyphicon-search" ng-show="search.length == 0"></span>
|
||||
<span class="glyphicon glyphicon-remove" ng-show="search.length > 0" ng-click="search = ''"></span>
|
||||
</span>
|
||||
<div class="row">
|
||||
<div class="dropdown col-md-2 tag-tree-dropdown" dropdown>
|
||||
<button class="btn btn-block btn-default" dropdown-toggle ng-disabled="disabled">
|
||||
Tags <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu tag-tree">
|
||||
<li ng-if="getChildrenTags().length == 0">No tags</li>
|
||||
<li ng-repeat="tag in getChildrenTags()" ng-include="'tag-tree-item'"></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="col-md-10 input-group">
|
||||
<span class="input-group-addon">
|
||||
<span class="glyphicon glyphicon glyphicon-info-sign"
|
||||
tooltip-placement="bottom"
|
||||
tooltip-html-unsafe="before:2012-05<br/>
|
||||
after:2012-05<br/>
|
||||
at:2012-05<br/>
|
||||
tag:car<br/>
|
||||
full:led<br/>
|
||||
shared:yes<br/>
|
||||
lang:fra"></span>
|
||||
</span>
|
||||
<input type="search" class="form-control" placeholder="Search" ng-model="search" />
|
||||
<span class="input-group-addon">
|
||||
<span class="glyphicon glyphicon-search" ng-show="search.length == 0"></span>
|
||||
<span class="glyphicon glyphicon-remove" ng-show="search.length > 0" ng-click="search = ''"></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="row table table-striped table-hover table-documents">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="col-xs-6" ng-click="sortDocuments(1)"><span class="glyphicon glyphicon-chevron-{{ sortColumn == 1 ? (asc ? 'down' : 'up') : '' }}"></span> Title</th>
|
||||
<th class="col-xs-3" ng-click="sortDocuments(3)"><span class="glyphicon glyphicon-chevron-{{ sortColumn == 3 ? (asc ? 'down' : 'up') : '' }}"></span> Creation date</th>
|
||||
<th class="col-xs-3 hidden-xs">Tags</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="col-xs-6" ng-click="sortDocuments(1)"><span class="glyphicon glyphicon-chevron-{{ sortColumn == 1 ? (asc ? 'down' : 'up') : '' }}"></span> Title</th>
|
||||
<th class="col-xs-3" ng-click="sortDocuments(3)"><span class="glyphicon glyphicon-chevron-{{ sortColumn == 3 ? (asc ? 'down' : 'up') : '' }}"></span> Creation date</th>
|
||||
<th class="col-xs-3 hidden-xs">Tags</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-click="viewDocument(document.id)" ng-repeat="document in documents">
|
||||
<td>
|
||||
{{ document.title }} ({{ document.file_count }})
|
||||
<span class="glyphicon glyphicon-share" ng-if="document.shared" tooltip="Shared"></span>
|
||||
<a href="#/document/view/{{ document.id }}" ng-click="$event.stopPropagation()"><span class="glyphicon glyphicon-link"></span></a>
|
||||
</td>
|
||||
<td>{{ document.create_date | date: 'yyyy-MM-dd' }}</td>
|
||||
<td class="hidden-xs cell-tags">
|
||||
<div class="tags">
|
||||
<span class="label label-info" ng-repeat="tag in document.tags" ng-style="{ 'background': tag.color }">
|
||||
<span class="shorten">{{ tag.name | shorten }}</span><span class="full">{{ tag.name }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-click="viewDocument(document.id)" ng-repeat="document in documents">
|
||||
<td>
|
||||
{{ document.title }} ({{ document.file_count }})
|
||||
<span class="glyphicon glyphicon-share" ng-if="document.shared" tooltip="Shared"></span>
|
||||
<a href="#/document/view/{{ document.id }}" target="_blank" ng-click="$event.stopPropagation()"><span class="glyphicon glyphicon-link"></span></a>
|
||||
</td>
|
||||
<td>{{ document.create_date | date: 'yyyy-MM-dd' }}</td>
|
||||
<td class="hidden-xs cell-tags">
|
||||
<div class="tags">
|
||||
<span class="label label-info" ng-repeat="tag in document.tags" ng-style="{ 'background': tag.color }">
|
||||
<span class="shorten">{{ tag.name | shorten }}</span><span class="full">{{ tag.name }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@ -70,4 +82,14 @@
|
||||
<div class="col-md-8">
|
||||
<div ui-view="document"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/ng-template" id="tag-tree-item">
|
||||
<span class="btn" ng-style="{ 'background-color': tag.color }"></span>
|
||||
<span class="btn btn-link" ng-click="setSearch('tag:' + tag.name)">
|
||||
{{ tag.name }}
|
||||
</span>
|
||||
<ul class="list-unstyled">
|
||||
<li ng-repeat="tag in getChildrenTags(tag.id)" ng-include="'tag-tree-item'"></li>
|
||||
</ul>
|
||||
</script>
|
@ -0,0 +1 @@
|
||||
<audit-log logs="logs" />
|
@ -0,0 +1,37 @@
|
||||
<p ng-bind-html="document.description | newline"></p>
|
||||
|
||||
<div ng-file-drop drag-over-class="bg-success" ng-multiple="true" allow-dir="false" ng-model="dropFiles"
|
||||
accept="image/*,application/pdf,application/zip" ng-file-change="fileDropped($files, $event, $rejectedFiles)">
|
||||
<div class="row upload-zone" ui-sortable="fileSortableOptions" ng-model="files">
|
||||
<div class="col-xs-6 col-sm-4 col-md-3 col-lg-2 text-center" ng-repeat="file in files">
|
||||
<div class="thumbnail" ng-if="file.id">
|
||||
<a ng-click="openFile(file)">
|
||||
<img class="thumbnail-file" ng-src="../api/file/{{ file.id }}/data?size=thumb" tooltip="{{ file.mimetype }}" tooltip-placement="top" />
|
||||
</a>
|
||||
<div class="caption" ng-show="document.writable">
|
||||
<div class="pull-left">
|
||||
<div class="btn btn-default handle"><span class="glyphicon glyphicon-resize-horizontal"></span></div>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<button class="btn btn-danger" ng-click="deleteFile(file)"><span class="glyphicon glyphicon-trash"></span></button>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="thumbnail" ng-if="!file.id">
|
||||
<p class="text-center lead">
|
||||
{{ file.status }}
|
||||
</p>
|
||||
<div class="caption">
|
||||
<progressbar value="file.progress" class="progress-info active"></progressbar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-center well-lg" ng-if="files.length == 0">
|
||||
<span class="glyphicon glyphicon-move"></span>
|
||||
Drag & drop files here to upload
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
@ -38,119 +38,24 @@
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<tabset>
|
||||
<tab>
|
||||
<tab-heading class="pointer">
|
||||
<ul class="nav nav-tabs">
|
||||
<li ng-class="{ active: $state.current.name == 'document.view.content' }">
|
||||
<a href="#/document/view/{{ document.id }}/content">
|
||||
<span class="glyphicon glyphicon-file"></span> Content
|
||||
</tab-heading>
|
||||
|
||||
<p ng-bind-html="document.description | newline"></p>
|
||||
|
||||
<div ng-file-drop drag-over-class="bg-success" ng-multiple="true" allow-dir="false" ng-model="dropFiles"
|
||||
accept="image/*,application/pdf,application/zip" ng-file-change="fileDropped($files, $event, $rejectedFiles)">
|
||||
<div class="row upload-zone" ui-sortable="fileSortableOptions" ng-model="files">
|
||||
<div class="col-xs-6 col-sm-4 col-md-3 col-lg-2 text-center" ng-repeat="file in files">
|
||||
<div class="thumbnail" ng-if="file.id">
|
||||
<a ng-click="openFile(file)">
|
||||
<img class="thumbnail-file" ng-src="../api/file/{{ file.id }}/data?size=thumb" tooltip="{{ file.mimetype }}" tooltip-placement="top" />
|
||||
</a>
|
||||
<div class="caption" ng-show="document.writable">
|
||||
<div class="pull-left">
|
||||
<div class="btn btn-default handle"><span class="glyphicon glyphicon-resize-horizontal"></span></div>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<button class="btn btn-danger" ng-click="deleteFile(file)"><span class="glyphicon glyphicon-trash"></span></button>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="thumbnail" ng-if="!file.id">
|
||||
<p class="text-center lead">
|
||||
{{ file.status }}
|
||||
</p>
|
||||
<div class="caption">
|
||||
<progressbar value="file.progress" class="progress-info active"></progressbar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-center well-lg" ng-if="files.length == 0">
|
||||
<span class="glyphicon glyphicon-move"></span>
|
||||
Drag & drop files here to upload
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</tab>
|
||||
|
||||
<tab>
|
||||
<tab-heading class="pointer">
|
||||
</a>
|
||||
</li>
|
||||
<li ng-class="{ active: $state.current.name == 'document.view.permissions' }">
|
||||
<a href="#/document/view/{{ document.id }}/permissions">
|
||||
<span class="glyphicon glyphicon-user"></span> Permissions
|
||||
</tab-heading>
|
||||
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th style="width: 40%">For</th>
|
||||
<th style="width: 40%">Permission</th>
|
||||
</tr>
|
||||
|
||||
<tr ng-repeat="(id, acl) in acls">
|
||||
<td><em>{{ acl[0].type == 'SHARE' ? 'Shared' : 'User' }}</em> {{ acl[0].name }}</td>
|
||||
<td>
|
||||
<span class="label label-default" style="margin-right: 6px;" ng-repeat="a in acl | orderBy: 'perm'">
|
||||
{{ a.perm }}
|
||||
<span ng-show="(document.creator != a.name && a.type == 'USER' || a.type != 'USER') && document.writable"
|
||||
class="glyphicon glyphicon-remove pointer"
|
||||
ng-click="deleteAcl(a)"></span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div ng-show="document.writable">
|
||||
<h4>Add a permission</h4>
|
||||
|
||||
<form name="aclForm" class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class=" col-sm-2 control-label" for="inputTarget">User</label>
|
||||
<div class="col-sm-3">
|
||||
<input required ng-maxlength="50" class="form-control" type="text" id="inputTarget"
|
||||
placeholder="Type a username" name="username" ng-model="acl.username" autocomplete="off"
|
||||
typeahead="username for username in getTargetAclTypeahead($viewValue) | filter: $viewValue"
|
||||
typeahead-wait-ms="200" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class=" col-sm-2 control-label" for="inputPermission">Permission</label>
|
||||
<div class="col-sm-3">
|
||||
<select class="form-control" ng-model="acl.perm" id="inputPermission">
|
||||
<option value="READ">Can read</option>
|
||||
<option value="READWRITE">Can edit</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
<button type="submit" class="btn btn-primary" ng-disabled="!aclForm.$valid" ng-click="addAcl()">
|
||||
<span class="glyphicon glyphicon-plus"></span>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</tab>
|
||||
|
||||
<tab>
|
||||
<tab-heading class="pointer">
|
||||
</a>
|
||||
</li>
|
||||
<li ng-class="{ active: $state.current.name == 'document.view.activity' }">
|
||||
<a href="#/document/view/{{ document.id }}/activity">
|
||||
<span class="glyphicon glyphicon-tasks"></span> Activity
|
||||
</tab-heading>
|
||||
|
||||
<audit-log logs="logs" />
|
||||
</tab>
|
||||
</tabset>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div ui-view="tab"></div>
|
||||
<div ui-view="file"></div>
|
||||
</div>
|
@ -0,0 +1,53 @@
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th style="width: 40%">For</th>
|
||||
<th style="width: 40%">Permission</th>
|
||||
</tr>
|
||||
|
||||
<tr ng-repeat="(id, acl) in acls">
|
||||
<td><em>{{ acl[0].type == 'SHARE' ? 'Shared' : 'User' }}</em> {{ acl[0].name }}</td>
|
||||
<td>
|
||||
<span class="label label-default" style="margin-right: 6px;" ng-repeat="a in acl | orderBy: 'perm'">
|
||||
{{ a.perm }}
|
||||
<span ng-show="(document.creator != a.name && a.type == 'USER' || a.type != 'USER') && document.writable"
|
||||
class="glyphicon glyphicon-remove pointer"
|
||||
ng-click="deleteAcl(a)"></span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div ng-show="document.writable">
|
||||
<h4>Add a permission</h4>
|
||||
|
||||
<form name="aclForm" class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class=" col-sm-2 control-label" for="inputTarget">User</label>
|
||||
<div class="col-sm-3">
|
||||
<input required ng-maxlength="50" class="form-control" type="text" id="inputTarget"
|
||||
placeholder="Type a username" name="username" ng-model="acl.username" autocomplete="off"
|
||||
typeahead="username for username in getTargetAclTypeahead($viewValue) | filter: $viewValue"
|
||||
typeahead-wait-ms="200" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class=" col-sm-2 control-label" for="inputPermission">Permission</label>
|
||||
<div class="col-sm-3">
|
||||
<select class="form-control" ng-model="acl.perm" id="inputPermission">
|
||||
<option value="READ">Can read</option>
|
||||
<option value="READWRITE">Can edit</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
<button type="submit" class="btn btn-primary" ng-disabled="!aclForm.$valid" ng-click="addAcl()">
|
||||
<span class="glyphicon glyphicon-plus"></span>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
@ -21,6 +21,15 @@
|
||||
<tbody>
|
||||
<tr ng-repeat="tag in tags | filter:search">
|
||||
<td><inline-edit value="tag.name" on-edit="updateTag(tag)" /></td>
|
||||
<td class="col-xs-4">
|
||||
<select class="form-control" ng-model="tag.parent" ng-change="updateTag(tag)">
|
||||
<option value="" ng-selected="!tag.parent"></option>
|
||||
<option ng-repeat="tag0 in tags"
|
||||
ng-if="tag0.id != tag.id"
|
||||
ng-selected="tag.parent == tag0.id"
|
||||
value="{{ tag0.id }}">Parent: {{ tag0.name }}</option>
|
||||
</select>
|
||||
</td>
|
||||
<td class="col-xs-1"><span colorpicker class="btn" on-hide="updateTag(tag)" data-color="" ng-model="tag.color" ng-style="{ 'background': tag.color }"> </span></td>
|
||||
<td class="col-xs-1"><button class="btn btn-danger pull-right" ng-click="deleteTag(tag)"><span class="glyphicon glyphicon-trash"></span></button></td>
|
||||
</tr>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +1,3 @@
|
||||
api.current_version=${project.version}
|
||||
api.min_version=1.0
|
||||
db.version=1
|
||||
db.version=2
|
@ -1,3 +1,3 @@
|
||||
api.current_version=${project.version}
|
||||
api.min_version=1.0
|
||||
db.version=1
|
||||
db.version=2
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user