1
0
mirror of https://github.com/sismics/docs.git synced 2025-12-14 10:16:21 +00:00

28 Commits
v1.7 ... v1.8

Author SHA1 Message Date
bgamard
6367a1fd15 v1.8 2020-03-26 19:57:10 +01:00
bgamard
2c5ff64d42 Closes #387: validation username and group name in UI 2020-03-25 19:02:50 +01:00
bgamard
e614cb41d8 update feedback api url 2020-03-25 18:13:49 +01:00
bgamard
82737e2280 Closes #334: highlight previously opened file 2020-03-07 17:56:01 +01:00
bgamard
3b5c27096b Closes #350: better relations widget 2020-03-07 17:46:40 +01:00
bgamard
8a85830bd3 #381: date fields manually editable 2020-03-07 00:36:03 +01:00
bgamard
19ac90688e upgrade guava 2020-02-19 20:47:26 +01:00
bgamard
5f4a6bc462 #336: fix search by type 2020-02-19 18:00:13 +01:00
bgamard
4c7f3166d4 Closes #332: tag text color legibility 2020-02-15 23:00:35 +01:00
bgamard
4233f4dd88 Closes #317: edit tag color hex code manually 2020-02-15 22:38:06 +01:00
bgamard
bd09312418 Closes #336: search document by file mime type 2020-02-15 22:05:04 +01:00
bgamard
11ab07b238 Closes #333: fix overflow in document table with a lot of tags 2020-02-15 15:44:32 +01:00
bgamard
d2e2f089fb update README for build deps 2020-02-14 21:48:45 +01:00
bgamard
d619f98de7 Closes #379: spaces and colons not allowed in tag name 2020-02-14 21:40:13 +01:00
bgamard
89228a52dc Closes #378: silence useless log from Jersey 2020-02-14 21:33:34 +01:00
bgamard
90a49efa4a Closes #373: high quality PDF to image conversion before OCR 2020-02-13 17:43:07 +01:00
junpet
a7423caeb1 Add Hungarian Language Support (#369)
Add Hungarian language support
2020-01-29 15:44:44 +01:00
bgamard
6f31a2c228 Closes #366: get the private key from the right user when processing files 2020-01-21 12:54:50 +01:00
Benjamin Gamard
fc98b0882f Merge remote-tracking branch 'origin/master' 2019-12-28 13:54:45 +01:00
Benjamin Gamard
dff05967ea Closes #363: pgsql compatibility for table alias in update queries 2019-12-28 13:53:42 +01:00
Benjamin Gamard
ec836a2f9d Update README.md 2019-10-13 01:24:10 +02:00
Benjamin Gamard
737c85cf00 Update README.md 2019-10-13 01:23:03 +02:00
Benjamin Gamard
ff7b07f464 Create FUNDING.yml 2019-10-12 23:05:35 +02:00
Mario Voigt
19422b5afa Fixed some language issue (#352) 2019-08-26 14:19:19 +02:00
Benjamin Gamard
6b93e413b6 #321: remove duplicate contributors 2019-06-03 11:45:38 +02:00
Mario Voigt
ab72736bcc Update de.json (#322)
Added missing german translation strings
2019-05-25 18:53:02 +02:00
Benjamin Gamard
38939e5d05 #314: force file content in utf8 2019-05-22 15:36:50 +02:00
Benjamin Gamard
1a90a0e0ad next dev iteration 2019-05-21 16:17:54 +02:00
58 changed files with 714 additions and 512 deletions

3
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
# These are supported funding model platforms
github: [jendib]

View File

@@ -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:
- |

View File

@@ -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

View File

@@ -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
-------

View File

@@ -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>
@@ -189,7 +189,26 @@
<groupId>org.postgresql</groupId>
<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>

View File

@@ -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.

View File

@@ -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());

View File

@@ -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());

View File

@@ -76,6 +76,11 @@ public class DocumentCriteria {
* A route is active.
*/
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;
}
}

View File

@@ -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());

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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());

View File

@@ -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"));
}
}

Binary file not shown.

View File

@@ -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>

View File

@@ -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>
@@ -68,6 +68,11 @@
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
</dependency>
<!-- Test dependencies -->
<dependency>

View File

@@ -111,7 +111,18 @@ public class ValidationUtil {
public static void validateHexColor(String s, String name, boolean nullable) throws ClientException {
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.
*

View File

@@ -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);

View File

@@ -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>

View File

@@ -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

View File

@@ -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]);

View File

@@ -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();
}

View File

@@ -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,12 +177,8 @@ 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)) {
parentId = null;
@@ -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,12 +259,8 @@ 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();
if (!aclDao.checkPermission(id, PermType.WRITE, getTargetIdList(null))) {

File diff suppressed because it is too large Load Diff

View File

@@ -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' }
];
})
/**

View File

@@ -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 () {

View File

@@ -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 });
}
};

View File

@@ -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('&#32;');
}
}
});

View File

@@ -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');
});
}
}
});

View File

@@ -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;
};
},

View File

@@ -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>

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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,8 +612,9 @@
"edit": "Bearbeiten",
"delete": "Löschen",
"rename": "Umbenennen",
"download": "Herunterladen",
"loading": "Lädt...",
"send": "Absenden",
"enabled": "Aktiviert",
"disabled": "Deaktiviert"
}
}

View File

@@ -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",

View File

@@ -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",

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 }">&nbsp;</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 }">&nbsp;</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">

View File

@@ -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>

View File

@@ -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>

View File

@@ -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" />

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 }">&nbsp;</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 }">&nbsp;</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>

View File

@@ -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 }">&nbsp;</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>

View File

@@ -120,6 +120,10 @@ ul.tag-tree {
cursor: pointer;
td {
.tags {
line-height: 190%;
}
.label {
margin-left: 5px;
}

View File

@@ -6,4 +6,5 @@ log4j.appender.MEMORY=com.sismics.util.log4j.MemoryAppender
log4j.appender.MEMORY.size=1000
log4j.logger.com.sismics=INFO
log4j.logger.org.apache.pdfbox=ERROR
log4j.logger.org.apache.pdfbox=ERROR
log4j.logger.org.glassfish.jersey.servlet.WebComponent=ERROR

View File

@@ -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)

View File

@@ -25,7 +25,23 @@ public class TestTagResource extends BaseJerseyTest {
// Login tag1
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")

13
pom.xml
View File

@@ -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>
@@ -240,7 +241,13 @@
<artifactId>jcl-over-slf4j</artifactId>
<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>