mirror of
https://github.com/sismics/docs.git
synced 2025-12-14 10:16:21 +00:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6367a1fd15 | ||
|
|
2c5ff64d42 | ||
|
|
e614cb41d8 | ||
|
|
82737e2280 | ||
|
|
3b5c27096b | ||
|
|
8a85830bd3 | ||
|
|
19ac90688e | ||
|
|
5f4a6bc462 | ||
|
|
4c7f3166d4 | ||
|
|
4233f4dd88 | ||
|
|
bd09312418 | ||
|
|
11ab07b238 | ||
|
|
d2e2f089fb | ||
|
|
d619f98de7 | ||
|
|
89228a52dc | ||
|
|
90a49efa4a | ||
|
|
a7423caeb1 | ||
|
|
6f31a2c228 | ||
|
|
fc98b0882f | ||
|
|
dff05967ea | ||
|
|
ec836a2f9d | ||
|
|
737c85cf00 | ||
|
|
ff7b07f464 | ||
|
|
19422b5afa | ||
|
|
6b93e413b6 | ||
|
|
ab72736bcc | ||
|
|
38939e5d05 | ||
|
|
1a90a0e0ad |
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [jendib]
|
||||
@@ -4,7 +4,7 @@ language: java
|
||||
before_install:
|
||||
- sudo add-apt-repository -y ppa:mc3man/trusty-media
|
||||
- sudo apt-get -qq update
|
||||
- sudo apt-get -y -q install ffmpeg mediainfo tesseract-ocr tesseract-ocr-fra tesseract-ocr-ita tesseract-ocr-kor tesseract-ocr-rus tesseract-ocr-ukr tesseract-ocr-spa tesseract-ocr-ara tesseract-ocr-hin tesseract-ocr-deu tesseract-ocr-pol tesseract-ocr-jpn tesseract-ocr-por tesseract-ocr-tha tesseract-ocr-jpn tesseract-ocr-chi-sim tesseract-ocr-chi-tra tesseract-ocr-nld tesseract-ocr-tur tesseract-ocr-heb
|
||||
- sudo apt-get -y -q install ffmpeg mediainfo tesseract-ocr tesseract-ocr-fra tesseract-ocr-ita tesseract-ocr-kor tesseract-ocr-rus tesseract-ocr-ukr tesseract-ocr-spa tesseract-ocr-ara tesseract-ocr-hin tesseract-ocr-deu tesseract-ocr-pol tesseract-ocr-jpn tesseract-ocr-por tesseract-ocr-tha tesseract-ocr-jpn tesseract-ocr-chi-sim tesseract-ocr-chi-tra tesseract-ocr-nld tesseract-ocr-tur tesseract-ocr-heb tesseract-ocr-hun
|
||||
- sudo apt-get -y -q install haveged && sudo service haveged start
|
||||
after_success:
|
||||
- |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
FROM sismics/ubuntu-jetty:9.4.12
|
||||
MAINTAINER b.gamard@sismics.com
|
||||
|
||||
RUN apt-get update && apt-get -y -q install ffmpeg mediainfo tesseract-ocr tesseract-ocr-fra tesseract-ocr-ita tesseract-ocr-kor tesseract-ocr-rus tesseract-ocr-ukr tesseract-ocr-spa tesseract-ocr-ara tesseract-ocr-hin tesseract-ocr-deu tesseract-ocr-pol tesseract-ocr-jpn tesseract-ocr-por tesseract-ocr-tha tesseract-ocr-jpn tesseract-ocr-chi-sim tesseract-ocr-chi-tra tesseract-ocr-nld tesseract-ocr-tur tesseract-ocr-heb && \
|
||||
RUN apt-get update && apt-get -y -q install ffmpeg mediainfo tesseract-ocr tesseract-ocr-fra tesseract-ocr-ita tesseract-ocr-kor tesseract-ocr-rus tesseract-ocr-ukr tesseract-ocr-spa tesseract-ocr-ara tesseract-ocr-hin tesseract-ocr-deu tesseract-ocr-pol tesseract-ocr-jpn tesseract-ocr-por tesseract-ocr-tha tesseract-ocr-jpn tesseract-ocr-chi-sim tesseract-ocr-chi-tra tesseract-ocr-nld tesseract-ocr-tur tesseract-ocr-heb tesseract-ocr-hun && \
|
||||
apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Remove the embedded javax.mail jar from Jetty
|
||||
|
||||
17
README.md
17
README.md
@@ -8,11 +8,9 @@
|
||||
|
||||
Teedy is an open source, lightweight document management system for individuals and businesses.
|
||||
|
||||
**Discuss it on [Product Hunt](https://www.producthunt.com/posts/sismics-docs) 🦄**
|
||||
|
||||
<hr />
|
||||
<h2 align="center">
|
||||
✨ Sismics Docs is now called Teedy! You can still find our cloud and support offer on <a href="https://teedy.io">teedy.io</a> ✨
|
||||
✨ <a href="https://github.com/users/jendib/sponsorship">Sponsor this project if you use and appreciate it!</a> ✨
|
||||
</h2>
|
||||
<hr />
|
||||
|
||||
@@ -86,7 +84,7 @@ The latest release is downloadable here: <https://github.com/sismics/docs/releas
|
||||
How to build Teedy from the sources
|
||||
----------------------------------
|
||||
|
||||
Prerequisites: JDK 8 with JCE, Maven 3, Tesseract 3 or 4
|
||||
Prerequisites: JDK 8 with JCE, Maven 3, NPM, Grunt, Tesseract 3 or 4
|
||||
|
||||
Teedy is organized in several Maven modules:
|
||||
|
||||
@@ -124,17 +122,6 @@ All contributions are more than welcomed. Contributions may close an issue, fix
|
||||
|
||||
The `master` branch is the default and base branch for the project. It is used for development and all Pull Requests should go there.
|
||||
|
||||
|
||||
Community
|
||||
---------
|
||||
|
||||
Get updates on Teedy's development and chat with the project maintainers:
|
||||
|
||||
- Follow [@teedyio on Twitter](https://twitter.com/teedyio)
|
||||
- Read and subscribe to [The Official Teedy Blog](https://blog.teedy.io/)
|
||||
- Check the [Official Website](https://teedy.io)
|
||||
- Join us [on Facebook](https://www.facebook.com/teedyio)
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.sismics.docs</groupId>
|
||||
<artifactId>docs-parent</artifactId>
|
||||
<version>1.7</version>
|
||||
<version>1.8</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -190,6 +190,25 @@
|
||||
<artifactId>postgresql</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- JDK 11 JAXB dependencies -->
|
||||
<dependency>
|
||||
<groupId>javax.xml.bind</groupId>
|
||||
<artifactId>jaxb-api</artifactId>
|
||||
<version>2.3.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.sun.xml.bind</groupId>
|
||||
<artifactId>jaxb-core</artifactId>
|
||||
<version>2.3.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.sun.xml.bind</groupId>
|
||||
<artifactId>jaxb-impl</artifactId>
|
||||
<version>2.3.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Test dependencies -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
|
||||
@@ -38,7 +38,7 @@ public class Constants {
|
||||
/**
|
||||
* Supported document languages.
|
||||
*/
|
||||
public static final List<String> SUPPORTED_LANGUAGES = Lists.newArrayList("eng", "fra", "ita", "deu", "spa", "por", "pol", "rus", "ukr", "ara", "hin", "chi_sim", "chi_tra", "jpn", "tha", "kor", "nld", "tur", "heb");
|
||||
public static final List<String> SUPPORTED_LANGUAGES = Lists.newArrayList("eng", "fra", "ita", "deu", "spa", "por", "pol", "rus", "ukr", "ara", "hin", "chi_sim", "chi_tra", "jpn", "tha", "kor", "nld", "tur", "heb", "hun");
|
||||
|
||||
/**
|
||||
* Base URL environment variable.
|
||||
|
||||
@@ -56,7 +56,7 @@ public class ContributorDao {
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<ContributorDto> getByDocumentId(String documentId) {
|
||||
EntityManager em = ThreadLocalContext.get().getEntityManager();
|
||||
StringBuilder sb = new StringBuilder("select u.USE_USERNAME_C, u.USE_EMAIL_C from T_CONTRIBUTOR c ");
|
||||
StringBuilder sb = new StringBuilder("select distinct u.USE_USERNAME_C, u.USE_EMAIL_C from T_CONTRIBUTOR c ");
|
||||
sb.append(" join T_USER u on u.USE_ID_C = c.CTR_IDUSER_C ");
|
||||
sb.append(" where c.CTR_IDDOC_C = :documentId ");
|
||||
Query q = em.createNativeQuery(sb.toString());
|
||||
|
||||
@@ -232,7 +232,7 @@ public class DocumentDao {
|
||||
*/
|
||||
public void updateFileId(Document document) {
|
||||
EntityManager em = ThreadLocalContext.get().getEntityManager();
|
||||
Query query = em.createNativeQuery("update T_DOCUMENT d set d.DOC_IDFILE_C = :fileId, d.DOC_UPDATEDATE_D = :updateDate where d.DOC_ID_C = :id");
|
||||
Query query = em.createNativeQuery("update T_DOCUMENT d set DOC_IDFILE_C = :fileId, DOC_UPDATEDATE_D = :updateDate where d.DOC_ID_C = :id");
|
||||
query.setParameter("updateDate", new Date());
|
||||
query.setParameter("fileId", document.getFileId());
|
||||
query.setParameter("id", document.getId());
|
||||
|
||||
@@ -77,6 +77,11 @@ public class DocumentCriteria {
|
||||
*/
|
||||
private Boolean activeRoute;
|
||||
|
||||
/**
|
||||
* MIME type of a file.
|
||||
*/
|
||||
private String mimeType;
|
||||
|
||||
public List<String> getTargetIdList() {
|
||||
return targetIdList;
|
||||
}
|
||||
@@ -181,4 +186,12 @@ public class DocumentCriteria {
|
||||
public void setActiveRoute(Boolean activeRoute) {
|
||||
this.activeRoute = activeRoute;
|
||||
}
|
||||
|
||||
public String getMimeType() {
|
||||
return mimeType;
|
||||
}
|
||||
|
||||
public void setMimeType(String mimeType) {
|
||||
this.mimeType = mimeType;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,9 +101,9 @@ public class FileProcessingAsyncListener {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the user from the database
|
||||
// Get the creating user from the database for its private key
|
||||
UserDao userDao = new UserDao();
|
||||
User user = userDao.getById(event.getUserId());
|
||||
User user = userDao.getById(file.getUserId());
|
||||
if (user == null) {
|
||||
// The user has been deleted meanwhile
|
||||
FileUtil.endProcessingFile(file.getId());
|
||||
|
||||
@@ -85,7 +85,7 @@ public class FileService extends AbstractScheduledService {
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
class TemporaryPathReference extends PhantomReference<Path> {
|
||||
static class TemporaryPathReference extends PhantomReference<Path> {
|
||||
String path;
|
||||
TemporaryPathReference(Path referent, ReferenceQueue<? super Path> q) {
|
||||
super(referent, q);
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.sismics.util.mime.MimeType;
|
||||
import org.apache.pdfbox.io.MemoryUsageSetting;
|
||||
import org.apache.pdfbox.multipdf.PDFMergerUtility;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.rendering.ImageType;
|
||||
import org.apache.pdfbox.rendering.PDFRenderer;
|
||||
import org.apache.pdfbox.text.PDFTextStripper;
|
||||
import org.slf4j.Logger;
|
||||
@@ -60,7 +61,7 @@ public class PdfFormatHandler implements FormatHandler {
|
||||
for (int pageIndex = 0; pageIndex < pdfDocument.getNumberOfPages(); pageIndex++) {
|
||||
log.info("OCR page " + (pageIndex + 1) + "/" + pdfDocument.getNumberOfPages() + " of PDF file containing only images");
|
||||
sb.append(" ");
|
||||
sb.append(FileUtil.ocrFile(language, renderer.renderImage(pageIndex)));
|
||||
sb.append(FileUtil.ocrFile(language, renderer.renderImageWithDPI(pageIndex, 300, ImageType.GRAY)));
|
||||
}
|
||||
return sb.toString();
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -46,7 +46,6 @@ import org.slf4j.LoggerFactory;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.*;
|
||||
|
||||
@@ -252,7 +251,7 @@ public class LuceneIndexingHandler implements IndexingHandler {
|
||||
" s.SHA_DELETEDATE_D IS NULL group by ac.ACL_SOURCEID_C) s on s.ACL_SOURCEID_C = d.DOC_ID_C " +
|
||||
" left join (SELECT count(f.FIL_ID_C) count, f.FIL_IDDOC_C " +
|
||||
" FROM T_FILE f " +
|
||||
" WHERE f.FIL_DELETEDATE_D IS NULL group by f.FIL_IDDOC_C) f on f.FIL_IDDOC_C = d.DOC_ID_C ");
|
||||
" WHERE f.FIL_DELETEDATE_D is null group by f.FIL_IDDOC_C) f on f.FIL_IDDOC_C = d.DOC_ID_C ");
|
||||
sb.append(" left join (select rs.*, rs3.idDocument " +
|
||||
"from T_ROUTE_STEP rs " +
|
||||
"join (select r.RTE_IDDOCUMENT_C idDocument, rs.RTP_IDROUTE_C idRoute, min(rs.RTP_ORDER_N) minOrder from T_ROUTE_STEP rs join T_ROUTE r on r.RTE_ID_C = rs.RTP_IDROUTE_C and r.RTE_DELETEDATE_D is null where rs.RTP_DELETEDATE_D is null and rs.RTP_ENDDATE_D is null group by rs.RTP_IDROUTE_C, r.RTE_IDDOCUMENT_C) rs3 on rs.RTP_IDROUTE_C = rs3.idRoute and rs.RTP_ORDER_N = rs3.minOrder " +
|
||||
@@ -325,6 +324,11 @@ public class LuceneIndexingHandler implements IndexingHandler {
|
||||
if (criteria.getShared() != null && criteria.getShared()) {
|
||||
criteriaList.add("s.count > 0");
|
||||
}
|
||||
if (criteria.getMimeType() != null) {
|
||||
sb.append("left join T_FILE f0 on f0.FIL_IDDOC_C = d.DOC_ID_C and f0.FIL_MIMETYPE_C = :mimeType and f0.FIL_DELETEDATE_D is null");
|
||||
parameterMap.put("mimeType", criteria.getMimeType());
|
||||
criteriaList.add("f0.FIL_ID_C is not null");
|
||||
}
|
||||
if (criteria.getLanguage() != null) {
|
||||
criteriaList.add("d.DOC_LANGUAGE_C = :language");
|
||||
parameterMap.put("language", criteria.getLanguage());
|
||||
@@ -390,7 +394,7 @@ public class LuceneIndexingHandler implements IndexingHandler {
|
||||
LuceneDictionary dictionary = new LuceneDictionary(directoryReader, "title");
|
||||
suggester.build(dictionary);
|
||||
int lastIndex = search.lastIndexOf(' ');
|
||||
String suggestQuery = search.substring(lastIndex < 0 ? 0 : lastIndex);
|
||||
String suggestQuery = search.substring(Math.max(lastIndex, 0));
|
||||
List<Lookup.LookupResult> lookupResultList = suggester.lookup(suggestQuery, false, 10);
|
||||
for (Lookup.LookupResult lookupResult : lookupResultList) {
|
||||
suggestionList.add(lookupResult.key.toString());
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.sismics.util.format;
|
||||
|
||||
import com.sismics.docs.core.util.format.PdfFormatHandler;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
|
||||
/**
|
||||
* Test of {@link PdfFormatHandler}
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class TestPdfFormatHandler {
|
||||
/**
|
||||
* Test related to https://github.com/sismics/docs/issues/373.
|
||||
*/
|
||||
@Test
|
||||
public void testIssue373() throws Exception {
|
||||
PdfFormatHandler formatHandler = new PdfFormatHandler();
|
||||
String content = formatHandler.extractContent("deu", Paths.get(ClassLoader.getSystemResource("file/issue373.pdf").toURI()));
|
||||
Assert.assertTrue(content.contains("Aufrechterhaltung"));
|
||||
Assert.assertTrue(content.contains("Außentemperatur"));
|
||||
Assert.assertTrue(content.contains("Grundumsatzmessungen"));
|
||||
Assert.assertTrue(content.contains("ermitteln"));
|
||||
}
|
||||
}
|
||||
BIN
docs-core/src/test/resources/file/issue373.pdf
Normal file
BIN
docs-core/src/test/resources/file/issue373.pdf
Normal file
Binary file not shown.
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.sismics.docs</groupId>
|
||||
<artifactId>docs-parent</artifactId>
|
||||
<version>1.7</version>
|
||||
<version>1.8</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.sismics.docs</groupId>
|
||||
<artifactId>docs-parent</artifactId>
|
||||
<version>1.7</version>
|
||||
<version>1.8</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -69,6 +69,11 @@
|
||||
<artifactId>joda-time</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>jul-to-slf4j</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Test dependencies -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
|
||||
@@ -112,6 +112,17 @@ public class ValidationUtil {
|
||||
ValidationUtil.validateLength(s, name, 7, 7, nullable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a tag name.
|
||||
*
|
||||
* @param name Name of the tag
|
||||
*/
|
||||
public static void validateTagName(String name) throws ClientException {
|
||||
if (name.contains(" ") || name.contains(":")) {
|
||||
throw new ClientException("IllegalTagName", "Spaces and colons are not allowed in tag name");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the provided string matches an URL with HTTP or HTTPS scheme.
|
||||
*
|
||||
|
||||
@@ -11,6 +11,7 @@ import org.apache.log4j.PatternLayout;
|
||||
import org.apache.log4j.RollingFileAppender;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.slf4j.bridge.SLF4JBridgeHandler;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.EntityTransaction;
|
||||
@@ -57,6 +58,8 @@ public class RequestContextFilter implements Filter {
|
||||
fileAppender.setMaxBackupIndex(5);
|
||||
fileAppender.activateOptions();
|
||||
org.apache.log4j.Logger.getRootLogger().addAppender(fileAppender);
|
||||
SLF4JBridgeHandler.removeHandlersForRootLogger();
|
||||
SLF4JBridgeHandler.install();
|
||||
|
||||
// Initialize the application context
|
||||
TransactionUtil.handle(AppContext::getInstance);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.sismics.docs</groupId>
|
||||
<artifactId>docs-parent</artifactId>
|
||||
<version>1.7</version>
|
||||
<version>1.8</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -26,25 +26,6 @@
|
||||
<artifactId>docs-web-common</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- JDK 11 JAXB dependencies -->
|
||||
<dependency>
|
||||
<groupId>javax.xml.bind</groupId>
|
||||
<artifactId>jaxb-api</artifactId>
|
||||
<version>2.3.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.sun.xml.bind</groupId>
|
||||
<artifactId>jaxb-core</artifactId>
|
||||
<version>2.3.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.sun.xml.bind</groupId>
|
||||
<artifactId>jaxb-impl</artifactId>
|
||||
<version>2.3.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Dependencies to Jersey -->
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey.containers</groupId>
|
||||
|
||||
@@ -6,3 +6,5 @@ log4j.appender.MEMORY=com.sismics.util.log4j.MemoryAppender
|
||||
log4j.appender.MEMORY.size=1000
|
||||
|
||||
log4j.logger.com.sismics=DEBUG
|
||||
log4j.logger.org.apache.pdfbox=ERROR
|
||||
log4j.logger.org.glassfish.jersey.servlet.WebComponent=ERROR
|
||||
@@ -569,6 +569,10 @@ public class DocumentResource extends BaseResource {
|
||||
documentCriteria.setLanguage(UUID.randomUUID().toString());
|
||||
}
|
||||
break;
|
||||
case "mime":
|
||||
// New mime type criteria
|
||||
documentCriteria.setMimeType(params[1]);
|
||||
break;
|
||||
case "by":
|
||||
// New creator criteria
|
||||
User user = userDao.getActiveByUsername(params[1]);
|
||||
|
||||
@@ -608,7 +608,7 @@ public class FileResource extends BaseResource {
|
||||
if (size != null) {
|
||||
if (size.equals("content")) {
|
||||
return Response.ok(Strings.nullToEmpty(file.getContent()))
|
||||
.header(HttpHeaders.CONTENT_TYPE, "text/plain")
|
||||
.header(HttpHeaders.CONTENT_TYPE, "text/plain; charset=utf-8")
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -155,7 +155,7 @@ public class TagResource extends BaseResource {
|
||||
* @apiSuccess {String} id Tag ID
|
||||
* @apiError (client) ForbiddenError Access denied
|
||||
* @apiError (client) ValidationError Validation error
|
||||
* @apiError (client) SpacesNotAllowed Spaces are not allowed in tag name
|
||||
* @apiError (client) IllegalTagName Spaces and colons are not allowed in tag name
|
||||
* @apiError (client) ParentNotFound Parent not found
|
||||
* @apiPermission user
|
||||
* @apiVersion 1.5.0
|
||||
@@ -177,11 +177,7 @@ public class TagResource extends BaseResource {
|
||||
// Validate input data
|
||||
name = ValidationUtil.validateLength(name, "name", 1, 36, false);
|
||||
ValidationUtil.validateHexColor(color, "color", true);
|
||||
|
||||
// Don't allow spaces
|
||||
if (name.contains(" ")) {
|
||||
throw new ClientException("SpacesNotAllowed", "Spaces are not allowed in tag name");
|
||||
}
|
||||
ValidationUtil.validateTagName(name);
|
||||
|
||||
// Check the parent
|
||||
if (StringUtils.isEmpty(parentId)) {
|
||||
@@ -237,7 +233,7 @@ public class TagResource extends BaseResource {
|
||||
* @apiSuccess {String} id Tag ID
|
||||
* @apiError (client) ForbiddenError Access denied
|
||||
* @apiError (client) ValidationError Validation error
|
||||
* @apiError (client) SpacesNotAllowed Spaces are not allowed in tag name
|
||||
* @apiError (client) IllegalTagName Spaces and colons are not allowed in tag name
|
||||
* @apiError (client) ParentNotFound Parent not found
|
||||
* @apiError (client) CircularReference Circular reference in parent tag
|
||||
* @apiError (client) NotFound Tag not found
|
||||
@@ -263,11 +259,7 @@ public class TagResource extends BaseResource {
|
||||
// Validate input data
|
||||
name = ValidationUtil.validateLength(name, "name", 1, 36, true);
|
||||
ValidationUtil.validateHexColor(color, "color", true);
|
||||
|
||||
// Don't allow spaces
|
||||
if (name.contains(" ")) {
|
||||
throw new ClientException("SpacesNotAllowed", "Spaces are not allowed in tag name");
|
||||
}
|
||||
ValidationUtil.validateTagName(name);
|
||||
|
||||
// Check permission
|
||||
AclDao aclDao = new AclDao();
|
||||
|
||||
735
docs-web/src/main/webapp/package-lock.json
generated
735
docs-web/src/main/webapp/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -436,6 +436,9 @@ angular.module('docs',
|
||||
} else {
|
||||
// Or else determine the language based on the user's browser
|
||||
$translateProvider.determinePreferredLanguage();
|
||||
if (!$translateProvider.use()) {
|
||||
$translateProvider.use('en');
|
||||
}
|
||||
}
|
||||
|
||||
// Configuring Timago
|
||||
@@ -520,7 +523,8 @@ angular.module('docs',
|
||||
{ key: 'kor', label: '한국어' },
|
||||
{ key: 'nld', label: 'Nederlands' },
|
||||
{ key: 'tur', label: 'Türkçe' },
|
||||
{ key: 'heb', label: 'עברית' }
|
||||
{ key: 'heb', label: 'עברית' },
|
||||
{ key: 'hun', label: 'Magyar' }
|
||||
];
|
||||
})
|
||||
/**
|
||||
|
||||
@@ -13,7 +13,6 @@ angular.module('docs').controller('DocumentDefault', function ($scope, $rootScop
|
||||
$scope.loadFiles = function () {
|
||||
Restangular.one('file/list').get().then(function (data) {
|
||||
$scope.files = data.files;
|
||||
// TODO Keep currently uploading files
|
||||
});
|
||||
};
|
||||
$scope.loadFiles();
|
||||
@@ -121,7 +120,7 @@ angular.module('docs').controller('DocumentDefault', function ($scope, $rootScop
|
||||
}
|
||||
|
||||
Restangular.withConfig(function (RestangularConfigurer) {
|
||||
RestangularConfigurer.setBaseUrl('https://api.sismicsdocs.com');
|
||||
RestangularConfigurer.setBaseUrl('https://api.teedy.io');
|
||||
}).one('api').post('feedback', {
|
||||
content: content
|
||||
}).then(function () {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
angular.module('docs').controller('DocumentViewContent', function ($scope, $rootScope, $stateParams, Restangular, $dialog, $state, Upload, $translate, $uibModal) {
|
||||
$scope.displayMode = _.isUndefined(localStorage.fileDisplayMode) ? 'grid' : localStorage.fileDisplayMode;
|
||||
$scope.openedFile = undefined;
|
||||
|
||||
/**
|
||||
* Watch for display mode change.
|
||||
@@ -45,7 +46,6 @@ angular.module('docs').controller('DocumentViewContent', function ($scope, $root
|
||||
$scope.loadFiles = function () {
|
||||
Restangular.one('file/list').get({ id: $stateParams.id }).then(function (data) {
|
||||
$scope.files = data.files;
|
||||
// TODO Keep currently uploading files
|
||||
});
|
||||
};
|
||||
$scope.loadFiles();
|
||||
@@ -55,7 +55,8 @@ angular.module('docs').controller('DocumentViewContent', function ($scope, $root
|
||||
*/
|
||||
$scope.openFile = function (file, $event) {
|
||||
if ($($event.target).parents('.currently-dragging').length === 0) {
|
||||
$state.go('document.view.content.file', {id: $stateParams.id, fileId: file.id})
|
||||
$scope.openedFile = file;
|
||||
$state.go('document.view.content.file', { id: $stateParams.id, fileId: file.id });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Add space between element directive.
|
||||
*/
|
||||
angular.module('docs').directive('addSpaceBetween', function () {
|
||||
return function (scope, element) {
|
||||
if(!scope.$last) {
|
||||
element.after(' ');
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Invert text color for more legibility directive.
|
||||
*/
|
||||
angular.module('docs').directive('invertTextColor', function () {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function(scope, element, attrs) {
|
||||
attrs.$observe('invertTextColor', function(hex) {
|
||||
if (!hex || hex.length !== 7) {
|
||||
return;
|
||||
}
|
||||
|
||||
hex = hex.slice(1);
|
||||
var r = parseInt(hex.slice(0, 2), 16),
|
||||
g = parseInt(hex.slice(2, 4), 16),
|
||||
b = parseInt(hex.slice(4, 6), 16);
|
||||
element.css('color', (r * 0.299 + g * 0.587 + b * 0.114) > 186
|
||||
? '#000000'
|
||||
: '#FFFFFF');
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -9,6 +9,7 @@ angular.module('docs').directive('selectRelation', function() {
|
||||
templateUrl: 'partial/docs/directive.selectrelation.html',
|
||||
replace: true,
|
||||
scope: {
|
||||
id: '=',
|
||||
relations: '=',
|
||||
ref: '@',
|
||||
ngDisabled: '='
|
||||
@@ -18,21 +19,12 @@ angular.module('docs').directive('selectRelation', function() {
|
||||
* Add a relation.
|
||||
*/
|
||||
$scope.addRelation = function($item) {
|
||||
// Does the new relation is already in the model
|
||||
var duplicate = _.find($scope.relations, function(relation) {
|
||||
if ($item.id === relation.id) {
|
||||
return relation;
|
||||
}
|
||||
});
|
||||
|
||||
// Add the new relation
|
||||
if (!duplicate) {
|
||||
$scope.relations.push({
|
||||
id: $item.id,
|
||||
title: $item.title,
|
||||
source: true
|
||||
});
|
||||
}
|
||||
$scope.relations.push({
|
||||
id: $item.id,
|
||||
title: $item.title,
|
||||
source: true
|
||||
});
|
||||
$scope.input = '';
|
||||
};
|
||||
|
||||
@@ -42,11 +34,11 @@ angular.module('docs').directive('selectRelation', function() {
|
||||
$scope.deleteRelation = function(deleteRelation) {
|
||||
$scope.relations = _.reject($scope.relations, function(relation) {
|
||||
return relation.id === deleteRelation.id;
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a promise for typeahead title.
|
||||
* Returns a promise for typeahead document.
|
||||
*/
|
||||
$scope.getDocumentTypeahead = function($viewValue) {
|
||||
var deferred = $q.defer();
|
||||
@@ -57,8 +49,16 @@ angular.module('docs').directive('selectRelation', function() {
|
||||
asc: true,
|
||||
search: $viewValue
|
||||
}).then(function(data) {
|
||||
deferred.resolve(data.documents);
|
||||
});
|
||||
deferred.resolve(_.reject(data.documents, function(document) {
|
||||
var duplicate = _.find($scope.relations, function(relation) {
|
||||
if (document.id === relation.id) {
|
||||
return relation;
|
||||
}
|
||||
});
|
||||
|
||||
return document.id === $scope.id || duplicate;
|
||||
}));
|
||||
});
|
||||
return deferred.promise;
|
||||
};
|
||||
},
|
||||
|
||||
@@ -106,6 +106,8 @@
|
||||
<script src="app/docs/directive/Acl.js" type="text/javascript"></script>
|
||||
<script src="app/docs/directive/AclEdit.js" type="text/javascript"></script>
|
||||
<script src="app/docs/directive/Pell.js" type="text/javascript"></script>
|
||||
<script src="app/docs/directive/AddSpaceBetween.js" type="text/javascript"></script>
|
||||
<script src="app/docs/directive/InvertTextColor.js" type="text/javascript"></script>
|
||||
<!-- endref -->
|
||||
</head>
|
||||
<body translate-cloak ng-cloak>
|
||||
|
||||
@@ -104,7 +104,7 @@ $provide.value("$locale", {
|
||||
"mediumDate": "dd.MM.y",
|
||||
"mediumTime": "HH:mm:ss",
|
||||
"short": "dd.MM.yy HH:mm",
|
||||
"shortDate": "dd.MM.yy",
|
||||
"shortDate": "dd.MM.yyyy",
|
||||
"shortTime": "HH:mm"
|
||||
},
|
||||
"NUMBER_FORMATS": {
|
||||
|
||||
@@ -104,7 +104,7 @@ $provide.value("$locale", {
|
||||
"mediumDate": "MMM d, y",
|
||||
"mediumTime": "h:mm:ss a",
|
||||
"short": "M/d/yy h:mm a",
|
||||
"shortDate": "M/d/yy",
|
||||
"shortDate": "MM/dd/yyyy",
|
||||
"shortTime": "h:mm a"
|
||||
},
|
||||
"NUMBER_FORMATS": {
|
||||
|
||||
@@ -86,7 +86,7 @@ $provide.value("$locale", {
|
||||
"mediumDate": "d MMM y",
|
||||
"mediumTime": "H:mm:ss",
|
||||
"short": "d/M/yy H:mm",
|
||||
"shortDate": "d/M/yy",
|
||||
"shortDate": "dd/MM/yyyy",
|
||||
"shortTime": "H:mm"
|
||||
},
|
||||
"NUMBER_FORMATS": {
|
||||
|
||||
@@ -86,7 +86,7 @@ $provide.value("$locale", {
|
||||
"mediumDate": "d MMM y",
|
||||
"mediumTime": "HH:mm:ss",
|
||||
"short": "dd/MM/y HH:mm",
|
||||
"shortDate": "dd/MM/y",
|
||||
"shortDate": "dd/MM/yyyy",
|
||||
"shortTime": "HH:mm"
|
||||
},
|
||||
"NUMBER_FORMATS": {
|
||||
|
||||
@@ -86,7 +86,7 @@ $provide.value("$locale", {
|
||||
"mediumDate": "y\u5e74M\u6708d\u65e5",
|
||||
"mediumTime": "ah:mm:ss",
|
||||
"short": "y/M/d ah:mm",
|
||||
"shortDate": "y/M/d",
|
||||
"shortDate": "yyyy/MM/dd",
|
||||
"shortTime": "ah:mm"
|
||||
},
|
||||
"NUMBER_FORMATS": {
|
||||
|
||||
@@ -86,7 +86,7 @@ $provide.value("$locale", {
|
||||
"mediumDate": "y\u5e74M\u6708d\u65e5",
|
||||
"mediumTime": "ah:mm:ss",
|
||||
"short": "y/M/d ah:mm",
|
||||
"shortDate": "y/M/d",
|
||||
"shortDate": "yyyy/MM/dd",
|
||||
"shortTime": "ah:mm"
|
||||
},
|
||||
"NUMBER_FORMATS": {
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
"shared_document_message": "Sie können dieses Dokument mit diesem Link freigeben. Beachten Sie, dass jeder, der diesen Link hat, das Dokument sehen kann.<br/><input class=\"form-control share-link\" type=\"text\" readonly=\"readonly\" value=\"{{ link }}\" onclick=\"this.select(); document.execCommand('copy');\" />",
|
||||
"not_found": "Dokument nicht gefunden",
|
||||
"forbidden": "Zugriff verweigert",
|
||||
"download_files": "Datei herunterladen",
|
||||
"download_files": "Dateien herunterladen",
|
||||
"export_pdf": "in PDF exportieren",
|
||||
"by_creator": "von",
|
||||
"comments": "Kommentar",
|
||||
@@ -107,6 +107,8 @@
|
||||
"workflow_comment": "Fügen Sie einen Workflow Kommentar hinzu",
|
||||
"workflow_validated_title": "Workflow-Schritt validiert",
|
||||
"workflow_validated_message": "Der Workflow-Schritt wurde erfolgreich validiert.",
|
||||
"display_mode_list": "Dateien in Liste anzeigen",
|
||||
"display_mode_grid": "Dateien im Raster anzeigen",
|
||||
"content": {
|
||||
"content": "Inhalt",
|
||||
"delete_file_title": "Datei löschen",
|
||||
@@ -276,6 +278,7 @@
|
||||
"menu_vocabularies": "Vokabulareinträge",
|
||||
"menu_configuration": "Einstellungen",
|
||||
"menu_inbox": "Posteingang durchsuchen",
|
||||
"menu_metadata": "Benutzerdefinierte Metadaten",
|
||||
"menu_monitoring": "Überwachung",
|
||||
"user": {
|
||||
"title": "Benutzerverwaltung",
|
||||
@@ -286,6 +289,8 @@
|
||||
"edit": {
|
||||
"delete_user_title": "Benutzer löschen",
|
||||
"delete_user_message": "Möchten Sie diesen Benutzer wirklich löschen? Alle zugehörigen Dokumente, Dateien und Tags werden gelöscht",
|
||||
"user_used_title": "Benutzer in Verwendung",
|
||||
"user_used_message": "Dieser Benutzer wird im Workflow \"{{ name }}\" benutzt",
|
||||
"edit_user_failed_title": "Dieser Benutzer existiert bereits",
|
||||
"edit_user_failed_message": "Dieser Benutzername wurde bereits von einem anderen Benutzer gewählt",
|
||||
"edit_user_title": "Bearbeiten \"{{ username }}\"",
|
||||
@@ -347,7 +352,10 @@
|
||||
"message": "Ihr Konto wird nicht mehr durch die Zwei-Faktor-Authentifizierung geschützt.",
|
||||
"confirm_password": "Bestätigen Sie ihr Passwort",
|
||||
"submit": "Deaktivieren der Zwei-Faktor-Authentifizierung"
|
||||
}
|
||||
},
|
||||
"test_totp": "Bitte geben Sie den auf Ihrem Telefon angezeigten Validierungscode ein:",
|
||||
"test_code_success": "Validierungscode OK",
|
||||
"test_code_fail": "Dieser Code ist nicht gültig. Überprüfen Sie, ob Ihr Telefon ordnungsgemäß konfiguriert ist, oder deaktivieren Sie die Zwei-Faktor-Authentifizierung"
|
||||
},
|
||||
"group": {
|
||||
"title": "Gruppenverwaltung",
|
||||
@@ -358,6 +366,8 @@
|
||||
"delete_group_message": "Wollen Sie diese Gruppe wirklich löschen?",
|
||||
"edit_group_failed_title": "Gruppe existiert bereits",
|
||||
"edit_group_failed_message": "Dieser Gruppenname wird bereits von einer anderen Gruppe übernommen",
|
||||
"group_used_title": "Gruppe in Verwendung",
|
||||
"group_used_message": "Diese Gruppe wird im Workflow \"{{ name }}\" verwendet",
|
||||
"edit_group_title": "Bearbeiten \"{{ name }}\"",
|
||||
"add_group_title": "Neue Gruppe hinzufügen",
|
||||
"name": "Name",
|
||||
@@ -403,6 +413,12 @@
|
||||
"webhook_create_date": "Erstelldatum",
|
||||
"webhook_add": "Webhook hinzufügen"
|
||||
},
|
||||
"metadata": {
|
||||
"title": "Konfiguration benutzerdefinierter Metadaten",
|
||||
"message": "Hier können Sie Ihren Dokumenten benutzerdefinierte Metadaten wie eine interne Kennung oder ein Ablaufdatum hinzufügen. Bitte beachten Sie, dass der Metadatentyp nach der Erstellung nicht mehr geändert werden kann.",
|
||||
"name": "Metadatensatz Name",
|
||||
"type": "Metadatensatz Typ"
|
||||
},
|
||||
"inbox": {
|
||||
"title": "Posteingang durchsuchen",
|
||||
"message": "Wenn Sie diese Funktion aktivieren, durchsucht das System den angegebenen Posteingang jede Minute nach <strong>ungelesenen</strong> E-Mails und importiert diese automatisch.<br/>Nach dem Import einer E-Mail wird diese als gelesen markiert.<br/>Folgen Sie den Links zu Konfigurationseinstellungen für <a href=\"https://support.google.com/mail/answer/7126229?hl=en\" target=\"_blank\">Gmail</a>, <a href=\"https://support.office.com/en-us/article/pop-imap-and-smtp-settings-for-outlook-com-d088b986-291d-42b8-9564-9c414e2aa040\" target=\"_blank\">Outlook.com</a>, <a href=\"https://help.yahoo.com/kb/SLN4075.html\" target=\"_blank\">Yahoo</a>.",
|
||||
@@ -559,6 +575,30 @@
|
||||
"first": "Erste",
|
||||
"last": "Letzte"
|
||||
},
|
||||
"onboarding": {
|
||||
"step1": {
|
||||
"title": "Das erste Mal?",
|
||||
"description": "Wenn Sie Teedy zum ersten Mal nutzen, klicken Sie auf die Schaltfläche Weiter. Andernfalls können Sie mich schließen."
|
||||
},
|
||||
"step2": {
|
||||
"title": "Dokumente",
|
||||
"description": "Teedy ist in Dokumenten organisiert und jedes Dokument enthält mehrere Dateien."
|
||||
},
|
||||
"step3": {
|
||||
"title": "Dateien",
|
||||
"description": "Sie können Dateien hinzufügen, nachdem Sie ein Dokument erstellt haben oder vorher, indem Sie diesen Schnell-Hochlade-Bereich verwenden."
|
||||
},
|
||||
"step4": {
|
||||
"title": "Suche",
|
||||
"description": "Dies ist die Hauptmethode, um Ihre Dokumente wiederzufinden. Es gibt auch eine erweiterte Suche mit dem Suchbutton."
|
||||
},
|
||||
"step5": {
|
||||
"title": "Tags",
|
||||
"description": "Dokumente können in Tags organisiert werden (ähnlich wie Oberordner). Erstellen Sie sie hier."
|
||||
}
|
||||
},
|
||||
"yes": "Ja",
|
||||
"no": "Nein",
|
||||
"ok": "OK",
|
||||
"cancel": "Abbrechen",
|
||||
"share": "Teilen",
|
||||
@@ -572,6 +612,7 @@
|
||||
"edit": "Bearbeiten",
|
||||
"delete": "Löschen",
|
||||
"rename": "Umbenennen",
|
||||
"download": "Herunterladen",
|
||||
"loading": "Lädt...",
|
||||
"send": "Absenden",
|
||||
"enabled": "Aktiviert",
|
||||
|
||||
@@ -562,7 +562,8 @@
|
||||
"email": "Must be a valid e-mail",
|
||||
"password_confirm": "Password and password confirmation must match",
|
||||
"number": "Number required",
|
||||
"no_space": "Spaces are not allowed"
|
||||
"no_space": "Spaces and colons are not allowed",
|
||||
"alphanumeric": "Only letters and numbers are allowed"
|
||||
},
|
||||
"action_type": {
|
||||
"ADD_TAG": "Add a tag",
|
||||
|
||||
@@ -551,7 +551,8 @@
|
||||
"email": "Doit être une adresse e-mail valide",
|
||||
"password_confirm": "Le mot de passe et sa confirmation doivent être identiques",
|
||||
"number": "Nombre requis",
|
||||
"no_space": "Les espaces ne sont pas autorisés"
|
||||
"no_space": "Les espaces ne sont pas autorisés",
|
||||
"alphanumeric": "Seuls les lettres et les chiffres sont autorisés"
|
||||
},
|
||||
"action_type": {
|
||||
"ADD_TAG": "Ajouter un tag",
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<a>
|
||||
<div ng-bind-html="match.label.title | uibTypeaheadHighlight: query"></div>
|
||||
<div class="small">{{ match.label.create_date | date: $root.dateFormat }}</div>
|
||||
</a>
|
||||
@@ -7,6 +7,7 @@
|
||||
</li>
|
||||
</ul>
|
||||
<input class="form-control" type="text" id="{{ ref }}" ng-attr-placeholder="{{ 'directive.selectrelation.typeahead' | translate }}" ng-model="input" ng-disabled="ngDisabled"
|
||||
autocomplete="off" uib-typeahead="document.title for document in getDocumentTypeahead($viewValue)"
|
||||
autocomplete="off" uib-typeahead="document for document in getDocumentTypeahead($viewValue)"
|
||||
typeahead-template-url="partial/docs/directive.selectionrelation.typeahead.html"
|
||||
typeahead-wait-ms="200" typeahead-on-select="addRelation($item)" />
|
||||
</div>
|
||||
@@ -1,7 +1,7 @@
|
||||
<div>
|
||||
<ul class="list-inline">
|
||||
<li ng-repeat="tag in tags">
|
||||
<span class="label label-info" ng-style="{ 'background': tag.color }">{{ tag.name }}
|
||||
<span class="label label-info" ng-style="{ 'background': tag.color }" invert-text-color="{{ tag.color }}">{{ tag.name }}
|
||||
<span class="fas fa-times" ng-click="deleteTag(tag)" ng-show="!ngDisabled"></span>
|
||||
</span>
|
||||
</li>
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
<div class="modal-header">
|
||||
<h3>{{ 'tag.new_tag' | translate }}</h3>
|
||||
</div>
|
||||
<form name="tagForm" novalidate>
|
||||
<form name="tagForm" class="form-inline text-center" novalidate>
|
||||
<div class="modal-body">
|
||||
<p class="input-group" ng-class="{ 'has-error': !tagForm.name.$valid && tagForm.$dirty }">
|
||||
<span colorpicker class="input-group-addon btn btn-default" data-color="#3a87ad" ng-model="tag.color" ng-style="{ 'background': tag.color }"> </span>
|
||||
<input type="text" name="name" ng-attr-placeholder="{{ 'tag.new_tag' | translate }}" class="form-control"
|
||||
ng-maxlength="36" required ng-model="tag.name" ui-validate="{ space: '!$value || $value.indexOf(\' \') == -1' }">
|
||||
</p>
|
||||
<span class="text-danger" ng-show="tagForm.name.$error.space && tagForm.$dirty">{{ 'validation.no_space' | translate }}</span>
|
||||
<div class="input-group" ng-class="{ 'has-error': !tagForm.color.$valid && tagForm.$dirty }">
|
||||
<span colorpicker class="input-group-addon btn btn-default" data-color="#3a87ad" ng-model="tag.color" ng-style="{ 'background': tag.color }"> </span>
|
||||
<input type="text" name="color" class="form-control" ng-maxlength="7" ng-model="tag.color" ng-show="hexaField">
|
||||
<button class="btn btn-default" ng-click="hexaField = true" ng-show="!hexaField"><span class="fas fa-microchip"></span></button>
|
||||
</div>
|
||||
<div class="form-group" ng-class="{ 'has-error': !tagForm.name.$valid && tagForm.$dirty }">
|
||||
<input type="text" name="name" ng-attr-placeholder="{{ 'tag.new_tag' | translate }}" class="form-control"
|
||||
ng-maxlength="36" required ng-model="tag.name" ui-validate="{ space: '!$value || $value.indexOf(\' \') == -1 && $value.indexOf(\':\') == -1' }">
|
||||
</div>
|
||||
<p class="text-danger" ng-show="tagForm.name.$error.space && tagForm.$dirty">{{ 'validation.no_space' | translate }}</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button ng-click="close(tag)" ng-disabled="!tagForm.$valid" class="btn btn-primary">
|
||||
|
||||
@@ -36,16 +36,21 @@
|
||||
<p class="help-block" ng-show="documentForm.description.$error.maxlength && documentForm.$dirty">{{ 'validation.too_long' | translate }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-group" ng-class="{ 'has-error': !documentForm.createDate.$valid }">
|
||||
<label class="col-sm-2 control-label" for="inputCreateDate">{{ 'document.creation_date' | translate }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" id="inputCreateDate"
|
||||
current-text="{{ 'directive.datepicker.current' | translate }}"
|
||||
clear-text="{{ 'directive.datepicker.clear' | translate }}"
|
||||
close-text="{{ 'directive.datepicker.close' | translate }}"
|
||||
ng-readonly="true" uib-datepicker-popup="{{ dateFormat }}" class="form-control"
|
||||
ng-model="document.create_date" datepicker-options="{ startingDay: 1, showWeeks: false }"
|
||||
ng-click="datepickerOpened = true" is-open="datepickerOpened" ng-disabled="fileIsUploading" />
|
||||
<div class="input-group">
|
||||
<input type="text" id="inputCreateDate" name="createDate"
|
||||
current-text="{{ 'directive.datepicker.current' | translate }}"
|
||||
clear-text="{{ 'directive.datepicker.clear' | translate }}"
|
||||
close-text="{{ 'directive.datepicker.close' | translate }}"
|
||||
uib-datepicker-popup="{{ dateFormat }}" class="form-control"
|
||||
ng-model="document.create_date" datepicker-options="{ startingDay: 1, showWeeks: false }"
|
||||
is-open="datepickerOpened" ng-disabled="fileIsUploading" />
|
||||
<span class="input-group-btn">
|
||||
<button type="button" class="btn btn-default" ng-click="datepickerOpened = true" ng-disabled="fileIsUploading"><i class="fas fa-calendar"></i></button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@@ -58,7 +63,7 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="inputFiles">{{ 'document.edit.new_files' | translate }}</label>
|
||||
<div class="col-sm-6">
|
||||
<div class="col-sm-10">
|
||||
<input type="file" ngf-select class="form-control" id="inputFiles" ngf-multiple="true" ng-model="newFiles" ng-disabled="fileIsUploading"></input>
|
||||
</div>
|
||||
<div class="col-sm-4" ng-if="orphanFiles.length > 0"
|
||||
@@ -84,14 +89,18 @@
|
||||
name="{{ meta.id }}" ng-model="meta.value" autocomplete="off"
|
||||
ng-disabled="fileIsUploading" />
|
||||
|
||||
<input ng-if="meta.type == 'DATE'"
|
||||
type="text" id="input{{ meta.id }}" name="{{ meta.id }}"
|
||||
current-text="{{ 'directive.datepicker.current' | translate }}"
|
||||
clear-text="{{ 'directive.datepicker.clear' | translate }}"
|
||||
close-text="{{ 'directive.datepicker.close' | translate }}"
|
||||
ng-readonly="true" uib-datepicker-popup="{{ dateFormat }}" class="form-control"
|
||||
ng-model="meta.value" datepicker-options="{ startingDay: 1, showWeeks: false }"
|
||||
ng-click="datepickerOpenedMeta[meta.id] = true" is-open="datepickerOpenedMeta[meta.id]" ng-disabled="fileIsUploading" />
|
||||
<div class="input-group" ng-if="meta.type == 'DATE'">
|
||||
<input type="text" id="input{{ meta.id }}" name="{{ meta.id }}"
|
||||
current-text="{{ 'directive.datepicker.current' | translate }}"
|
||||
clear-text="{{ 'directive.datepicker.clear' | translate }}"
|
||||
close-text="{{ 'directive.datepicker.close' | translate }}"
|
||||
uib-datepicker-popup="{{ dateFormat }}" class="form-control"
|
||||
ng-model="meta.value" datepicker-options="{ startingDay: 1, showWeeks: false }"
|
||||
is-open="datepickerOpenedMeta[meta.id]" ng-disabled="fileIsUploading" />
|
||||
<span class="input-group-btn">
|
||||
<button type="button" class="btn btn-default" ng-click="datepickerOpenedMeta[meta.id] = true" ng-disabled="fileIsUploading"><i class="fas fa-calendar"></i></button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<input ng-if="meta.type == 'INTEGER'"
|
||||
ng-pattern="/^[0-9]*$/" class="form-control" type="text" id="input{{ meta.id }}"
|
||||
@@ -182,7 +191,7 @@
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="inputRelations">{{ 'document.relations' | translate }}</label>
|
||||
<div class="col-sm-10">
|
||||
<select-relation relations="document.relations" ref="inputRelations" ng-disabled="fileIsUploading"></select-relation>
|
||||
<select-relation id="document.id" relations="document.relations" ref="inputRelations" ng-disabled="fileIsUploading"></select-relation>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -258,7 +258,7 @@
|
||||
</div>
|
||||
|
||||
<div class="tags small">
|
||||
<span class="label label-info" ng-repeat="tag in document.tags" ng-style="{ 'background': tag.color }">
|
||||
<span class="label label-info" ng-repeat="tag in document.tags" ng-style="{ 'background': tag.color }" invert-text-color="{{ tag.color }}" add-space-between>
|
||||
{{ tag.name }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -161,7 +161,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody ui-sortable="fileSortableOptions" ng-model="files">
|
||||
<tr ng-repeat="file in files">
|
||||
<tr ng-repeat="file in files" ng-class="{ 'active': openedFile.id == file.id }">
|
||||
<td class="pointer" ng-click="openFile(file, $event)">
|
||||
<div class="thumbnail-list">
|
||||
<img ng-src="../api/file/{{ file.id }}/data?size=thumb" />
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
|
||||
<ul class="list-inline">
|
||||
<li ng-repeat="tag in document.tags">
|
||||
<span class="label label-info pointer" ng-click="setSearch('tag:' + tag.name)" ng-style="{ 'background': tag.color }">{{ tag.name }}</span>
|
||||
<span class="label label-info pointer" ng-click="setSearch('tag:' + tag.name)" ng-style="{ 'background': tag.color }" invert-text-color="{{ tag.color }}">{{ tag.name }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<label class="col-sm-2 control-label" for="inputName">{{ 'settings.group.edit.name' | translate }}</label>
|
||||
<div class="col-sm-7">
|
||||
<input name="name" type="text" id="inputName" required class="form-control"
|
||||
ui-validate="{ space: '!$value || $value.indexOf(\' \') == -1' }"
|
||||
ng-pattern="/^[a-zA-Z0-9_]*$/"
|
||||
ng-minlength="3" ng-maxlength="50" ng-attr-placeholder="{{ 'settings.group.edit.name' | translate }}" ng-model="group.name"/>
|
||||
</div>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<span class="help-block" ng-show="editGroupForm.name.$error.required && editGroupForm.$dirty">{{ 'validation.required' | translate }}</span>
|
||||
<span class="help-block" ng-show="editGroupForm.name.$error.minlength && editGroupForm.$dirty">{{ 'validation.too_short' | translate }}</span>
|
||||
<span class="help-block" ng-show="editGroupForm.name.$error.maxlength && editGroupForm.$dirty">{{ 'validation.too_long' | translate }}</span>
|
||||
<span class="help-block" ng-show="editGroupForm.name.$error.space && editGroupForm.$dirty">{{ 'validation.no_space' | translate }}</span>
|
||||
<span class="help-block" ng-show="editGroupForm.name.$error.pattern && editGroupForm.$dirty">{{ 'validation.alphanumeric' | translate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<label class="col-sm-2 control-label" for="inputUserUsername">{{ 'settings.user.edit.username' | translate }}</label>
|
||||
<div class="col-sm-7">
|
||||
<input name="userUsername" type="text" id="inputUserUsername" required ng-disabled="isEdit()" class="form-control"
|
||||
ng-pattern="/^[a-zA-Z0-9_]*$/"
|
||||
ng-minlength="3" ng-maxlength="50" ng-attr-placeholder="{{ 'settings.user.edit.username' | translate }}" ng-model="user.username"/>
|
||||
</div>
|
||||
|
||||
@@ -16,6 +17,7 @@
|
||||
<span class="help-block" ng-show="editUserForm.userUsername.$error.required && editUserForm.$dirty">{{ 'validation.required' | translate }}</span>
|
||||
<span class="help-block" ng-show="editUserForm.userUsername.$error.minlength && editUserForm.$dirty">{{ 'validation.too_short' | translate }}</span>
|
||||
<span class="help-block" ng-show="editUserForm.userUsername.$error.maxlength && editUserForm.$dirty">{{ 'validation.too_long' | translate }}</span>
|
||||
<span class="help-block" ng-show="editUserForm.userUsername.$error.pattern && editUserForm.$dirty">{{ 'validation.alphanumeric' | translate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -13,16 +13,19 @@
|
||||
|
||||
<div class="col-sm-6">
|
||||
<input type="text" name="name" class="form-control" id="inputName"
|
||||
ng-maxlength="36" required ng-model="tag.name" ui-validate="{ space: '!$value || $value.indexOf(\' \') == -1' }">
|
||||
ng-maxlength="36" required ng-model="tag.name" ui-validate="{ space: '!$value || $value.indexOf(\' \') == -1 && $value.indexOf(\':\') == -1' }">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="form-group" ng-class="{ 'has-error': !editTagForm.color.$valid, success: editTagForm.color.$valid }">
|
||||
<label class="col-sm-2 control-label" for="inputColor">{{ 'tag.edit.color' | translate }}</label>
|
||||
|
||||
<div class="col-sm-2">
|
||||
<span colorpicker class="btn" data-color="" id="inputColor"
|
||||
ng-model="tag.color" ng-style="{ 'background': tag.color }"> </span>
|
||||
<div class="input-group" id="inputColor" ng-class="{ 'has-error': !editTagForm.color.$valid && tagForm.$dirty }">
|
||||
<span colorpicker class="input-group-addon btn btn-default" data-color="#3a87ad" ng-model="tag.color" ng-style="{ 'background': tag.color }"> </span>
|
||||
<button class="btn btn-default" ng-click="hexaField = true" ng-show="!hexaField"><span class="fas fa-microchip"></span></button>
|
||||
<input type="text" name="color" class="form-control" ng-maxlength="7" ng-model="tag.color" ng-show="hexaField">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -13,13 +13,17 @@
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="well well-3d">
|
||||
<form name="tagForm" novalidate>
|
||||
<p class="input-group" ng-class="{ 'has-error': !tagForm.name.$valid && tagForm.$dirty }">
|
||||
<form class="form-inline text-center" name="tagForm" novalidate>
|
||||
<div class="input-group" ng-class="{ 'has-error': !tagForm.color.$valid && tagForm.$dirty }">
|
||||
<span colorpicker class="input-group-addon btn btn-default" data-color="#3a87ad" ng-model="tag.color" ng-style="{ 'background': tag.color }"> </span>
|
||||
<input type="text" name="color" class="form-control" ng-maxlength="7" ng-model="tag.color" ng-show="hexaField">
|
||||
<button class="btn btn-default" ng-click="hexaField = true" ng-show="!hexaField"><span class="fas fa-microchip"></span></button>
|
||||
</div>
|
||||
<div class="form-group" ng-class="{ 'has-error': !tagForm.name.$valid && tagForm.$dirty }">
|
||||
<input type="text" name="name" ng-attr-placeholder="{{ 'tag.new_tag' | translate }}" class="form-control"
|
||||
ng-maxlength="36" required ng-model="tag.name" ui-validate="{ space: '!$value || $value.indexOf(\' \') == -1' }">
|
||||
<span class="input-group-addon btn btn-primary" ng-disabled="!tagForm.$valid" ng-click="addTag()">{{ 'add' | translate }}</span>
|
||||
</p>
|
||||
ng-maxlength="36" required ng-model="tag.name" ui-validate="{ space: '!$value || $value.indexOf(\' \') == -1 && $value.indexOf(\':\') == -1' }">
|
||||
</div>
|
||||
<span class="btn btn-primary" ng-disabled="!tagForm.$valid" ng-click="addTag()">{{ 'add' | translate }}</span>
|
||||
<span class="text-danger" ng-show="tagForm.name.$error.space && tagForm.$dirty">{{ 'validation.no_space' | translate }}</span>
|
||||
</form>
|
||||
|
||||
|
||||
@@ -120,6 +120,10 @@ ul.tag-tree {
|
||||
cursor: pointer;
|
||||
|
||||
td {
|
||||
.tags {
|
||||
line-height: 190%;
|
||||
}
|
||||
|
||||
.label {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
@@ -7,3 +7,4 @@ log4j.appender.MEMORY.size=1000
|
||||
|
||||
log4j.logger.com.sismics=INFO
|
||||
log4j.logger.org.apache.pdfbox=ERROR
|
||||
log4j.logger.org.glassfish.jersey.servlet.WebComponent=ERROR
|
||||
@@ -214,6 +214,8 @@ public class TestDocumentResource extends BaseJerseyTest {
|
||||
Assert.assertEquals(0, searchDocuments("tag:super !tag:hr", document1Token));
|
||||
Assert.assertEquals(1, searchDocuments("shared:yes", document1Token));
|
||||
Assert.assertEquals(2, searchDocuments("lang:eng", document1Token));
|
||||
Assert.assertEquals(1, searchDocuments("mime:image/png", document1Token));
|
||||
Assert.assertEquals(0, searchDocuments("mime:empty/void", document1Token));
|
||||
Assert.assertEquals(1, searchDocuments("after:2010 before:2040-08 tag:super shared:yes lang:eng title description full:uranium", document1Token));
|
||||
|
||||
// Search documents (nothing)
|
||||
|
||||
@@ -26,6 +26,22 @@ public class TestTagResource extends BaseJerseyTest {
|
||||
clientUtil.createUser("tag1");
|
||||
String tag1Token = clientUtil.login("tag1");
|
||||
|
||||
// Create a tag with a wrong name
|
||||
Response response = target().path("/tag").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, tag1Token)
|
||||
.put(Entity.form(new Form()
|
||||
.param("name", "Tag:3")
|
||||
.param("color", "#ff0000")));
|
||||
Assert.assertEquals(Status.BAD_REQUEST, Status.fromStatusCode(response.getStatus()));
|
||||
|
||||
// Create a tag with a wrong name
|
||||
response = target().path("/tag").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, tag1Token)
|
||||
.put(Entity.form(new Form()
|
||||
.param("name", "Tag 3")
|
||||
.param("color", "#ff0000")));
|
||||
Assert.assertEquals(Status.BAD_REQUEST, Status.fromStatusCode(response.getStatus()));
|
||||
|
||||
// Create a tag
|
||||
JsonObject json = target().path("/tag").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, tag1Token)
|
||||
@@ -46,7 +62,7 @@ public class TestTagResource extends BaseJerseyTest {
|
||||
Assert.assertNotNull(tag4Id);
|
||||
|
||||
// Create a circular reference
|
||||
Response response = target().path("/tag/" + tag3Id).request()
|
||||
response = target().path("/tag/" + tag3Id).request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, tag1Token)
|
||||
.post(Entity.form(new Form()
|
||||
.param("name", "Tag3")
|
||||
|
||||
11
pom.xml
11
pom.xml
@@ -6,7 +6,7 @@
|
||||
<groupId>com.sismics.docs</groupId>
|
||||
<artifactId>docs-parent</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<version>1.7</version>
|
||||
<version>1.8</version>
|
||||
|
||||
<name>Docs Parent</name>
|
||||
|
||||
@@ -22,10 +22,11 @@
|
||||
<org.apache.commons.commons-email.version>1.5</org.apache.commons.commons-email.version>
|
||||
<org.freemarker.freemarker.version>2.3.28</org.freemarker.freemarker.version>
|
||||
<commons-dbcp.version>1.4</commons-dbcp.version>
|
||||
<com.google.guava.guava.version>26.0-jre</com.google.guava.guava.version>
|
||||
<com.google.guava.guava.version>28.2-jre</com.google.guava.guava.version>
|
||||
<log4j.log4j.version>1.2.16</log4j.log4j.version>
|
||||
<org.slf4j.version>1.6.4</org.slf4j.version>
|
||||
<org.slf4j.jcl-over-slf4j.version>1.6.6</org.slf4j.jcl-over-slf4j.version>
|
||||
<org.slf4j.jul-to-slf4j.version>1.6.6</org.slf4j.jul-to-slf4j.version>
|
||||
<junit.junit.version>4.12</junit.junit.version>
|
||||
<com.h2database.h2.version>1.4.197</com.h2database.h2.version>
|
||||
<org.glassfish.jersey.version>2.27</org.glassfish.jersey.version>
|
||||
@@ -241,6 +242,12 @@
|
||||
<version>${org.slf4j.jcl-over-slf4j.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>jul-to-slf4j</artifactId>
|
||||
<version>${org.slf4j.jul-to-slf4j.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
|
||||
Reference in New Issue
Block a user