1
0
mirror of https://github.com/sismics/docs.git synced 2025-12-14 18:26:17 +00:00

34 Commits
v1.6 ... v1.7

Author SHA1 Message Date
Benjamin Gamard
7aa9fa4646 v1.7 2019-05-21 15:55:41 +02:00
Benjamin Gamard
82d788c8d3 Closes #300: adjust tests 2019-05-21 15:53:54 +02:00
Benjamin Gamard
ab8176efcb #300: custom metadata fields: UI read/write 2019-05-21 15:44:23 +02:00
Benjamin Gamard
b4c3e7a928 update Docker image infos 2019-05-20 15:18:08 +02:00
Benjamin Gamard
2db263fb68 #300: custom metadata fields: API read 2019-05-20 15:08:16 +02:00
Benjamin Gamard
5fd4d37972 #300: custom metadata fields: API write 2019-05-17 16:00:03 +02:00
Benjamin Gamard
9b1dbf351a #300: custom metadata fields: UI admin 2019-05-15 14:15:55 +02:00
Benjamin Gamard
4c7c058e0d Merge remote-tracking branch 'origin/master' 2019-05-15 13:34:21 +02:00
Benjamin Gamard
f8dc08b02b #300: custom metadata fields: API admin 2019-05-15 13:34:01 +02:00
yosef langer
0e6bc3ce54 Hebrew Language Support (#320) 2019-05-11 16:26:01 +02:00
Benjamin Gamard
fcb018406d Merge remote-tracking branch 'origin/master' 2019-05-07 14:38:15 +02:00
Benjamin Gamard
40756a5e4b Fix for https://bugs.openjdk.java.net/browse/JDK-8216039 2019-05-07 14:38:05 +02:00
Benjamin Gamard
61b12bdebd Closes #309: store onboarding status server side 2019-05-06 18:12:44 +02:00
Benjamin Gamard
8b1c41ae1e Closes #305: exclude tags from search 2019-05-03 15:35:47 +02:00
Benjamin Gamard
d654564f6b Closes #318: Sort file list on drag & drop before sending to server 2019-05-03 15:09:43 +02:00
Benjamin Gamard
8bd22ebafa Closes #301: download link in overflow menu 2019-05-03 14:09:35 +02:00
Benjamin Gamard
647e66d57b update api doc 2019-05-03 13:55:18 +02:00
Benjamin Gamard
67c8ac1aa3 Closes #307: log workflow create/delete in document logs 2019-05-03 13:54:10 +02:00
Benjamin Gamard
f336c7ae53 Closes #313: remove administrators from ACL targets search 2019-05-03 13:27:23 +02:00
Benjamin Gamard
9ea1dad62d Closes #296: firefox hack to prevent file open on drag & drop 2019-05-03 13:19:21 +02:00
Benjamin Gamard
58bc374e64 Closes #306: Prevent deleting/renaming users/groups used in route models 2019-05-02 16:19:50 +02:00
Benjamin Gamard
cea0d4887d Closes #302: increase version history modal size 2019-05-02 11:19:08 +02:00
Benjamin Gamard
d5e73ecd8b Closes #308: update user quota when deleting a document 2019-05-02 11:13:16 +02:00
Benjamin Gamard
2235a0498b Merge remote-tracking branch 'origin/master' 2019-05-02 10:54:28 +02:00
Benjamin Gamard
3f9b92831c Closes #312: fix file importer description 2019-05-02 10:54:19 +02:00
Benjamin Gamard
5680750c82 Merge remote-tracking branch 'origin/master' 2019-05-01 17:36:27 +02:00
Benjamin Gamard
298e3efe49 Spanish translation 2019-05-01 17:35:35 +02:00
Burak Sormageç
7b2bd6f9eb Turkish Language Support (#304)
Add turkish language support
2019-03-07 20:34:02 +01:00
Benjamin Gamard
d935e07990 pgsql compatibility 2019-02-12 14:55:12 +01:00
Benjamin Gamard
868a74c184 sismics docs -> teedy 2019-02-12 13:57:54 +01:00
Benjamin Gamard
a86af9736b fix test 2019-02-07 16:55:58 +01:00
Benjamin Gamard
8bd4d27d2f increase h2 lock timeout 2019-02-07 15:17:38 +01:00
Benjamin Gamard
94951c59f3 quick test 2019-02-07 15:08:17 +01:00
Benjamin Gamard
e39c83a5a6 update the file ID on a document with a native query 2019-02-07 14:50:47 +01:00
120 changed files with 2892 additions and 309 deletions

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
- 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 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 && \
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 && \
apt-get clean && rm -rf /var/lib/apt/lists/*
# Remove the embedded javax.mail jar from Jetty

View File

@@ -1,27 +1,27 @@
<h3 align="center">
<img src="https://www.sismicsdocs.com/img/github-title.png" alt="Sismics Docs" width=500 />
<img src="https://teedy.io/img/github-title.png" alt="Teedy" width=500 />
</h3>
[![Twitter: @sismicsdocs](https://img.shields.io/badge/contact-@sismicsdocs-blue.svg?style=flat)](https://twitter.com/sismicsdocs)
[![Twitter: @teedyio](https://img.shields.io/badge/contact-@teedyio-blue.svg?style=flat)](https://twitter.com/teedyio)
[![License: GPL v2](https://img.shields.io/badge/License-GPL%20v2-blue.svg)](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
[![Build Status](https://secure.travis-ci.org/sismics/docs.png)](http://travis-ci.org/sismics/docs)
Docs is an open source, lightweight document management system for individuals and businesses.
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">
We just launched a Cloud version of Sismics Docs! Head to <a href="https://www.sismicsdocs.com/">sismicsdocs.com</a> for more informations
Sismics Docs is now called Teedy! You can still find our cloud and support offer on <a href="https://teedy.io">teedy.io</a>
</h2>
<hr />
![New!](https://www.sismicsdocs.com/img/laptop-demo.png?20180301)
![New!](https://teedy.io/img/laptop-demo.png?20180301)
Demo
----
A demo is available at [demo.sismicsdocs.com](https://demo.sismicsdocs.com)
A demo is available at [demo.teedy.io](https://demo.teedy.io)
- Guest login is enabled with read access on all documents
- "admin" login with "admin" password
- "demo" login with "password" password
@@ -36,6 +36,7 @@ Features
- Flexible search engine with suggestions and highlighting
- Full text search in all supported files
- All [Dublin Core](http://dublincore.org/) metadata
- Custom user-defined metadata ![New!](https://www.sismics.com/public/img/new.png)
- Workflow system ![New!](https://www.sismics.com/public/img/new.png)
- 256-bit AES encryption of stored files
- File versioning ![New!](https://www.sismics.com/public/img/new.png)
@@ -58,14 +59,15 @@ Features
Install with Docker
-------------------
From a Docker host, run this command to download and install Sismics Docs. The server will run on <http://[your-docker-host-ip]:8100>.
A preconfigured Docker image is available, including OCR and media conversion tools, listening on port 8080. The database is an embedded H2 database but PostgreSQL is also supported for more performance.
**The default admin password is "admin". Don't forget to change it before going to production.**
- Master branch, can be unstable. Not recommended for production use: `sismics/docs:latest`
- Latest stable version: `sismics/docs:v1.7`
docker run --rm --name sismics_docs_latest -d -e DOCS_BASE_URL='http://[your-docker-host-ip]:8100' -p 8100:8080 -v sismics_docs_latest:/data sismics/docs:latest
<img src="http://www.newdesignfile.com/postpic/2011/01/green-info-icon_206509.png" width="16px" height="16px"> **Note:** You will need to change [your-docker-host-ip] with the IP address or FQDN of your docker host e.g.
The data directory is `/data`. Don't forget to mount a volume on it.
FQDN: http://docs.sismics.com
IP: http://192.168.100.10
To build external URL, the server is expecting a `DOCS_BASE_URL` environment variable (for example https://teedy.mycompany.com)
Manual installation
-------------------
@@ -81,12 +83,12 @@ Manual installation
The latest release is downloadable here: <https://github.com/sismics/docs/releases> in WAR format.
**The default admin password is "admin". Don't forget to change it before going to production.**
How to build Docs from the sources
How to build Teedy from the sources
----------------------------------
Prerequisites: JDK 8 with JCE, Maven 3, Tesseract 3 or 4
Docs is organized in several Maven modules:
Teedy is organized in several Maven modules:
- docs-core
- docs-web
@@ -126,15 +128,15 @@ The `master` branch is the default and base branch for the project. It is used f
Community
---------
Get updates on Sismics Docs' development and chat with the project maintainers:
Get updates on Teedy's development and chat with the project maintainers:
- Follow [@sismicsdocs on Twitter](https://twitter.com/sismicsdocs)
- Read and subscribe to [The Official Sismics Docs Blog](https://blog.sismicsdocs.com/)
- Check the [Official Website](https://www.sismicsdocs.com)
- Join us [on Facebook](https://www.facebook.com/sismicsdocs)
- 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
-------
Docs is released under the terms of the GPL license. See `COPYING` for more
Teedy is released under the terms of the GPL license. See `COPYING` for more
information or see <http://opensource.org/licenses/GPL-2.0>.

View File

@@ -4,7 +4,7 @@ buildscript {
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.3.0'
classpath 'com.android.tools.build:gradle:3.4.0'
}
}
apply plugin: 'com.android.application'

View File

@@ -1,6 +1,7 @@
package com.sismics.docs;
import android.app.Application;
import android.support.v7.app.AppCompatDelegate;
import com.sismics.docs.model.application.ApplicationContext;
import com.sismics.docs.util.PreferenceUtil;
@@ -22,5 +23,7 @@ public class MainApplication extends Application {
// TODO Provide documents to intent action get content
super.onCreate();
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
}
}

View File

@@ -63,14 +63,13 @@ public class DocListFragment extends Fragment {
recyclerView.setAdapter(adapter);
recyclerView.setHasFixedSize(true);
recyclerView.setLongClickable(true);
recyclerView.addItemDecoration(new DividerItemDecoration(getResources().getDrawable(R.drawable.abc_list_divider_mtrl_alpha)));
// Configure the LayoutManager
final LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(layoutManager);
// Configure the swipe refresh layout
swipeRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.swipeRefreshLayout);
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshLayout);
swipeRefreshLayout.setColorSchemeResources(android.R.color.holo_blue_bright,
android.R.color.holo_green_light,
android.R.color.holo_orange_light,
@@ -194,7 +193,7 @@ public class DocListFragment extends Fragment {
private void loadDocuments(final View view, final boolean reset) {
if (view == null) return;
final View progressBar = view.findViewById(R.id.progressBar);
final TextView documentsEmptyView = (TextView) view.findViewById(R.id.documentsEmptyView);
final TextView documentsEmptyView = view.findViewById(R.id.documentsEmptyView);
if (reset) {
loading = true;

View File

@@ -156,7 +156,7 @@ public class OkHttpUtil {
public static OkHttpClient buildClient(final Context context) {
// One-time header computation
if (userAgent == null) {
userAgent = "Sismics Docs Android " + ApplicationUtil.getVersionName(context) + "/Android " + Build.VERSION.RELEASE + "/" + Build.MODEL;
userAgent = "Teedy Android " + ApplicationUtil.getVersionName(context) + "/Android " + Build.VERSION.RELEASE + "/" + Build.MODEL;
}
if (acceptLanguage == null) {

View File

@@ -29,7 +29,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-light"
android:textColor="#212121"
android:textColor="?android:attr/textColorPrimary"
android:text="Test"
android:textSize="16sp"
android:ellipsize="end"
@@ -46,7 +46,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-light"
android:textColor="#777777"
android:textColor="?android:attr/textColorPrimary"
android:text="test2"
android:textSize="16sp"
android:maxLines="1"
@@ -69,7 +69,7 @@
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:textColor="#777777"
android:textColor="?android:attr/textColorPrimary"
android:fontFamily="sans-serif-light"/>
</RelativeLayout>

View File

@@ -9,7 +9,6 @@
<android.support.design.widget.CoordinatorLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/overview_coordinator_layout"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
android:layout_width="match_parent"
android:layout_height="match_parent">
@@ -22,7 +21,7 @@
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:popupTheme="@style/AppTheme"
app:layout_scrollFlags="enterAlways|scroll|snap" />
</android.support.design.widget.AppBarLayout>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

@@ -11,7 +11,7 @@
<!-- App -->
<string name="drawer_open">Navigationsleiste öffnen</string>
<string name="drawer_close">Navigationsleiste schließen</string>
<string name="login_explain"><![CDATA[Um zu beginnen, müssen Sie Sismics Docs Server herunterladen und installieren <a href="https://github.com/sismics/docs">github.com/sismics/docs</a>, sowie die Login-Daten unten eingeben]]></string>
<string name="login_explain"><![CDATA[Um zu beginnen, müssen Sie Teedy Server herunterladen und installieren <a href="https://github.com/sismics/docs">github.com/sismics/docs</a>, sowie die Login-Daten unten eingeben]]></string>
<string name="server">Server</string>
<string name="username">Username</string>
<string name="password">Password</string>
@@ -83,7 +83,7 @@
<string name="file_delete_failure">Netzwerkfehler beim Löschen der Datei</string>
<string name="file_deleting_message">Lösche Datei</string>
<string name="error_reading_file">Fehler beim Lesen der Datei</string>
<string name="upload_notification_title">Sismics Docs</string>
<string name="upload_notification_title">Teedy</string>
<string name="upload_notification_message">Neue Datei in das Dokument hochladen</string>
<string name="upload_notification_error">Fehler beim Hochladen der neuen Datei</string>
<string name="delete_file">Aktuelle Datei löschen</string>
@@ -119,9 +119,9 @@
<string name="export_comments">Kommentare exportieren</string>
<string name="export_metadata">Metadaten exportieren</string>
<string name="mm">mm</string>
<string name="download_file_title">Sismics Docs Datei Export</string>
<string name="download_document_title">Sismics Docs Dokumentenexport</string>
<string name="download_pdf_title">Sismics Docs PDF Export</string>
<string name="download_file_title">Teedy Datei Export</string>
<string name="download_document_title">Teedy Dokumentenexport</string>
<string name="download_pdf_title">Teedy PDF Export</string>
<string name="latest_activity">Letzte Aktivität</string>
<string name="activity">Aktivitäten</string>
<string name="email">E-Mail</string>

View File

@@ -11,7 +11,7 @@
<!-- App -->
<string name="drawer_open">Ouvrir le menu de navigation</string>
<string name="drawer_close">Fermer le menu de navigation</string>
<string name="login_explain"><![CDATA[Pour commencer, vous devez télécharger et installer le serveur Sismics Docs sur <a href="https://github.com/sismics/docs">github.com/sismics/docs</a> et entrer son URL ci-dessous]]></string>
<string name="login_explain"><![CDATA[Pour commencer, vous devez télécharger et installer le serveur Teedy sur <a href="https://github.com/sismics/docs">github.com/sismics/docs</a> et entrer son URL ci-dessous]]></string>
<string name="server">Serveur</string>
<string name="username">Nom d\'utilisateur</string>
<string name="password">Mot de passe</string>
@@ -83,7 +83,7 @@
<string name="file_delete_failure">Erreur réseau lors de la suppression du fichier</string>
<string name="file_deleting_message">Suppression du fichier</string>
<string name="error_reading_file">Erreur lors de la lecture du fichier</string>
<string name="upload_notification_title">Sismics Docs</string>
<string name="upload_notification_title">Teedy</string>
<string name="upload_notification_message">Envoi du nouveau fichier</string>
<string name="upload_notification_error">Erreur lors de l\'envoi du nouveau fichier</string>
<string name="delete_file">Supprimer ce fichier</string>
@@ -119,9 +119,9 @@
<string name="export_comments">Exporter les commentaires</string>
<string name="export_metadata">Exporter les métadonnées</string>
<string name="mm">mm</string>
<string name="download_file_title">Export de fichier Sismics Docs</string>
<string name="download_document_title">Export de document Sismics Docs</string>
<string name="download_pdf_title">Export PDF Sismics Docs</string>
<string name="download_file_title">Export de fichier Teedy</string>
<string name="download_document_title">Export de document Teedy</string>
<string name="download_pdf_title">Export PDF Teedy</string>
<string name="latest_activity">Activité récente</string>
<string name="activity">Activité</string>
<string name="email">E-mail</string>

View File

@@ -9,10 +9,10 @@
<string name="validate_error_alphanumeric">Only letters and numbers</string>
<!-- App -->
<string name="app_name" translatable="false">Sismics Docs</string>
<string name="app_name" translatable="false">Teedy</string>
<string name="drawer_open">Open navigation drawer</string>
<string name="drawer_close">Close navigation drawer</string>
<string name="login_explain"><![CDATA[To start, you must download and install Sismics Docs Server on <a href="https://github.com/sismics/docs">github.com/sismics/docs</a> and enter its below]]></string>
<string name="login_explain"><![CDATA[To start, you must download and install Teedy Server on <a href="https://github.com/sismics/docs">github.com/sismics/docs</a> and enter its below]]></string>
<string name="server">Server</string>
<string name="username">Username</string>
<string name="password">Password</string>
@@ -87,7 +87,7 @@
<string name="file_delete_failure">Network error while deleting the current file</string>
<string name="file_deleting_message">Deleting file</string>
<string name="error_reading_file">Error while reading the file</string>
<string name="upload_notification_title">Sismics Docs</string>
<string name="upload_notification_title">Teedy</string>
<string name="upload_notification_message">Uploading the new file to the document</string>
<string name="upload_notification_error">Error uploading the new file</string>
<string name="delete_file">Delete current file</string>
@@ -123,9 +123,9 @@
<string name="export_comments">Export comments</string>
<string name="export_metadata">Export metadata</string>
<string name="mm">mm</string>
<string name="download_file_title">Sismics Docs file export</string>
<string name="download_document_title">Sismics Docs document export</string>
<string name="download_pdf_title">Sismics Docs PDF export</string>
<string name="download_file_title">Teedy file export</string>
<string name="download_document_title">Teedy document export</string>
<string name="download_pdf_title">Teedy PDF export</string>
<string name="latest_activity">Latest activity</string>
<string name="activity">Activity</string>
<string name="email">E-mail</string>

View File

@@ -1,12 +1,12 @@
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<style name="AppTheme" parent="Theme.AppCompat.DayNight">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.Light.DarkActionBar">
<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="colorPrimary">@color/colorPrimary</item>
@@ -14,7 +14,7 @@
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="AppThemeDark" parent="Theme.AppCompat.NoActionBar">
<style name="AppThemeDark" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>

View File

@@ -1,6 +1,6 @@
#Wed Jan 30 16:31:31 CET 2019
#Tue May 07 11:49:13 CEST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.sismics.docs</groupId>
<artifactId>docs-parent</artifactId>
<version>1.6-SNAPSHOT</version>
<version>1.7</version>
<relativePath>..</relativePath>
</parent>

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");
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");
/**
* Base URL environment variable.

View File

@@ -0,0 +1,14 @@
package com.sismics.docs.core.constant;
/**
* Metadata type.
*
* @author bgamard
*/
public enum MetadataType {
STRING,
INTEGER,
FLOAT,
DATE,
BOOLEAN
}

View File

@@ -61,6 +61,7 @@ public class AuditLogDao {
queries.add(baseQuery + " where l.LOG_IDENTITY_C in (select f.FIL_ID_C from T_FILE f where f.FIL_IDDOC_C = :documentId) ");
queries.add(baseQuery + " where l.LOG_IDENTITY_C in (select c.COM_ID_C from T_COMMENT c where c.COM_IDDOC_C = :documentId) ");
queries.add(baseQuery + " where l.LOG_IDENTITY_C in (select a.ACL_ID_C from T_ACL a where a.ACL_SOURCEID_C = :documentId) ");
queries.add(baseQuery + " where l.LOG_IDENTITY_C in (select r.RTE_ID_C from T_ROUTE r where r.RTE_IDDOCUMENT_C = :documentId) ");
parameterMap.put("documentId", criteria.getDocumentId());
}

View File

@@ -27,7 +27,6 @@ public class CommentDao {
* @param comment Comment
* @param userId User ID
* @return New ID
* @throws Exception
*/
public String create(Comment comment, String userId) {
// Create the UUID
@@ -99,7 +98,7 @@ public class CommentDao {
@SuppressWarnings("unchecked")
List<Object[]> l = q.getResultList();
List<CommentDto> commentDtoList = new ArrayList<CommentDto>();
List<CommentDto> commentDtoList = new ArrayList<>();
for (Object[] o : l) {
int i = 0;
CommentDto commentDto = new CommentDto();
@@ -107,7 +106,7 @@ public class CommentDao {
commentDto.setContent((String) o[i++]);
commentDto.setCreateTimestamp(((Timestamp) o[i++]).getTime());
commentDto.setCreatorName((String) o[i++]);
commentDto.setCreatorEmail((String) o[i++]);
commentDto.setCreatorEmail((String) o[i]);
commentDtoList.add(commentDto);
}
return commentDtoList;

View File

@@ -196,21 +196,6 @@ public class DocumentDao {
* @return Updated document
*/
public Document update(Document document, String userId) {
Document documentDb = updateSilently(document);
// Create audit log
AuditLogUtil.create(documentDb, AuditLogType.UPDATE, userId);
return documentDb;
}
/**
* Update a document without audit log.
*
* @param document Document to update
* @return Updated document
*/
public Document updateSilently(Document document) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the document
@@ -234,9 +219,27 @@ public class DocumentDao {
documentDb.setFileId(document.getFileId());
documentDb.setUpdateDate(new Date());
// Create audit log
AuditLogUtil.create(documentDb, AuditLogType.UPDATE, userId);
return documentDb;
}
/**
* Update the file ID on a document.
*
* @param document Document
*/
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.setParameter("updateDate", new Date());
query.setParameter("fileId", document.getFileId());
query.setParameter("id", document.getId());
query.executeUpdate();
}
/**
* Returns the number of documents.
*

View File

@@ -0,0 +1,89 @@
package com.sismics.docs.core.dao;
import com.sismics.docs.core.constant.MetadataType;
import com.sismics.docs.core.dao.dto.DocumentMetadataDto;
import com.sismics.docs.core.model.jpa.DocumentMetadata;
import com.sismics.util.context.ThreadLocalContext;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* Document metadata DAO.
*
* @author bgamard
*/
public class DocumentMetadataDao {
/**
* Creates a new document metadata.
*
* @param documentMetadata Document metadata
* @return New ID
*/
public String create(DocumentMetadata documentMetadata) {
// Create the UUID
documentMetadata.setId(UUID.randomUUID().toString());
// Create the document metadata
EntityManager em = ThreadLocalContext.get().getEntityManager();
em.persist(documentMetadata);
return documentMetadata.getId();
}
/**
* Updates a document metadata.
*
* @param documentMetadata Document metadata
* @return Updated document metadata
*/
public DocumentMetadata update(DocumentMetadata documentMetadata) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the document metadata
Query q = em.createQuery("select u from DocumentMetadata u where u.id = :id");
q.setParameter("id", documentMetadata.getId());
DocumentMetadata documentMetadataDb = (DocumentMetadata) q.getSingleResult();
// Update the document metadata
documentMetadataDb.setValue(documentMetadata.getValue());
return documentMetadata;
}
/**
* Returns the list of all metadata values on a document.
*
* @param documentId Document ID
* @return List of metadata
*/
@SuppressWarnings("unchecked")
public List<DocumentMetadataDto> getByDocumentId(String documentId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
StringBuilder sb = new StringBuilder("select dm.DME_ID_C, dm.DME_IDDOCUMENT_C, dm.DME_IDMETADATA_C, dm.DME_VALUE_C, m.MET_TYPE_C");
sb.append(" from T_DOCUMENT_METADATA dm, T_METADATA m ");
sb.append(" where dm.DME_IDMETADATA_C = m.MET_ID_C and dm.DME_IDDOCUMENT_C = :documentId and m.MET_DELETEDATE_D is null");
// Perform the search
Query q = em.createNativeQuery(sb.toString());
q.setParameter("documentId", documentId);
List<Object[]> l = q.getResultList();
// Assemble results
List<DocumentMetadataDto> dtoList = new ArrayList<>();
for (Object[] o : l) {
int i = 0;
DocumentMetadataDto dto = new DocumentMetadataDto();
dto.setId((String) o[i++]);
dto.setDocumentId((String) o[i++]);
dto.setMetadataId((String) o[i++]);
dto.setValue((String) o[i++]);
dto.setType(MetadataType.valueOf((String) o[i]));
dtoList.add(dto);
}
return dtoList;
}
}

View File

@@ -0,0 +1,148 @@
package com.sismics.docs.core.dao;
import com.google.common.base.Joiner;
import com.sismics.docs.core.constant.AuditLogType;
import com.sismics.docs.core.constant.MetadataType;
import com.sismics.docs.core.dao.criteria.MetadataCriteria;
import com.sismics.docs.core.dao.dto.MetadataDto;
import com.sismics.docs.core.model.jpa.Metadata;
import com.sismics.docs.core.util.AuditLogUtil;
import com.sismics.docs.core.util.jpa.QueryParam;
import com.sismics.docs.core.util.jpa.QueryUtil;
import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.util.context.ThreadLocalContext;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import java.util.*;
/**
* Metadata DAO.
*
* @author bgamard
*/
public class MetadataDao {
/**
* Creates a new metdata.
*
* @param metadata Metadata
* @param userId User ID
* @return New ID
*/
public String create(Metadata metadata, String userId) {
// Create the UUID
metadata.setId(UUID.randomUUID().toString());
// Create the metadata
EntityManager em = ThreadLocalContext.get().getEntityManager();
em.persist(metadata);
// Create audit log
AuditLogUtil.create(metadata, AuditLogType.CREATE, userId);
return metadata.getId();
}
/**
* Update a metadata.
*
* @param metadata Metadata to update
* @param userId User ID
* @return Updated metadata
*/
public Metadata update(Metadata metadata, String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the metadata
Query q = em.createQuery("select r from Metadata r where r.id = :id and r.deleteDate is null");
q.setParameter("id", metadata.getId());
Metadata metadataDb = (Metadata) q.getSingleResult();
// Update the metadata
metadataDb.setName(metadata.getName());
// Create audit log
AuditLogUtil.create(metadataDb, AuditLogType.UPDATE, userId);
return metadataDb;
}
/**
* Gets an active metadata by its ID.
*
* @param id Metadata ID
* @return Metadata
*/
public Metadata getActiveById(String id) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
try {
Query q = em.createQuery("select r from Metadata r where r.id = :id and r.deleteDate is null");
q.setParameter("id", id);
return (Metadata) q.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
/**
* Deletes a metadata.
*
* @param id Metadata ID
* @param userId User ID
*/
public void delete(String id, String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the metadata
Query q = em.createQuery("select r from Metadata r where r.id = :id and r.deleteDate is null");
q.setParameter("id", id);
Metadata metadataDb = (Metadata) q.getSingleResult();
// Delete the metadata
Date dateNow = new Date();
metadataDb.setDeleteDate(dateNow);
// Create audit log
AuditLogUtil.create(metadataDb, AuditLogType.DELETE, userId);
}
/**
* Returns the list of all metadata.
*
* @param criteria Search criteria
* @param sortCriteria Sort criteria
* @return List of metadata
*/
public List<MetadataDto> findByCriteria(MetadataCriteria criteria, SortCriteria sortCriteria) {
Map<String, Object> parameterMap = new HashMap<>();
List<String> criteriaList = new ArrayList<>();
StringBuilder sb = new StringBuilder("select m.MET_ID_C c0, m.MET_NAME_C c1, m.MET_TYPE_C c2");
sb.append(" from T_METADATA m ");
criteriaList.add("m.MET_DELETEDATE_D is null");
if (!criteriaList.isEmpty()) {
sb.append(" where ");
sb.append(Joiner.on(" and ").join(criteriaList));
}
// Perform the search
QueryParam queryParam = QueryUtil.getSortedQueryParam(new QueryParam(sb.toString(), parameterMap), sortCriteria);
@SuppressWarnings("unchecked")
List<Object[]> l = QueryUtil.getNativeQuery(queryParam).getResultList();
// Assemble results
List<MetadataDto> dtoList = new ArrayList<>();
for (Object[] o : l) {
int i = 0;
MetadataDto dto = new MetadataDto();
dto.setId((String) o[i++]);
dto.setName((String) o[i++]);
dto.setType(MetadataType.valueOf((String) o[i]));
dtoList.add(dto);
}
return dtoList;
}
}

View File

@@ -36,13 +36,13 @@ public class RelationDao {
List<Object[]> l = q.getResultList();
// Assemble results
List<RelationDto> relationDtoList = new ArrayList<RelationDto>();
List<RelationDto> relationDtoList = new ArrayList<>();
for (Object[] o : l) {
int i = 0;
RelationDto relationDto = new RelationDto();
relationDto.setId((String) o[i++]);
relationDto.setTitle((String) o[i++]);
String fromDocId = (String) o[i++];
String fromDocId = (String) o[i];
relationDto.setSource(documentId.equals(fromDocId));
relationDtoList.add(relationDto);
}

View File

@@ -91,10 +91,15 @@ public class RouteDao {
* Deletes a route and the associated steps.
*
* @param routeId Route ID
* @param userId User ID
*/
public void deleteRoute(String routeId) {
public void deleteRoute(String routeId, String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Create audit log
Route route = em.find(Route.class, routeId);
AuditLogUtil.create(route, AuditLogType.DELETE, userId);
em.createNativeQuery("update T_ROUTE_STEP rs set RTP_DELETEDATE_D = :dateNow where rs.RTP_IDROUTE_C = :routeId and rs.RTP_DELETEDATE_D is null")
.setParameter("routeId", routeId)
.setParameter("dateNow", new Date())

View File

@@ -61,7 +61,7 @@ public class RouteModelDao {
q.setParameter("id", routeModel.getId());
RouteModel routeModelDb = (RouteModel) q.getSingleResult();
// Update the group
// Update the route model
routeModelDb.setName(routeModel.getName());
routeModelDb.setSteps(routeModel.getSteps());
@@ -88,6 +88,18 @@ public class RouteModelDao {
}
}
/**
* Returns the list of all route models.
*
* @return List of route models
*/
@SuppressWarnings("unchecked")
public List<RouteModel> findAll() {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select r from RouteModel r where r.deleteDate is null");
return q.getResultList();
}
/**
* Deletes a route model.
*

View File

@@ -171,6 +171,26 @@ public class UserDao {
return user;
}
/**
* Update the onboarding status.
*
* @param user User to update
* @return Updated user
*/
public User updateOnboarding(User user) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the user
Query q = em.createQuery("select u from User u where u.id = :id and u.deleteDate is null");
q.setParameter("id", user.getId());
User userDb = (User) q.getSingleResult();
// Update the user
userDb.setOnboarding(user.isOnboarding());
return user;
}
/**
* Gets a user by its ID.
*

View File

@@ -51,6 +51,12 @@ public class DocumentCriteria {
*/
private List<List<String>> tagIdList;
/**
* Tag IDs to excluded.
* The first and second level list will be excluded.
*/
private List<List<String>> excludedTagIdList;
/**
* Shared status.
*/
@@ -119,6 +125,15 @@ public class DocumentCriteria {
this.tagIdList = tagIdList;
}
public List<List<String>> getExcludedTagIdList() {
return excludedTagIdList;
}
public DocumentCriteria setExcludedTagIdList(List<List<String>> excludedTagIdList) {
this.excludedTagIdList = excludedTagIdList;
return this;
}
public Boolean getShared() {
return shared;
}

View File

@@ -0,0 +1,9 @@
package com.sismics.docs.core.dao.criteria;
/**
* Metadata criteria.
*
* @author bgamard
*/
public class MetadataCriteria {
}

View File

@@ -0,0 +1,94 @@
package com.sismics.docs.core.dao.dto;
import com.sismics.docs.core.constant.MetadataType;
/**
* Document metadata DTO.
*
* @author bgamard
*/
public class DocumentMetadataDto {
/**
* Document metadata ID.
*/
private String id;
/**
* Document ID.
*/
private String documentId;
/**
* Metadata ID.
*/
private String metadataId;
/**
* Name.
*/
private String name;
/**
* Value.
*/
private String value;
/**
* Type.
*/
private MetadataType type;
public String getId() {
return id;
}
public DocumentMetadataDto setId(String id) {
this.id = id;
return this;
}
public String getName() {
return name;
}
public DocumentMetadataDto setName(String name) {
this.name = name;
return this;
}
public MetadataType getType() {
return type;
}
public DocumentMetadataDto setType(MetadataType type) {
this.type = type;
return this;
}
public String getDocumentId() {
return documentId;
}
public DocumentMetadataDto setDocumentId(String documentId) {
this.documentId = documentId;
return this;
}
public String getMetadataId() {
return metadataId;
}
public DocumentMetadataDto setMetadataId(String metadataId) {
this.metadataId = metadataId;
return this;
}
public String getValue() {
return value;
}
public DocumentMetadataDto setValue(String value) {
this.value = value;
return this;
}
}

View File

@@ -0,0 +1,52 @@
package com.sismics.docs.core.dao.dto;
import com.sismics.docs.core.constant.MetadataType;
/**
* Metadata DTO.
*
* @author bgamard
*/
public class MetadataDto {
/**
* Metadata ID.
*/
private String id;
/**
* Name.
*/
private String name;
/**
* Type.
*/
private MetadataType type;
public String getId() {
return id;
}
public MetadataDto setId(String id) {
this.id = id;
return this;
}
public String getName() {
return name;
}
public MetadataDto setName(String name) {
this.name = name;
return this;
}
public MetadataType getType() {
return type;
}
public MetadataDto setType(MetadataType type) {
this.type = type;
return this;
}
}

View File

@@ -58,7 +58,7 @@ public class DocumentUpdatedAsyncListener {
}
// Update database and index
documentDao.updateSilently(document);
documentDao.updateFileId(document);
AppContext.getInstance().getIndexingHandler().updateDocument(document);
// Update contributors list

View File

@@ -96,7 +96,7 @@ public class FileProcessingAsyncListener {
final File file = event.getFile();
FormatHandler formatHandler = FormatHandlerUtil.find(file.getMimeType());
if (formatHandler == null) {
log.error("Format unhandled: " + file.getMimeType());
log.info("Format unhandled: " + file.getMimeType());
FileUtil.endProcessingFile(file.getId());
return;
}

View File

@@ -0,0 +1,91 @@
package com.sismics.docs.core.model.jpa;
import com.google.common.base.MoreObjects;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
/**
* Link between a document and a metadata, holding the value.
*
* @author bgamard
*/
@Entity
@Table(name = "T_DOCUMENT_METADATA")
public class DocumentMetadata implements Serializable {
/**
* Serial version UID.
*/
private static final long serialVersionUID = 1L;
/**
* Document metadata ID.
*/
@Id
@Column(name = "DME_ID_C", length = 36)
private String id;
/**
* Document ID.
*/
@Column(name = "DME_IDDOCUMENT_C", nullable = false, length = 36)
private String documentId;
/**
* Metadata ID.
*/
@Column(name = "DME_IDMETADATA_C", nullable = false, length = 36)
private String metadataId;
/**
* Value.
*/
@Column(name = "DME_VALUE_C", length = 4000)
private String value;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getDocumentId() {
return documentId;
}
public void setDocumentId(String documentId) {
this.documentId = documentId;
}
public String getMetadataId() {
return metadataId;
}
public DocumentMetadata setMetadataId(String metadataId) {
this.metadataId = metadataId;
return this;
}
public String getValue() {
return value;
}
public DocumentMetadata setValue(String value) {
this.value = value;
return this;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("id", id)
.add("documentId", documentId)
.add("metadataId", metadataId)
.toString();
}
}

View File

@@ -0,0 +1,92 @@
package com.sismics.docs.core.model.jpa;
import com.google.common.base.MoreObjects;
import com.sismics.docs.core.constant.MetadataType;
import javax.persistence.*;
import java.util.Date;
/**
* Metadata entity.
*
* @author bgamard
*/
@Entity
@Table(name = "T_METADATA")
public class Metadata implements Loggable {
/**
* Metadata ID.
*/
@Id
@Column(name = "MET_ID_C", length = 36)
private String id;
/**
* Name.
*/
@Column(name = "MET_NAME_C", length = 50, nullable = false)
private String name;
/**
* Type.
*/
@Column(name = "MET_TYPE_C", length = 20, nullable = false)
@Enumerated(EnumType.STRING)
private MetadataType type;
/**
* Deletion date.
*/
@Column(name = "MET_DELETEDATE_D")
private Date deleteDate;
public String getId() {
return id;
}
public Metadata setId(String id) {
this.id = id;
return this;
}
public String getName() {
return name;
}
public Metadata setName(String name) {
this.name = name;
return this;
}
public MetadataType getType() {
return type;
}
public Metadata setType(MetadataType type) {
this.type = type;
return this;
}
@Override
public Date getDeleteDate() {
return deleteDate;
}
public void setDeleteDate(Date deleteDate) {
this.deleteDate = deleteDate;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("id", id)
.add("name", name)
.add("type", type)
.toString();
}
@Override
public String toMessage() {
return name;
}
}

View File

@@ -47,6 +47,12 @@ public class User implements Loggable {
@Column(name = "USE_PRIVATEKEY_C", nullable = false, length = 100)
private String privateKey;
/**
* False when the user passed the onboarding.
*/
@Column(name = "USE_ONBOARDING_B", nullable = false)
private boolean onboarding;
/**
* TOTP secret key.
*/
@@ -198,6 +204,15 @@ public class User implements Loggable {
return this;
}
public boolean isOnboarding() {
return onboarding;
}
public User setOnboarding(boolean onboarding) {
this.onboarding = onboarding;
return this;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)

View File

@@ -22,9 +22,9 @@ public class DirectoryUtil {
*/
public static Path getBaseDataDirectory() {
Path baseDataDir = null;
if (StringUtils.isNotBlank(EnvironmentUtil.getDocsHome())) {
if (StringUtils.isNotBlank(EnvironmentUtil.getTeedyHome())) {
// If the docs.home property is set then use it
baseDataDir = Paths.get(EnvironmentUtil.getDocsHome());
baseDataDir = Paths.get(EnvironmentUtil.getTeedyHome());
} else if (EnvironmentUtil.isUnitTest()) {
// For unit testing, use a temporary directory
baseDataDir = Paths.get(System.getProperty("java.io.tmpdir"));

View File

@@ -32,6 +32,7 @@ public class EncryptionUtil {
static {
// Initialize Bouncy Castle provider
Security.insertProviderAt(new BouncyCastleProvider(), 1);
Security.removeProvider("SunRsaSign");
}
/**

View File

@@ -0,0 +1,196 @@
package com.sismics.docs.core.util;
import com.google.common.collect.Maps;
import com.sismics.docs.core.constant.MetadataType;
import com.sismics.docs.core.dao.DocumentMetadataDao;
import com.sismics.docs.core.dao.MetadataDao;
import com.sismics.docs.core.dao.criteria.MetadataCriteria;
import com.sismics.docs.core.dao.dto.DocumentMetadataDto;
import com.sismics.docs.core.dao.dto.MetadataDto;
import com.sismics.docs.core.model.jpa.DocumentMetadata;
import com.sismics.docs.core.util.jpa.SortCriteria;
import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObjectBuilder;
import java.text.MessageFormat;
import java.util.List;
import java.util.Map;
/**
* Metadata utilities.
*
* @author bgamard
*/
public class MetadataUtil {
/**
* Update custom metadata on a document.
*
* @param documentId Document ID
* @param metadataIdList Metadata ID list
* @param metadataValueList Metadata value list
*/
public static void updateMetadata(String documentId, List<String> metadataIdList, List<String> metadataValueList) throws Exception {
if (metadataIdList == null || metadataValueList == null || metadataIdList.isEmpty()) {
return;
}
if (metadataIdList.size() != metadataValueList.size()) {
throw new Exception("metadata_id and metadata_value must have the same length");
}
Map<String, String> newValues = Maps.newHashMap();
for (int i = 0; i < metadataIdList.size(); i++) {
newValues.put(metadataIdList.get(i), metadataValueList.get(i));
}
MetadataDao metadataDao = new MetadataDao();
DocumentMetadataDao documentMetadataDao = new DocumentMetadataDao();
List<MetadataDto> metadataDtoList = metadataDao.findByCriteria(new MetadataCriteria(), null);
List<DocumentMetadataDto> documentMetadataDtoList = documentMetadataDao.getByDocumentId(documentId);
// Update existing values
for (DocumentMetadataDto documentMetadataDto : documentMetadataDtoList) {
if (newValues.containsKey(documentMetadataDto.getMetadataId())) {
// Update the value
String value = newValues.get(documentMetadataDto.getMetadataId());
validateValue(documentMetadataDto.getType(), value);
updateValue(documentMetadataDto.getId(), value);
newValues.remove(documentMetadataDto.getMetadataId());
} else {
// Remove the value
updateValue(documentMetadataDto.getId(), null);
}
}
// Create new values
for (Map.Entry<String, String> entry : newValues.entrySet()) {
// Search the metadata definition
MetadataDto metadata = null;
for (MetadataDto metadataDto : metadataDtoList) {
if (metadataDto.getId().equals(entry.getKey())) {
metadata = metadataDto;
break;
}
}
if (metadata == null) {
throw new Exception(MessageFormat.format("Metadata not found: {0}", entry.getKey()));
}
// Add the value
validateValue(metadata.getType(), entry.getValue());
createValue(documentId, entry.getKey(), entry.getValue());
}
}
/**
* Validate a custom metadata value.
*
* @param type Metadata type
* @param value Value
* @throws Exception In case of validation error
*/
private static void validateValue(MetadataType type, String value) throws Exception {
switch (type) {
case STRING:
case BOOLEAN:
return;
case DATE:
try {
Long.parseLong(value);
} catch (NumberFormatException e) {
throw new Exception("Date value not parsable as timestamp");
}
break;
case FLOAT:
try {
Double.parseDouble(value);
} catch (NumberFormatException e) {
throw new Exception("Float value not parsable");
}
break;
case INTEGER:
try {
Integer.parseInt(value);
} catch (NumberFormatException e) {
throw new Exception("Integer value not parsable");
}
break;
}
}
/**
* Create a custom metadata value on a document.
*
* @param documentId Document ID
* @param metadataId Metadata ID
* @param value Value
*/
private static void createValue(String documentId, String metadataId, String value) {
DocumentMetadataDao documentMetadataDao = new DocumentMetadataDao();
DocumentMetadata documentMetadata = new DocumentMetadata();
documentMetadata.setDocumentId(documentId);
documentMetadata.setMetadataId(metadataId);
documentMetadata.setValue(value);
documentMetadataDao.create(documentMetadata);
}
/**
* Update a custom metadata value.
*
* @param documentMetadataId Document metadata ID
* @param value Value
*/
private static void updateValue(String documentMetadataId, String value) {
DocumentMetadataDao documentMetadataDao = new DocumentMetadataDao();
DocumentMetadata documentMetadata = new DocumentMetadata();
documentMetadata.setId(documentMetadataId);
documentMetadata.setValue(value);
documentMetadataDao.update(documentMetadata);
}
/**
* Add custom metadata to a JSON response.
*
* @param json JSON
* @param documentId Document ID
*/
public static void addMetadata(JsonObjectBuilder json, String documentId) {
DocumentMetadataDao documentMetadataDao = new DocumentMetadataDao();
MetadataDao metadataDao = new MetadataDao();
List<MetadataDto> metadataDtoList = metadataDao.findByCriteria(new MetadataCriteria(), new SortCriteria(1, true));
List<DocumentMetadataDto> documentMetadataDtoList = documentMetadataDao.getByDocumentId(documentId);
JsonArrayBuilder metadata = Json.createArrayBuilder();
for (MetadataDto metadataDto : metadataDtoList) {
JsonObjectBuilder meta = Json.createObjectBuilder()
.add("id", metadataDto.getId())
.add("name", metadataDto.getName())
.add("type", metadataDto.getType().name());
for (DocumentMetadataDto documentMetadataDto : documentMetadataDtoList) {
if (documentMetadataDto.getMetadataId().equals(metadataDto.getId())) {
if (documentMetadataDto.getValue() != null) {
switch (metadataDto.getType()) {
case STRING:
meta.add("value", documentMetadataDto.getValue());
break;
case BOOLEAN:
meta.add("value", Boolean.parseBoolean(documentMetadataDto.getValue()));
break;
case DATE:
meta.add("value", Long.parseLong(documentMetadataDto.getValue()));
break;
case FLOAT:
meta.add("value", Double.parseDouble(documentMetadataDto.getValue()));
break;
case INTEGER:
meta.add("value", Integer.parseInt(documentMetadataDto.getValue()));
break;
}
}
}
}
metadata.add(meta);
}
json.add("metadata", metadata);
}
}

View File

@@ -6,6 +6,7 @@ import com.sismics.docs.core.constant.AclType;
import com.sismics.docs.core.constant.PermType;
import com.sismics.docs.core.dao.AclDao;
import com.sismics.docs.core.dao.DocumentDao;
import com.sismics.docs.core.dao.RouteModelDao;
import com.sismics.docs.core.dao.UserDao;
import com.sismics.docs.core.dao.criteria.UserCriteria;
import com.sismics.docs.core.dao.dto.RouteStepDto;
@@ -15,8 +16,14 @@ import com.sismics.docs.core.event.RouteStepValidateEvent;
import com.sismics.docs.core.model.context.AppContext;
import com.sismics.docs.core.model.jpa.Acl;
import com.sismics.docs.core.model.jpa.Document;
import com.sismics.docs.core.model.jpa.RouteModel;
import com.sismics.util.context.ThreadLocalContext;
import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonObject;
import javax.json.JsonReader;
import java.io.StringReader;
import java.util.List;
/**
@@ -87,4 +94,31 @@ public class RoutingUtil {
AppContext.getInstance().getMailEventBus().post(routeStepValidateEvent);
}
}
/**
* Find the first route model name matching a target type and name.
*
* @param targetType Target type
* @param targetName Target name
* @return Route model name or null if none is matching
*/
public static String findRouteModelNameByTargetName(AclTargetType targetType, String targetName) {
RouteModelDao routeModelDao = new RouteModelDao();
List<RouteModel> routeModelList = routeModelDao.findAll();
for (RouteModel routeModel : routeModelList) {
try (JsonReader reader = Json.createReader(new StringReader(routeModel.getSteps()))) {
JsonArray stepsJson = reader.readArray();
for (int order = 0; order < stepsJson.size(); order++) {
JsonObject step = stepsJson.getJsonObject(order);
JsonObject target = step.getJsonObject("target");
AclTargetType routeTargetType = AclTargetType.valueOf(target.getString("type"));
String routeTargetName = target.getString("name");
if (targetType == routeTargetType && targetName.equals(routeTargetName)) {
return routeModel.getName();
}
}
}
}
return null;
}
}

View File

@@ -309,6 +309,19 @@ public class LuceneIndexingHandler implements IndexingHandler {
criteriaList.add("(" + Joiner.on(" OR ").join(tagCriteriaList) + ")");
}
}
if (criteria.getExcludedTagIdList() != null && !criteria.getExcludedTagIdList().isEmpty()) {
int index = 0;
for (List<String> tagIdList : criteria.getExcludedTagIdList()) {
List<String> tagCriteriaList = Lists.newArrayList();
for (String tagId : tagIdList) {
sb.append(String.format("left join T_DOCUMENT_TAG dtex%d on dtex%d.DOT_IDDOCUMENT_C = d.DOC_ID_C and dtex%d.DOT_IDTAG_C = :tagIdEx%d and dtex%d.DOT_DELETEDATE_D is null ", index, index, index, index, index));
parameterMap.put("tagIdEx" + index, tagId);
tagCriteriaList.add(String.format("dtex%d.DOT_ID_C is null", index));
index++;
}
criteriaList.add("(" + Joiner.on(" AND ").join(tagCriteriaList) + ")");
}
}
if (criteria.getShared() != null && criteria.getShared()) {
criteriaList.add("s.count > 0");
}

View File

@@ -124,11 +124,11 @@ public class EmailUtil {
// Application name
Config themeConfig = configDao.getById(ConfigType.THEME);
String appName = "Sismics Docs";
String appName = "Teedy";
if (themeConfig != null) {
try (JsonReader reader = Json.createReader(new StringReader(themeConfig.getValue()))) {
JsonObject themeJson = reader.readObject();
appName = themeJson.getString("name", "Sismics Docs");
appName = themeJson.getString("name", "Teedy");
}
}

View File

@@ -15,7 +15,7 @@ public class EnvironmentUtil {
private static String MAC_OS_USER_HOME = System.getProperty("user.home");
private static String DOCS_HOME = System.getProperty("docs.home");
private static String TEEDY_HOME = System.getProperty("docs.home");
/**
* In a web application context.
@@ -90,8 +90,8 @@ public class EnvironmentUtil {
*
* @return Home directory
*/
public static String getDocsHome() {
return DOCS_HOME;
public static String getTeedyHome() {
return TEEDY_HOME;
}
/**

View File

@@ -48,8 +48,11 @@ public class DialectUtil {
sql = sql.replaceAll("(cached|memory) table", "table");
sql = sql.replaceAll("datetime", "timestamp");
sql = sql.replaceAll("longvarchar", "text");
sql = sql.replaceAll("bit not null", "bool not null");
sql = sql.replaceAll("bit default 1", "bool default true");
sql = sql.replaceAll("bit default 0", "bool default false");
sql = sql.replaceAll("bit not null default 1", "bool not null default true");
sql = sql.replaceAll("bit not null default 0", "bool not null default false");
sql = sql.replaceAll("bit not null", "bool not null");
return sql;
}
}

View File

@@ -88,7 +88,7 @@ public final class EMF {
if (databaseUrl == null) {
props.put("hibernate.connection.driver_class", "org.h2.Driver");
props.put("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
props.put("hibernate.connection.url", "jdbc:h2:file:" + dbFile + ";CACHE_SIZE=65536");
props.put("hibernate.connection.url", "jdbc:h2:file:" + dbFile + ";CACHE_SIZE=65536;LOCK_TIMEOUT=10000");
props.put("hibernate.connection.username", "sa");
} else {
props.put("hibernate.connection.driver_class", "org.postgresql.Driver");

View File

@@ -1 +1 @@
db.version=22
db.version=24

View File

@@ -0,0 +1,2 @@
alter table T_USER add column USE_ONBOARDING_B bit not null default 1;
update T_CONFIG set CFG_VALUE_C = '23' where CFG_ID_C = 'DB_VERSION';

View File

@@ -0,0 +1,5 @@
create cached table T_METADATA ( MET_ID_C varchar(36) not null, MET_NAME_C varchar(50) not null, MET_TYPE_C varchar(20) not null, MET_DELETEDATE_D datetime, primary key (MET_ID_C) );
create cached table T_DOCUMENT_METADATA ( DME_ID_C varchar(36) not null, DME_IDDOCUMENT_C varchar(36) not null, DME_IDMETADATA_C varchar(36) not null, DME_VALUE_C varchar(4000) null, primary key (DME_ID_C) );
alter table T_DOCUMENT_METADATA add constraint FK_DME_IDDOCUMENT_C foreign key (DME_IDDOCUMENT_C) references T_DOCUMENT (DOC_ID_C) on delete restrict on update restrict;
alter table T_DOCUMENT_METADATA add constraint FK_DME_IDMETADATA_C foreign key (DME_IDMETADATA_C) references T_METADATA (MET_ID_C) on delete restrict on update restrict;
update T_CONFIG set CFG_VALUE_C = '24' where CFG_ID_C = 'DB_VERSION';

View File

@@ -22,9 +22,9 @@ const prefs = new preferences('com.sismics.docs.importer',{
});
// Welcome message
console.log('Sismics Docs Importer 1.0.0, https://www.sismicsdocs.com' +
console.log('Teedy Importer 1.0.0, https://teedy.io' +
'\n\n' +
'This program let you import files from your system to Sismics Docs' +
'This program let you import files from your system to Teedy' +
'\n');
// Ask for the base URL
@@ -33,7 +33,7 @@ const askBaseUrl = () => {
{
type: 'input',
name: 'baseUrl',
message: 'What is the base URL of your Docs? (eg. https://docs.mycompany.com)',
message: 'What is the base URL of your Teedy? (eg. https://teedy.mycompany.com)',
default: prefs.importer.baseUrl
}
]).then(answers => {
@@ -42,12 +42,12 @@ const askBaseUrl = () => {
// Test base URL
const spinner = ora({
text: 'Checking connection to Docs',
text: 'Checking connection to Teedy',
spinner: 'flips'
}).start();
request(answers.baseUrl + '/api/app', function (error, response) {
if (!response || response.statusCode !== 200) {
spinner.fail('Connection to Docs failed: ' + error);
spinner.fail('Connection to Teedy failed: ' + error);
askBaseUrl();
return;
}
@@ -82,7 +82,7 @@ const askCredentials = () => {
// Test credentials
const spinner = ora({
text: 'Checking connection to Docs',
text: 'Checking connection to Teedy',
spinner: 'flips'
}).start();
request.post({

View File

@@ -1,7 +1,7 @@
{
"name": "docs-importer",
"name": "teedy-importer",
"version": "1.5.1",
"description": "Import files to Sismics Docs",
"description": "Import files to Teedy",
"bin": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.sismics.docs</groupId>
<artifactId>docs-parent</artifactId>
<version>1.6-SNAPSHOT</version>
<version>1.7</version>
<relativePath>..</relativePath>
</parent>

View File

@@ -26,7 +26,7 @@ import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
/**
* Stress app for Sismics Docs.
* Stress app for Teedy.
*
* @author bgamard
*/

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.sismics.docs</groupId>
<artifactId>docs-parent</artifactId>
<version>1.6-SNAPSHOT</version>
<version>1.7</version>
<relativePath>..</relativePath>
</parent>

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.sismics.docs</groupId>
<artifactId>docs-parent</artifactId>
<version>1.6-SNAPSHOT</version>
<version>1.7</version>
<relativePath>..</relativePath>
</parent>

View File

@@ -1,3 +1,3 @@
api.current_version=${project.version}
api.min_version=1.0
db.version=22
db.version=24

View File

@@ -228,18 +228,24 @@ public class AclResource extends BaseResource {
SortCriteria sortCriteria = new SortCriteria(1, true);
List<UserDto> userDtoList = userDao.findByCriteria(new UserCriteria().setSearch(search), sortCriteria);
for (UserDto userDto : userDtoList) {
// No need to add users who will skip ACL check anyways
if (!SecurityUtil.skipAclCheck(Lists.newArrayList(userDto.getId()))) {
users.add(Json.createObjectBuilder()
.add("name", userDto.getUsername()));
}
}
// Search groups
GroupDao groupDao = new GroupDao();
JsonArrayBuilder groups = Json.createArrayBuilder();
List<GroupDto> groupDtoList = groupDao.findByCriteria(new GroupCriteria().setSearch(search), sortCriteria);
for (GroupDto groupDto : groupDtoList) {
// No need to add users who will skip ACL check anyways
if (!SecurityUtil.skipAclCheck(Lists.newArrayList(groupDto.getId()))) {
groups.add(Json.createObjectBuilder()
.add("name", groupDto.getName()));
}
}
JsonObjectBuilder response = Json.createObjectBuilder()
.add("users", users)

View File

@@ -235,6 +235,9 @@ public class DocumentResource extends BaseResource {
document.add("route_step", step);
}
// Add custom metadata
MetadataUtil.addMetadata(document, documentId);
return Response.ok().entity(document.build()).build();
}
@@ -459,11 +462,15 @@ public class DocumentResource extends BaseResource {
switch (params[0]) {
case "tag":
case "!tag":
// New tag criteria
List<TagDto> tagDtoList = TagUtil.findByName(params[1], allTagDtoList);
if (documentCriteria.getTagIdList() == null) {
documentCriteria.setTagIdList(new ArrayList<>());
}
if (documentCriteria.getExcludedTagIdList() == null) {
documentCriteria.setExcludedTagIdList(new ArrayList<>());
}
if (tagDtoList.isEmpty()) {
// No tag found, the request must returns nothing
documentCriteria.getTagIdList().add(Lists.newArrayList(UUID.randomUUID().toString()));
@@ -476,8 +483,12 @@ public class DocumentResource extends BaseResource {
tagIdList.add(childrenTagDto.getId());
}
}
if (params[0].startsWith("!")) {
documentCriteria.getExcludedTagIdList().add(tagIdList);
} else {
documentCriteria.getTagIdList().add(tagIdList);
}
}
break;
case "after":
case "before":
@@ -606,6 +617,8 @@ public class DocumentResource extends BaseResource {
* @apiParam {String} [rights] Rights
* @apiParam {String[]} [tags] List of tags ID
* @apiParam {String[]} [relations] List of related documents ID
* @apiParam {String[]} [metadata_id] List of metadata ID
* @apiParam {String[]} [metadata_value] List of metadata values
* @apiParam {String} language Language
* @apiParam {Number} [create_date] Create date (timestamp)
* @apiSuccess {String} id Document ID
@@ -626,6 +639,8 @@ public class DocumentResource extends BaseResource {
* @param rights Rights
* @param tagList Tags
* @param relationList Relations
* @param metadataIdList Metadata ID list
* @param metadataValueList Metadata value list
* @param language Language
* @param createDateStr Creation date
* @return Response
@@ -644,6 +659,8 @@ public class DocumentResource extends BaseResource {
@FormParam("rights") String rights,
@FormParam("tags") List<String> tagList,
@FormParam("relations") List<String> relationList,
@FormParam("metadata_id") List<String> metadataIdList,
@FormParam("metadata_value") List<String> metadataValueList,
@FormParam("language") String language,
@FormParam("create_date") String createDateStr) {
if (!authenticate()) {
@@ -696,6 +713,13 @@ public class DocumentResource extends BaseResource {
// Update relations
updateRelationList(document.getId(), relationList);
// Update custom metadata
try {
MetadataUtil.updateMetadata(document.getId(), metadataIdList, metadataValueList);
} catch (Exception e) {
throw new ClientException("ValidationError", e.getMessage());
}
// Raise a document created event
DocumentCreatedAsyncEvent documentCreatedAsyncEvent = new DocumentCreatedAsyncEvent();
documentCreatedAsyncEvent.setUserId(principal.getId());
@@ -726,6 +750,8 @@ public class DocumentResource extends BaseResource {
* @apiParam {String} [rights] Rights
* @apiParam {String[]} [tags] List of tags ID
* @apiParam {String[]} [relations] List of related documents ID
* @apiParam {String[]} [metadata_id] List of metadata ID
* @apiParam {String[]} [metadata_value] List of metadata values
* @apiParam {String} language Language
* @apiParam {Number} [create_date] Create date (timestamp)
* @apiSuccess {String} id Document ID
@@ -755,6 +781,8 @@ public class DocumentResource extends BaseResource {
@FormParam("rights") String rights,
@FormParam("tags") List<String> tagList,
@FormParam("relations") List<String> relationList,
@FormParam("metadata_id") List<String> metadataIdList,
@FormParam("metadata_value") List<String> metadataValueList,
@FormParam("language") String language,
@FormParam("create_date") String createDateStr) {
if (!authenticate()) {
@@ -817,6 +845,13 @@ public class DocumentResource extends BaseResource {
// Update relations
updateRelationList(id, relationList);
// Update custom metadata
try {
MetadataUtil.updateMetadata(document.getId(), metadataIdList, metadataValueList);
} catch (Exception e) {
throw new ClientException("ValidationError", e.getMessage());
}
// Raise a document updated event
DocumentUpdatedAsyncEvent documentUpdatedAsyncEvent = new DocumentUpdatedAsyncEvent();
documentUpdatedAsyncEvent.setUserId(principal.getId());
@@ -961,14 +996,29 @@ public class DocumentResource extends BaseResource {
// Delete the document
documentDao.delete(id, principal.getId());
// Raise file deleted events (don't bother sending document updated event)
long totalSize = 0L;
for (File file : fileList) {
// Store the file size to update the quota
java.nio.file.Path storedFile = DirectoryUtil.getStorageDirectory().resolve(file.getId());
try {
totalSize += Files.size(storedFile);
} catch (IOException e) {
// The file doesn't exists on disk, which is weird, but not fatal
}
// Raise file deleted event
FileDeletedAsyncEvent fileDeletedAsyncEvent = new FileDeletedAsyncEvent();
fileDeletedAsyncEvent.setUserId(principal.getId());
fileDeletedAsyncEvent.setFile(file);
ThreadLocalContext.get().addAsyncEvent(fileDeletedAsyncEvent);
}
// Update the user quota
UserDao userDao = new UserDao();
User user = userDao.getById(principal.getId());
user.setStorageCurrent(user.getStorageCurrent() - totalSize);
userDao.updateQuota(user);
// Raise a document deleted event
DocumentDeletedAsyncEvent documentDeletedAsyncEvent = new DocumentDeletedAsyncEvent();
documentDeletedAsyncEvent.setUserId(principal.getId());

View File

@@ -2,6 +2,7 @@ package com.sismics.docs.rest.resource;
import com.google.common.base.Strings;
import com.google.common.collect.Sets;
import com.sismics.docs.core.constant.AclTargetType;
import com.sismics.docs.core.dao.GroupDao;
import com.sismics.docs.core.dao.RoleBaseFunctionDao;
import com.sismics.docs.core.dao.UserDao;
@@ -12,6 +13,7 @@ import com.sismics.docs.core.dao.dto.UserDto;
import com.sismics.docs.core.model.jpa.Group;
import com.sismics.docs.core.model.jpa.User;
import com.sismics.docs.core.model.jpa.UserGroup;
import com.sismics.docs.core.util.RoutingUtil;
import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.docs.rest.constant.BaseFunction;
import com.sismics.rest.exception.ClientException;
@@ -149,6 +151,14 @@ public class GroupResource extends BaseResource {
parentId = parentGroup.getId();
}
// Check that this group is not used in any workflow in case of renaming
if (!name.equals(groupName)) {
String routeModelName = RoutingUtil.findRouteModelNameByTargetName(AclTargetType.GROUP, groupName);
if (routeModelName != null) {
throw new ClientException("GroupUsedInRouteModel", routeModelName);
}
}
// Update the group
groupDao.update(group.setName(name)
.setParentId(parentId), principal.getId());
@@ -169,6 +179,7 @@ public class GroupResource extends BaseResource {
* @apiSuccess {String} status Status OK
* @apiError (client) ForbiddenError Access denied
* @apiError (client) NotFound Group not found
* @apiError (client) GroupUsedInRouteModel The group is used in a route model
* @apiPermission admin
* @apiVersion 1.5.0
*
@@ -198,6 +209,12 @@ public class GroupResource extends BaseResource {
}
}
// Check that this group is not used in any workflow
String routeModelName = RoutingUtil.findRouteModelNameByTargetName(AclTargetType.GROUP, groupName);
if (routeModelName != null) {
throw new ClientException("GroupUsedInRouteModel", routeModelName);
}
// Delete the group
groupDao.delete(group.getId(), principal.getId());

View File

@@ -0,0 +1,208 @@
package com.sismics.docs.rest.resource;
import com.sismics.docs.core.constant.MetadataType;
import com.sismics.docs.core.dao.MetadataDao;
import com.sismics.docs.core.dao.criteria.MetadataCriteria;
import com.sismics.docs.core.dao.dto.MetadataDto;
import com.sismics.docs.core.model.jpa.Metadata;
import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.docs.rest.constant.BaseFunction;
import com.sismics.rest.exception.ForbiddenClientException;
import com.sismics.rest.util.ValidationUtil;
import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObjectBuilder;
import javax.ws.rs.*;
import javax.ws.rs.core.Response;
import java.util.List;
/**
* Metadata REST resources.
*
* @author bgamard
*/
@Path("/metadata")
public class MetadataResource extends BaseResource {
/**
* Returns the list of all configured metadata.
*
* @api {get} /metadata Get configured metadata
* @apiName GetMetadata
* @apiGroup Metadata
* @apiParam {Number} sort_column Column index to sort on
* @apiParam {Boolean} asc If true, sort in ascending order
* @apiSuccess {Object[]} metadata List of metadata
* @apiSuccess {String} metadata.id ID
* @apiSuccess {String} metadata.name Name
* @apiSuccess {String="STRING","INTEGER","FLOAT","DATE","BOOLEAN"} metadata.type Type
* @apiError (client) ForbiddenError Access denied
* @apiPermission user
* @apiVersion 1.7.0
*
* @return Response
*/
@GET
public Response list(
@QueryParam("sort_column") Integer sortColumn,
@QueryParam("asc") Boolean asc) {
if (!authenticate()) {
throw new ForbiddenClientException();
}
JsonArrayBuilder metadata = Json.createArrayBuilder();
SortCriteria sortCriteria = new SortCriteria(sortColumn, asc);
MetadataDao metadataDao = new MetadataDao();
List<MetadataDto> metadataDtoList = metadataDao.findByCriteria(new MetadataCriteria(), sortCriteria);
for (MetadataDto metadataDto : metadataDtoList) {
metadata.add(Json.createObjectBuilder()
.add("id", metadataDto.getId())
.add("name", metadataDto.getName())
.add("type", metadataDto.getType().name()));
}
JsonObjectBuilder response = Json.createObjectBuilder()
.add("metadata", metadata);
return Response.ok().entity(response.build()).build();
}
/**
* Add a metadata.
*
* @api {put} /metadata Add a custom metadata
* @apiName PutMetadata
* @apiGroup Metadata
* @apiParam {String{1..50}} name Name
* @apiParam {String="STRING","INTEGER","FLOAT","DATE","BOOLEAN"} type Type
* @apiSuccess {String} id ID
* @apiSuccess {String} name Name
* @apiSuccess {String="STRING","INTEGER","FLOAT","DATE","BOOLEAN"} type Type
* @apiError (client) ForbiddenError Access denied
* @apiError (client) ValidationError Validation error
* @apiPermission admin
* @apiVersion 1.7.0
*
* @param name Name
* @param typeStr Type
* @return Response
*/
@PUT
public Response add(@FormParam("name") String name,
@FormParam("type") String typeStr) {
if (!authenticate()) {
throw new ForbiddenClientException();
}
checkBaseFunction(BaseFunction.ADMIN);
// Validate input data
name = ValidationUtil.validateLength(name, "name", 1, 50, false);
MetadataType type = MetadataType.valueOf(ValidationUtil.validateLength(typeStr, "type", 1, 20, false));
// Create the metadata
MetadataDao metadataDao = new MetadataDao();
Metadata metadata = new Metadata();
metadata.setName(name);
metadata.setType(type);
metadataDao.create(metadata, principal.getId());
// Returns the metadata
JsonObjectBuilder response = Json.createObjectBuilder()
.add("id", metadata.getId())
.add("name", metadata.getName())
.add("type", metadata.getType().name());
return Response.ok().entity(response.build()).build();
}
/**
* Update a metadata.
*
* @api {post} /metadata/:id Update a custom metadata
* @apiName PostMetadataId
* @apiGroup Metadata
* @apiParam {String} id Metadata ID
* @apiParam {String{1..50}} name Name
* @apiSuccess {String} id ID
* @apiSuccess {String} name Name
* @apiSuccess {String="STRING","INTEGER","FLOAT","DATE","BOOLEAN"} type Type
* @apiError (client) ForbiddenError Access denied
* @apiError (client) ValidationError Validation error
* @apiError (client) NotFound Metadata not found
* @apiPermission admin
* @apiVersion 1.7.0
*
* @param id ID
* @param name Name
* @return Response
*/
@POST
@Path("{id: [a-z0-9\\-]+}")
public Response update(@PathParam("id") String id,
@FormParam("name") String name) {
if (!authenticate()) {
throw new ForbiddenClientException();
}
checkBaseFunction(BaseFunction.ADMIN);
// Validate input data
name = ValidationUtil.validateLength(name, "name", 1, 50, false);
// Get the metadata
MetadataDao metadataDao = new MetadataDao();
Metadata metadata = metadataDao.getActiveById(id);
if (metadata == null) {
throw new NotFoundException();
}
// Update the metadata
metadata.setName(name);
metadataDao.update(metadata, principal.getId());
// Returns the metadata
JsonObjectBuilder response = Json.createObjectBuilder()
.add("id", metadata.getId())
.add("name", metadata.getName())
.add("type", metadata.getType().name());
return Response.ok().entity(response.build()).build();
}
/**
* Delete a metadata.
*
* @api {delete} /metadata/:id Delete a custom metadata
* @apiName DeleteMetadataId
* @apiGroup Metadata
* @apiParam {String} id Metadata ID
* @apiSuccess {String} status Status OK
* @apiError (client) ForbiddenError Access denied
* @apiError (client) NotFound Metadata not found
* @apiPermission admin
* @apiVersion 1.7.0
*
* @param id ID
* @return Response
*/
@DELETE
@Path("{id: [a-z0-9\\-]+}")
public Response delete(@PathParam("id") String id) {
if (!authenticate()) {
throw new ForbiddenClientException();
}
checkBaseFunction(BaseFunction.ADMIN);
// Get the metadata
MetadataDao metadataDao = new MetadataDao();
Metadata metadata = metadataDao.getActiveById(id);
if (metadata == null) {
throw new NotFoundException();
}
// Delete the metadata
metadataDao.delete(id, principal.getId());
// Always return OK
JsonObjectBuilder response = Json.createObjectBuilder()
.add("status", "ok");
return Response.ok().entity(response.build()).build();
}
}

View File

@@ -326,7 +326,7 @@ public class RouteResource extends BaseResource {
// Delete the route and the steps
RouteDao routeDao = new RouteDao();
routeDao.deleteRoute(routeStepDto.getRouteId());
routeDao.deleteRoute(routeStepDto.getRouteId(), principal.getId());
// Always return OK
JsonObjectBuilder response = Json.createObjectBuilder()

View File

@@ -82,7 +82,7 @@ public class ThemeResource extends BaseResource {
public Response get() {
JsonObject themeConfig = getThemeConfig();
JsonObjectBuilder json = Json.createObjectBuilder();
json.add("name", themeConfig.getString("name", "Sismics Docs"));
json.add("name", themeConfig.getString("name", "Teedy"));
json.add("color", themeConfig.getString("color", "#ffffff"));
json.add("css", themeConfig.getString("css", ""));
return Response.ok().entity(json.build()).build();

View File

@@ -2,6 +2,7 @@ package com.sismics.docs.rest.resource;
import com.google.common.base.Strings;
import com.google.common.collect.Sets;
import com.sismics.docs.core.constant.AclTargetType;
import com.sismics.docs.core.constant.ConfigType;
import com.sismics.docs.core.constant.Constants;
import com.sismics.docs.core.dao.*;
@@ -15,6 +16,7 @@ import com.sismics.docs.core.event.PasswordLostEvent;
import com.sismics.docs.core.model.context.AppContext;
import com.sismics.docs.core.model.jpa.*;
import com.sismics.docs.core.util.ConfigUtil;
import com.sismics.docs.core.util.RoutingUtil;
import com.sismics.docs.core.util.authentication.AuthenticationUtil;
import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.docs.rest.constant.BaseFunction;
@@ -99,6 +101,7 @@ public class UserResource extends BaseResource {
user.setPassword(password);
user.setEmail(email);
user.setStorageQuota(storageQuota);
user.setOnboarding(true);
// Create the user
UserDao userDao = new UserDao();
@@ -434,6 +437,7 @@ public class UserResource extends BaseResource {
* @apiGroup User
* @apiSuccess {String} status Status OK
* @apiError (client) ForbiddenError Access denied or the user cannot be deleted
* @apiError (client) UserUsedInRouteModel The user is used in a route model
* @apiPermission user
* @apiVersion 1.5.0
*
@@ -450,6 +454,12 @@ public class UserResource extends BaseResource {
throw new ClientException("ForbiddenError", "This user cannot be deleted");
}
// Check that this user is not used in any workflow
String routeModelName = RoutingUtil.findRouteModelNameByTargetName(AclTargetType.USER, principal.getName());
if (routeModelName != null) {
throw new ClientException("UserUsedInRouteModel", routeModelName);
}
// Find linked data
DocumentDao documentDao = new DocumentDao();
List<Document> documentList = documentDao.findByUserId(principal.getId());
@@ -493,6 +503,7 @@ public class UserResource extends BaseResource {
* @apiSuccess {String} status Status OK
* @apiError (client) ForbiddenError Access denied or the user cannot be deleted
* @apiError (client) UserNotFound The user does not exist
* @apiError (client) UserUsedInRouteModel The user is used in a route model
* @apiPermission admin
* @apiVersion 1.5.0
*
@@ -512,7 +523,7 @@ public class UserResource extends BaseResource {
throw new ClientException("ForbiddenError", "The guest user cannot be deleted");
}
// Check if the user exists
// Check that the user exists
UserDao userDao = new UserDao();
User user = userDao.getActiveByUsername(username);
if (user == null) {
@@ -526,6 +537,12 @@ public class UserResource extends BaseResource {
throw new ClientException("ForbiddenError", "The admin user cannot be deleted");
}
// Check that this user is not used in any workflow
String routeModelName = RoutingUtil.findRouteModelNameByTargetName(AclTargetType.USER, username);
if (routeModelName != null) {
throw new ClientException("UserUsedInRouteModel", routeModelName);
}
// Find linked data
DocumentDao documentDao = new DocumentDao();
List<Document> documentList = documentDao.findByUserId(user.getId());
@@ -606,6 +623,7 @@ public class UserResource extends BaseResource {
* @apiGroup User
* @apiSuccess {Boolean} anonymous True if no user is connected
* @apiSuccess {Boolean} is_default_password True if the admin has the default password
* @apiSuccess {Boolean} onboarding True if the UI needs to display the onboarding
* @apiSuccess {String} username Username
* @apiSuccess {String} email E-mail
* @apiSuccess {Number} storage_quota Storage quota (in bytes)
@@ -649,7 +667,8 @@ public class UserResource extends BaseResource {
.add("email", user.getEmail())
.add("storage_quota", user.getStorageQuota())
.add("storage_current", user.getStorageCurrent())
.add("totp_enabled", user.getTotpKey() != null);
.add("totp_enabled", user.getTotpKey() != null)
.add("onboarding", user.isOnboarding());
// Base functions
JsonArrayBuilder baseFunctions = Json.createArrayBuilder();
@@ -883,6 +902,39 @@ public class UserResource extends BaseResource {
return Response.ok().entity(response.build()).build();
}
/**
* Mark the onboarding experience as passed.
*
* @api {post} /user/onboarded Mark the onboarding experience as passed
* @apiDescription Once the onboarding experience has been passed by the user, this resource prevent it from being displayed again.
* @apiName PostUserOnboarded
* @apiGroup User
* @apiSuccess {String} status Status OK
* @apiError (client) ForbiddenError Access denied
* @apiPermission user
* @apiVersion 1.7.0
*
* @return Response
*/
@POST
@Path("onboarded")
public Response onboarded() {
if (!authenticate()) {
throw new ForbiddenClientException();
}
// Save it
UserDao userDao = new UserDao();
User user = userDao.getActiveByUsername(principal.getName());
user.setOnboarding(false);
userDao.updateOnboarding(user);
// Always return OK
JsonObjectBuilder response = Json.createObjectBuilder()
.add("status", "ok");
return Response.ok().entity(response.build()).build();
}
/**
* Enable time-based one-time password.
*

View File

@@ -198,7 +198,7 @@ public class VocabularyResource extends BaseResource {
/**
* Delete a vocabulary entry.
*
* @api {delete} /vocabulary/:id Delete vocabulary entry
* @api {delete} /vocabulary/:id Delete a vocabulary entry
* @apiName DeleteVocabularyId
* @apiGroup Vocabulary
* @apiParam {String} id Entry ID

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -5,7 +5,7 @@
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0"
metadata-complete="true">
<display-name>Docs</display-name>
<display-name>Teedy</display-name>
<!-- Proper loader/unloader of ImageIO plugins -->
<listener>

View File

@@ -1,4 +1,4 @@
The web client and Android application for **Sismics Docs** are only examples
The web client and Android application for **Teedy** are only examples
of what is possible with the provided REST API. Everything you see in those apps are
accessible using the API.
@@ -6,8 +6,8 @@ This documentation is divided in two parts. The first will get you started on es
steps like authentication and the second part is a full reference of every endpoints.
## API URL
The base URL depends on your server. If your instance of Docs is accessible through
`https://docs.mycompany.com`, then the base API URL is `https://docs.mycompany.com/api`.
The base URL depends on your server. If your instance of Teedy is accessible through
`https://teedy.mycompany.com`, then the base API URL is `https://teedy.mycompany.com/api`.
## Verbs and status codes
The API uses restful verbs.

View File

@@ -9,8 +9,8 @@
"url": "git://github.com/sismics/docs.git"
},
"apidoc": {
"name": "Sismics Docs API",
"title": "Sismics Docs API",
"name": "Teedy API",
"title": "Teedy API",
"url": "/api",
"template": {
"withCompare": false,

View File

@@ -1,7 +1,7 @@
'use strict';
/**
* Sismics Docs application.
* Teedy application.
*/
angular.module('docs',
// Dependencies
@@ -145,6 +145,15 @@ angular.module('docs',
}
}
})
.state('settings.metadata', {
url: '/metadata',
views: {
'settings': {
templateUrl: 'partial/docs/settings.metadata.html',
controller: 'SettingsMetadata'
}
}
})
.state('settings.user', {
url: '/user',
views: {
@@ -411,9 +420,10 @@ angular.module('docs',
prefix: 'locale/',
suffix: '.json?@build.date@'
})
.registerAvailableLanguageKeys(['en', 'fr', 'de', 'ru', 'zh_CN', 'zh_TW'], {
.registerAvailableLanguageKeys(['en', 'es', 'fr', 'de', 'ru', 'zh_CN', 'zh_TW'], {
'ru_*': 'ru',
'en_*': 'en',
'es_*': 'es',
'fr_*': 'fr',
'de_*': 'de',
'*': 'en'
@@ -508,7 +518,9 @@ angular.module('docs',
{ key: 'jpn', label: '日本語' },
{ key: 'tha', label: 'ภาษาไทย' },
{ key: 'kor', label: '한국어' },
{ key: 'nld', label: 'Nederlands' }
{ key: 'nld', label: 'Nederlands' },
{ key: 'tur', label: 'Türkçe' },
{ key: 'heb', label: 'עברית' }
];
})
/**

View File

@@ -319,9 +319,9 @@ angular.module('docs').controller('Document', function ($scope, $rootScope, $tim
*/
$scope.extractNavigatedTag = function () {
// Find the current tag in the search query
var tagFound = /tag:([^ ]*)/.exec($scope.search);
var tagFound = /(^| )tag:([^ ]*)/.exec($scope.search);
if (tagFound) {
tagFound = tagFound[1];
tagFound = tagFound[2];
// We search only for exact match
$scope.navigatedTag = _.findWhere($scope.tags, { name: tagFound });
} else {

View File

@@ -3,7 +3,7 @@
/**
* Document default controller.
*/
angular.module('docs').controller('DocumentDefault', function ($scope, $rootScope, $state, Restangular, Upload, $translate, $uibModal, $dialog) {
angular.module('docs').controller('DocumentDefault', function ($scope, $rootScope, $state, Restangular, Upload, $translate, $uibModal, $dialog, User) {
// Load user audit log
Restangular.one('auditlog').get().then(function (data) {
$scope.logs = data.logs;
@@ -145,10 +145,12 @@ angular.module('docs').controller('DocumentDefault', function ($scope, $rootScop
// Onboarding
$translate('onboarding.step1.title').then(function () {
if (localStorage.onboardingDisplayed || $(window).width() < 1000) {
User.userInfo().then(function(userData) {
if (!userData.onboarding || $(window).width() < 1000) {
return;
}
localStorage.onboardingDisplayed = true;
Restangular.one('user').post('onboarded');
$rootScope.userInfo.onboarding = false;
$rootScope.onboardingEnabled = true;
@@ -190,3 +192,4 @@ angular.module('docs').controller('DocumentDefault', function ($scope, $rootScop
];
});
});
});

View File

@@ -59,9 +59,18 @@ angular.module('docs').controller('DocumentEdit', function($rootScope, $scope, $
$scope.document = {
tags: [],
relations: [],
language: language
language: language,
metadata: []
};
// Get custom metadata list
Restangular.one('metadata').get({
sort_column: 1,
asc: true
}).then(function(data) {
$scope.document.metadata = data.metadata;
});
if ($scope.navigatedTag) {
$scope.document.tags.push($scope.navigatedTag);
}
@@ -93,6 +102,20 @@ angular.module('docs').controller('DocumentEdit', function($rootScope, $scope, $
// Extract ids from relations (only when our document is the source)
document.relations = _.pluck(_.where(document.relations, { source: true }), 'id');
// Extract custom metadata values
var metadata = _.reject(document.metadata, function (meta) {
return _.isUndefined(meta.value) || meta.value === '' || meta.value == null;
});
document.metadata_id = _.pluck(metadata, 'id');
document.metadata_value = _.pluck(metadata, 'value');
document.metadata_value = _.map(document.metadata_value, function (val) {
if (val instanceof Date) {
return val.getTime();
}
return val;
});
// Send to server
if ($scope.isEdit()) {
promise = Restangular.one('document', $stateParams.id).post('', document);
} else {

View File

@@ -20,7 +20,15 @@ angular.module('docs').controller('DocumentViewContent', function ($scope, $root
forceHelperSize: true,
forcePlaceholderSize: true,
tolerance: 'pointer',
start: function() {
$(this).addClass('currently-dragging');
},
stop: function () {
var _this = this;
setTimeout(function(){
$(_this).removeClass('currently-dragging');
}, 300);
// Send new positions to server
$scope.$apply(function () {
Restangular.one('file').post('reorder', {
@@ -45,8 +53,10 @@ angular.module('docs').controller('DocumentViewContent', function ($scope, $root
/**
* Navigate to the selected file.
*/
$scope.openFile = function (file) {
$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})
}
};
/**
@@ -101,6 +111,9 @@ angular.module('docs').controller('DocumentViewContent', function ($scope, $root
}
if (files && files.length) {
// Sort by filename
files = _.sortBy(files, 'name');
// Adding files to the UI
var newfiles = [];
_.each(files, function (file) {
@@ -195,10 +208,14 @@ angular.module('docs').controller('DocumentViewContent', function ($scope, $root
});
};
/**
* Open versions history.
*/
$scope.openVersions = function (file) {
$uibModal.open({
templateUrl: 'partial/docs/file.versions.html',
controller: 'ModalFileVersions',
size: 'lg',
resolve: {
file: function () {
return file;

View File

@@ -29,7 +29,7 @@ angular.module('docs').controller('SettingsConfig', function($scope, $rootScope,
// Update the theme
$scope.update = function () {
$scope.theme.name = $scope.theme.name.length === 0 ? 'Sismics Docs' : $scope.theme.name;
$scope.theme.name = $scope.theme.name.length === 0 ? 'Teedy' : $scope.theme.name;
Restangular.one('theme').post('', $scope.theme).then(function () {
var stylesheet = $('#theme-stylesheet')[0];
stylesheet.href = stylesheet.href.replace(/\?.*|$/, '?' + new Date().getTime());

View File

@@ -51,6 +51,11 @@ angular.module('docs').controller('SettingsGroupEdit', function($scope, $dialog,
var msg = $translate.instant('settings.group.edit.edit_group_failed_message');
var btns = [{result: 'ok', label: $translate.instant('ok'), cssClass: 'btn-primary'}];
$dialog.messageBox(title, msg, btns);
} else if (e.data.type === 'GroupUsedInRouteModel') {
var title = $translate.instant('settings.group.edit.group_used_title');
var msg = $translate.instant('settings.group.edit.group_used_message', { name: e.data.message });
var btns = [{result: 'ok', label: $translate.instant('ok'), cssClass: 'btn-primary'}];
$dialog.messageBox(title, msg, btns);
}
});
};
@@ -71,8 +76,13 @@ angular.module('docs').controller('SettingsGroupEdit', function($scope, $dialog,
Restangular.one('group', $stateParams.name).remove().then(function() {
$scope.loadGroups();
$state.go('settings.group');
}, function() {
$state.go('settings.group');
}, function(e) {
if (e.data.type === 'GroupUsedInRouteModel') {
var title = $translate.instant('settings.group.edit.group_used_title');
var msg = $translate.instant('settings.group.edit.group_used_message', { name: e.data.message });
var btns = [{result: 'ok', label: $translate.instant('ok'), cssClass: 'btn-primary'}];
$dialog.messageBox(title, msg, btns);
}
});
}
});

View File

@@ -0,0 +1,34 @@
'use strict';
/**
* Settings metadata page controller.
*/
angular.module('docs').controller('SettingsMetadata', function($scope, Restangular) {
// Load metadata
Restangular.one('metadata').get({
sort_column: 1,
asc: true
}).then(function(data) {
$scope.metadata = data.metadata;
});
// Add a metadata
$scope.addMetadata = function() {
Restangular.one('metadata').put($scope.newmetadata).then(function(data) {
$scope.metadata.push(data);
$scope.newmetadata = {};
});
};
// Delete a metadata
$scope.deleteMetadata = function(meta) {
Restangular.one('metadata', meta.id).remove().then(function() {
$scope.metadata.splice($scope.metadata.indexOf(meta), 1);
});
};
// Update a metadata
$scope.updateMetadata = function(meta) {
Restangular.one('metadata', meta.id).post('', meta);
};
});

View File

@@ -70,8 +70,13 @@ angular.module('docs').controller('SettingsUserEdit', function($scope, $dialog,
Restangular.one('user', $stateParams.username).remove().then(function () {
$scope.loadUsers();
$state.go('settings.user');
}, function() {
$state.go('settings.user');
}, function(e) {
if (e.data.type === 'UserUsedInRouteModel') {
var title = $translate.instant('settings.user.edit.user_used_title');
var msg = $translate.instant('settings.user.edit.user_used_message', { name: e.data.message });
var btns = [{result: 'ok', label: $translate.instant('ok'), cssClass: 'btn-primary'}];
$dialog.messageBox(title, msg, btns);
}
});
}
});

View File

@@ -61,9 +61,10 @@ angular.module('share',
prefix: 'locale/',
suffix: '.json?@build.date@'
})
.registerAvailableLanguageKeys(['en', 'fr', 'de', 'ru', 'zh_CN', 'zh_TW'], {
.registerAvailableLanguageKeys(['en', 'es', 'fr', 'de', 'ru', 'zh_CN', 'zh_TW'], {
'ru_*': 'ru',
'en_*': 'en',
'es_*': 'es',
'fr_*': 'fr',
'de_*': 'de',
'*': 'en'

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,7 +1,7 @@
<!DOCTYPE html>
<html ng-app="docs">
<head>
<title ng-bind-template="{{ pageTitle ? pageTitle : appName }}">Sismics Docs</title>
<title ng-bind-template="{{ pageTitle ? pageTitle : appName }}">Teedy</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
@@ -93,6 +93,7 @@
<script src="app/docs/controller/settings/SettingsGroup.js" type="text/javascript"></script>
<script src="app/docs/controller/settings/SettingsGroupEdit.js" type="text/javascript"></script>
<script src="app/docs/controller/settings/SettingsVocabulary.js" type="text/javascript"></script>
<script src="app/docs/controller/settings/SettingsMetadata.js" type="text/javascript"></script>
<script src="app/docs/controller/usergroup/UserGroup.js" type="text/javascript"></script>
<script src="app/docs/controller/usergroup/UserProfile.js" type="text/javascript"></script>
<script src="app/docs/controller/usergroup/GroupProfile.js" type="text/javascript"></script>
@@ -121,15 +122,11 @@
<span class="icon-bar"></span>
</button>
<div class="hidden-xs navbar-text navbar-logo">
<img src="../api/theme/image/logo" />
</div>
<a class="navbar-brand" href="#">
<span ng-if="appName == 'Sismics Docs'">
<span style="color: #e43935;">Sismics</span> <span style="color: #2aabd2;">Docs</span>
<span ng-if="appName == 'Teedy'">
<span style="color: #2aabd2;">teedy</span>
</span>
<span ng-if="appName != 'Sismics Docs'" style="color: #888;">{{ appName }}</span>
<span ng-if="appName != 'Teedy'" style="color: #888;">{{ appName }}</span>
</a>
</div>
@@ -184,6 +181,7 @@
<span ng-switch-when="en">English</span>
<span ng-switch-when="fr">Français</span>
<span ng-switch-when="de">Deutsch</span>
<span ng-switch-when="es">Española</span>
<span ng-switch-when="ru">русский</span>
<span ng-switch-when="zh_CN">简体中文</span>
<span ng-switch-when="zh_TW">繁體中文</span>
@@ -194,6 +192,7 @@
<li><a href ng-click="changeLanguage('en')" ng-class="{ 'bg-info': currentLang == 'en' }">English</a></li>
<li><a href ng-click="changeLanguage('fr')" ng-class="{ 'bg-info': currentLang == 'fr' }">Français</a></li>
<li><a href ng-click="changeLanguage('de')" ng-class="{ 'bg-info': currentLang == 'de' }">Deutsch</a></li>
<li><a href ng-click="changeLanguage('es')" ng-class="{ 'bg-info': currentLang == 'es' }">Española</a></li>
<li><a href ng-click="changeLanguage('ru')" ng-class="{ 'bg-info': currentLang == 'ru' }">русский</a></li>
<li><a href ng-click="changeLanguage('zh_CN')" ng-class="{ 'bg-info': currentLang == 'zh_CN' }">简体中文</a></li>
<li><a href ng-click="changeLanguage('zh_TW')" ng-class="{ 'bg-info': currentLang == 'zh_TW' }">繁體中文</a></li>

View File

@@ -1,7 +1,7 @@
/**!
* =======================================================================
* Sismics Docs patch applied to encode filenames with encodeURIComponent.
* =======================================================================
* ================================================================
* Teedy patch applied to encode filenames with encodeURIComponent.
* ================================================================
*
* AngularJS file upload directives and services. Supoorts: file upload/drop/paste, resume, cancel/abort,
* progress, resize, thumbnail, preview, validation and CORS

View File

@@ -149,6 +149,29 @@ angular.module('yaru22.angular-timeago').config(["timeAgoSettings", function(tim
'use strict';
angular.module('yaru22.angular-timeago').config(["timeAgoSettings", function(timeAgoSettings) {
timeAgoSettings.strings['es'] = {
prefixAgo: 'hace',
prefixFromNow: 'dentro de',
suffixAgo: null,
suffixFromNow: null,
seconds: 'menos de un minuto',
minute: 'un minuto',
minutes: '%d minutos',
hour: 'una hora',
hours: '%d horas',
day: 'un día',
days: '%d días',
month: 'un mes',
months: '%d meses',
year: 'un año',
years: '%d años',
numbers: []
};
}]);
'use strict';
angular.module('yaru22.angular-timeago').config(["timeAgoSettings", function(timeAgoSettings) {
timeAgoSettings.strings['es_ES'] = {
prefixAgo: 'hace',

View File

@@ -0,0 +1,125 @@
'use strict';
angular.module("ngLocale", [], ["$provide", function($provide) {
var PLURAL_CATEGORY = {ZERO: "zero", ONE: "one", TWO: "two", FEW: "few", MANY: "many", OTHER: "other"};
$provide.value("$locale", {
"DATETIME_FORMATS": {
"AMPMS": [
"a. m.",
"p. m."
],
"DAY": [
"domingo",
"lunes",
"martes",
"mi\u00e9rcoles",
"jueves",
"viernes",
"s\u00e1bado"
],
"ERANAMES": [
"antes de Cristo",
"despu\u00e9s de Cristo"
],
"ERAS": [
"a. C.",
"d. C."
],
"FIRSTDAYOFWEEK": 0,
"MONTH": [
"enero",
"febrero",
"marzo",
"abril",
"mayo",
"junio",
"julio",
"agosto",
"septiembre",
"octubre",
"noviembre",
"diciembre"
],
"SHORTDAY": [
"dom.",
"lun.",
"mar.",
"mi\u00e9.",
"jue.",
"vie.",
"s\u00e1b."
],
"SHORTMONTH": [
"ene.",
"feb.",
"mar.",
"abr.",
"may.",
"jun.",
"jul.",
"ago.",
"sept.",
"oct.",
"nov.",
"dic."
],
"STANDALONEMONTH": [
"enero",
"febrero",
"marzo",
"abril",
"mayo",
"junio",
"julio",
"agosto",
"septiembre",
"octubre",
"noviembre",
"diciembre"
],
"WEEKENDRANGE": [
5,
6
],
"fullDate": "EEEE, d 'de' MMMM 'de' y",
"longDate": "d 'de' MMMM 'de' y",
"medium": "d MMM y H:mm:ss",
"mediumDate": "d MMM y",
"mediumTime": "H:mm:ss",
"short": "d/M/yy H:mm",
"shortDate": "d/M/yy",
"shortTime": "H:mm"
},
"NUMBER_FORMATS": {
"CURRENCY_SYM": "\u20ac",
"DECIMAL_SEP": ",",
"GROUP_SEP": ".",
"PATTERNS": [
{
"gSize": 3,
"lgSize": 3,
"maxFrac": 3,
"minFrac": 0,
"minInt": 1,
"negPre": "-",
"negSuf": "",
"posPre": "",
"posSuf": ""
},
{
"gSize": 3,
"lgSize": 3,
"maxFrac": 2,
"minFrac": 2,
"minInt": 1,
"negPre": "-",
"negSuf": "\u00a0\u00a4",
"posPre": "",
"posSuf": "\u00a0\u00a4"
}
]
},
"id": "es",
"localeID": "es",
"pluralCat": function(n, opt_precision) { if (n == 1) { return PLURAL_CATEGORY.ONE; } return PLURAL_CATEGORY.OTHER;}
});
}]);

View File

@@ -466,9 +466,9 @@
},
"feedback": {
"title": "Geben Sie uns Feedback",
"message": "Irgendwelche Vorschläge oder Fragen zu Sismics Docs? Wir hören Ihnen gerne zu!",
"message": "Irgendwelche Vorschläge oder Fragen zu Teedy? Wir hören Ihnen gerne zu!",
"sent_title": "Feedback gesendet",
"sent_message": "Vielen Dank für Ihr Feedback! Es wird uns helfen, Sismics Docs noch besser zu machen."
"sent_message": "Vielen Dank für Ihr Feedback! Es wird uns helfen, Teedy noch besser zu machen."
},
"import": {
"title": "Wird importiert",

View File

@@ -50,7 +50,7 @@
"search_before_date": "Created before this date",
"search_after_date": "Created after this date",
"search_before_update_date": "Updated before this date",
"search_after_update_date": "Update after this date",
"search_after_update_date": "Updated after this date",
"search_tags": "Tags",
"search_shared": "Only shared documents",
"search_workflow": "Workflow assigned to me",
@@ -278,6 +278,7 @@
"menu_vocabularies": "Vocabularies",
"menu_configuration": "Configuration",
"menu_inbox": "Inbox scanning",
"menu_metadata": "Custom metadata",
"menu_monitoring": "Monitoring",
"user": {
"title": "Users management",
@@ -288,6 +289,8 @@
"edit": {
"delete_user_title": "Delete user",
"delete_user_message": "Do you really want to delete this user? All associated documents, files and tags will be deleted",
"user_used_title": "User in use",
"user_used_message": "This user is used in the workflow \"{{ name }}\"",
"edit_user_failed_title": "User already exists",
"edit_user_failed_message": "This username is already taken by another user",
"edit_user_title": "Edit \"{{ username }}\"",
@@ -320,7 +323,7 @@
"add_workflow_title": "Add a workflow",
"name": "Name",
"name_placeholder": "Step name or description",
"drag_help": "Drag and drop to rorder the step",
"drag_help": "Drag and drop to reorder the step",
"type": "Step type",
"type_approve": "Approve",
"type_validate": "Validate",
@@ -363,6 +366,8 @@
"delete_group_message": "Do you really want to delete this group?",
"edit_group_failed_title": "Group already exists",
"edit_group_failed_message": "This group name is already taken by another group",
"group_used_title": "Group in use",
"group_used_message": "This group is used in the workflow \"{{ name }}\"",
"edit_group_title": "Edit \"{{ name }}\"",
"add_group_title": "Add a group",
"name": "Name",
@@ -408,6 +413,12 @@
"webhook_create_date": "Create date",
"webhook_add": "Add a webhook"
},
"metadata": {
"title": "Custom metadata configuration",
"message": "Here you can add custom metadata to your documents like an internal identifier or an expiration date. Please note that the metadata type cannot be changed after creation.",
"name": "Metadata name",
"type": "Metadata type"
},
"inbox": {
"title": "Inbox scanning",
"message": "By enabling this feature, the system will scan the specified inbox every minute for <strong>unread</strong> emails and automatically import them.<br/>After importing an email, it will be marked as read.<br/>Configuration settings for <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>.",
@@ -464,16 +475,16 @@
"need_2": "Scan a directory for new files and import them",
"line_1": "Go to <a href=\"https://github.com/sismics/docs/releases\">sismics/docs/releases</a> and download the file importer tool for your system.",
"line_2": "Follow the <a href=\"https://github.com/sismics/docs/tree/master/docs-importer\">instructions here</a> to use this tool.",
"line_3": "Your files will be imported in <a href=\"#/document\">Quick upload</a>, then you can group them into documents.",
"line_3": "Your files will be imported in documents according to the file importer configuration.",
"download": "Download",
"instructions": "Instructions"
}
},
"feedback": {
"title": "Give us a feedback",
"message": "Any suggestion or question about Sismics Docs? We listen to you!",
"message": "Any suggestion or question about Teedy? We listen to you!",
"sent_title": "Feedback sent",
"sent_message": "Thank you for your feedback! It will help us make Sismics Docs even better."
"sent_message": "Thank you for your feedback! It will help us make Teedy even better."
},
"import": {
"title": "Importing",
@@ -567,11 +578,11 @@
"onboarding": {
"step1": {
"title": "First time?",
"description": "If it's your first time on Sismics Docs, click the Next button, otherwise feel free to close me."
"description": "If it's your first time on Teedy, click the Next button, otherwise feel free to close me."
},
"step2": {
"title": "Documents",
"description": "Sismics Docs is organized in documents and each document contains multiple files."
"description": "Teedy is organized in documents and each document contains multiple files."
},
"step3": {
"title": "Files",
@@ -586,6 +597,8 @@
"description": "Documents can be organized in tags (which are like super-folders). Create them here."
}
},
"yes": "Yes",
"no": "No",
"ok": "OK",
"cancel": "Cancel",
"share": "Share",
@@ -599,6 +612,7 @@
"edit": "Edit",
"delete": "Delete",
"rename": "Rename",
"download": "Download",
"loading": "Loading...",
"send": "Send",
"enabled": "Enabled",

View File

@@ -0,0 +1,606 @@
{
"login": {
"username": "Usuario",
"password": "Contraseña",
"validation_code_required": "Validación de código requerida",
"validation_code_title": "Ha activado la autenticación en dos pasos para su cuenta. Por favor, introduzca el código generado por la app en su teléfono.",
"validation_code": "Código de validación",
"remember_me": "Recordarme",
"submit": "Iniciar sesión",
"login_as_guest": "Iniciar sesión como invitado",
"login_failed_title": "Error al iniciar sesión",
"login_failed_message": "Usuario o contraseña incorrectos",
"password_lost_btn": "¿Contraseña olvidada?",
"password_lost_sent_title": "Correo de reestablecimiento de contraseña enviado",
"password_lost_sent_message": "Se ha enviado un correo a <strong>{{ username }}</strong> para reestablecer su contraseña",
"password_lost_error_title": "Error al reestablecer la contraseña",
"password_lost_error_message": "Imposible enviar correo de reestablecimiento de contraseña, por favor, contacte a su administrador para hacerlo manualmente"
},
"passwordlost": {
"title": "Contraseña olvidada",
"message": "Por favor, introduzca su usuario para recibir un enlace de reestablecimiento. Si no recuerda su usuario, por favor, contacte a su administrador",
"submit": "Reestablecer mi contraseña"
},
"passwordreset": {
"message": "Por favor, introduzca la nueva contraseña",
"submit": "Cambiar mi contraseña",
"error_title": "Error al cambiar tu contraseña",
"error_message": "Su petición de recuperación de contraseña ha expirado, por favor, pídala nuevamente en la página de inicio de sesión"
},
"index": {
"toggle_navigation": "Cambiar modo navegación",
"nav_documents": "Documentos",
"nav_tags": "Etiquetas",
"nav_users_groups": "Usuarios y Grupos",
"error_info": "{{ count }} nuevo error{{ count > 1 ? 's' : '' }}",
"logged_as": "Sesión iniciada como {{ username }}",
"nav_settings": "Ajustes",
"logout": "Cerrar sesión",
"global_quota_warning": "<strong>¡Advertencia!</strong> Cuota global casi alcanzada en {{ current | number: 0 }}MB ({{ percent | number: 1 }}%) usados en {{ total | number: 0 }}MB"
},
"document": {
"navigation_up": "Subir un nivel",
"toggle_navigation": "Cambiar modo navegación",
"display_mode_list": "Mostrar documentos en lista",
"display_mode_grid": "Mostrar documentos en cuadrícula",
"search_simple": "Búsqueda sencilla",
"search_fulltext": "Búsqueda texto completo",
"search_creator": "Creador",
"search_language": "Idioma",
"search_before_date": "Creado antes de esta fecha",
"search_after_date": "Creado después de esta fecha",
"search_before_update_date": "Actualizado antes de esta fecha",
"search_after_update_date": "Actualizado después de esta fecha",
"search_tags": "Etiquetas",
"search_shared": "Solo documentos compartidos",
"search_workflow": "Flujo de trabajo asignado a mi",
"search_clear": "Limpiar",
"any_language": "Cualquier idioma",
"add_document": "Añadir un documento",
"import_eml": "Importar desde un correo (formato EML)",
"tags": "Etiquetas",
"no_tags": "Ninguna etiqueta",
"no_documents": "No hay documento(s) en la base de datos",
"search": "Buscar",
"search_empty": "Sin coincidencias para <strong>\"{{ search }}\"</strong>",
"shared": "Compartido",
"current_step_name": "Progreso actual",
"title": "Título",
"description": "Descripción",
"contributors": "Contribuidores",
"language": "Idioma",
"creation_date": "Fecha de creación",
"subject": "Tema",
"identifier": "Identificador",
"publisher": "Editor",
"format": "Formato",
"source": "Fuente",
"type": "Tipo",
"coverage": "Cobertura",
"rights": "Derechos",
"relations": "Relations",
"page_size": "Tamaño página",
"page_size_10": "10 por página",
"page_size_20": "20 por página",
"page_size_30": "30 por página",
"upgrade_quota": "Para actualizar su cuota, pregunte al administrador",
"quota": "{{ current | number: 0 }}MB ({{ percent | number: 1 }}%) usados en {{ total | number: 0 }}MB",
"count": "{{ count }} documento{{ count > 1 ? 's' : '' }} encontrado{{ count > 1 ? 's' : '' }}",
"last_updated": "Último actualizado {{ date | timeAgo: dateFormat }}",
"view": {
"delete_comment_title": "Eliminar comentario",
"delete_comment_message": "¿Desea realmente eliminar este comentario?",
"delete_document_title": "Eliminar documento",
"delete_document_message": "¿Desea realmente eliminar este documento?",
"shared_document_title": "Documento compartido",
"shared_document_message": "Puede compartir el documento ofreciendo este enlace. Tenga en cuenta que cualquiera que tenga este enlace puede ver el documento.<br/><input class=\"form-control share-link\" type=\"text\" readonly=\"readonly\" value=\"{{ link }}\" onclick=\"this.select(); document.execCommand('copy');\" />",
"not_found": "Documento no encontrado",
"forbidden": "Acceso denegado",
"download_files": "Descargar archivos",
"export_pdf": "Exportar a PDF",
"by_creator": "por",
"comments": "Comentarios",
"no_comments": "Aún no hay comentarios sobre este documento",
"add_comment": "Añadir un comentario",
"error_loading_comments": "Error al cargar comentarios",
"workflow_current": "Flujo de trabajo actual",
"workflow_comment": "Añadir comentario de flujo de trabajo",
"workflow_validated_title": "Flujo de trabajo validado",
"workflow_validated_message": "El flujo de trabajo ha sido validado con éxito",
"content": {
"content": "Contenido",
"delete_file_title": "Eliminar archivo",
"delete_file_message": "¿Desea realmente eliminar este archivo?",
"upload_pending": "Pendiente...",
"upload_progress": "Subiendo...",
"upload_error": "Error al subir",
"upload_error_quota": "Cuota alcanzada",
"drop_zone": "Arrastre y suelte archivos aquí para subir",
"add_files": "Añadir archivos",
"file_processing_indicator": "El archivo esta siendo procesado. La búsqueda no estará disponible hasta que finalize el proceso.",
"reprocess_file": "Volver a procesar el archivo",
"upload_new_version": "Subir una nueva versión",
"open_versions": "Mostrar historial de versiones",
"display_mode_list": "Mostrar documentos en lista",
"display_mode_grid": "Mostrar documentos en cuadrícula"
},
"workflow": {
"workflow": "Flujo de trabajo",
"message": "Verifique o valide sus documentos con gente de su organización usando flujos de trabajo.",
"workflow_start_label": "¿Qué flujo de trabajo iniciar?",
"add_more_workflow": "Añadir más flujos de trabajo",
"start_workflow_submit": "Iniciar flujo de trabajo",
"full_name": "<strong>{{ name }}</strong> iniciado a {{ create_date | date }}",
"cancel_workflow": "Cancelar el flujo de trabajo actual",
"cancel_workflow_title": "Cancelar flujo de trabajo",
"cancel_workflow_message": "¿Desea realmente cancelar el flujo de trabajo actual?",
"no_workflow": "No puede iniciar ningún flujo de trabajo en este documento."
},
"permissions": {
"permissions": "Permisos",
"message": "Los permisos pueden ser aplicados directamente a este documento, o pueden provenir de <a href=\"#/tag\">etiquetas</a>.",
"title": "Permisos en este documento",
"inherited_tags": "Permisos heredados por etiquetas",
"acl_source": "De",
"acl_target": "Para",
"acl_permission": "Permisos"
},
"activity": {
"activity": "Actividad",
"message": "Todas las acciones en este documento se registran aquí."
}
},
"edit": {
"document_edited_with_errors": "Documento editado correctamente, pero algunos archivos no pueden ser subidos",
"document_added_with_errors": "Documento añadido correctamente, pero algunos archivos no pueden ser subidos",
"quota_reached": "Cuota alcanzada",
"primary_metadata": "Metadatos primarios",
"title_placeholder": "Nombre dado al recurso",
"description_placeholder": "Cuenta del recurso",
"new_files": "Nuevos archivos",
"orphan_files": "+ {{ count }} archivo{{ count > 1 ? 's' : '' }}",
"additional_metadata": "Metadatos adicionales",
"subject_placeholder": "Tema del recurso",
"identifier_placeholder": "Una referencia inequívoca al recurso dentro de un contexto dado",
"publisher_placeholder": "Una entidad responsable de hacer que el recurso esté disponible",
"format_placeholder": "El formato de archivo, medio físico o dimensiones del recurso.",
"source_placeholder": "Un recurso relacionado del cual se deriva el recurso descrito",
"uploading_files": "Subiendo archivos..."
},
"default": {
"upload_pending": "Pendiente...",
"upload_progress": "Subiendo...",
"upload_error": "Error al subir",
"upload_error_quota": "Cuota alcanzada",
"quick_upload": "Subida rápida",
"drop_zone": "Arrastre y suelte archivos aquí para subir",
"add_files": "Añadir archivos",
"add_new_document": "Añadir al nuevo documento",
"latest_activity": "Última actividad",
"footer_sismics": "Hecho a mano con <span class=\"fas fa-heart\"></span> por <a href=\"https://www.sismics.com\" target=\"_blank\">Sismics</a>",
"api_documentation": "Documentación API",
"feedback": "Déjenos un comentario",
"workflow_document_list": "Documentos asignados para usted",
"select_all": "Seleccionar todo",
"select_none": "Deseleccionar"
},
"pdf": {
"export_title": "Exportar a PDF",
"export_metadata": "Exportar metadatos",
"export_comments": "Exportar comentarios",
"fit_to_page": "Ajustar imagen a página",
"margin": "Margen",
"millimeter": "mm"
},
"share": {
"title": "Compartir documento",
"message": "Nombre el uso compartido si desea compartir el mismo documento más veces.",
"submit": "Compartir"
}
},
"file": {
"view": {
"previous": "Anterior",
"next": "Siguiente",
"not_found": "Archivo no encontrado"
},
"edit": {
"title": "Editar archivo",
"name": "Nombre de archivo"
},
"versions": {
"title": "Historial de versiones",
"filename": "Nombre de archivo",
"mimetype": "Tipo",
"create_date": "Fecha de creación",
"version": "Versión"
}
},
"tag": {
"new_tag": "Nueva etiqueta",
"search": "Buscar",
"default": {
"title": "Etiquetas",
"message_1": "<strong>Etiquetas</strong> son rótulos asociados a documentos.",
"message_2": "Un documento puede ser etiquetdo por múltiples etiquetas, y una etiqueta puede ser aplicada a varios documentos.",
"message_3": "Usando el botón <span class=\"fas fa-pencil-alt\"></span> , puedes editar permisos en una etiqueta.",
"message_4": "Si una etiqueta puede ser leida por otro usuario o grupo, los documentos asociados podrán ser leidos por estos también.",
"message_5": "Por ejemplo, etiquete los documentos de su compañia con una etiqueta <span class=\"label label-info\">MiCompañia</span> y añada el permiso <strong>Puede leer</strong> a un grupo <span class=\"btn btn-default\">empleados</span>"
},
"edit": {
"delete_tag_title": "Eliminar etiqueta",
"delete_tag_message": "¿Desea realmente eliminar esta etiqueta?",
"name": "Nombre",
"color": "Color",
"parent": "Principal",
"info": "Los permisos en esta etiqueta también se aplicarán a los documentos etiquetados <span class=\"label label-info\" ng-style=\"{ 'background': color }\">{{ name }}</span>",
"circular_reference_title": "Referencia circular",
"circular_reference_message": "La jerarquía de las etiquetas principales se encuentra en un búcle redundante, elija otra principal."
}
},
"group": {
"profile": {
"members": "Miembros",
"no_members": "Sin miembros",
"related_links": "Enlaces relacionados",
"edit_group": "Editar {{ name }} grupo"
}
},
"user": {
"profile": {
"groups": "Grupos",
"quota_used": "Cuota usada",
"percent_used": "{{ percent | number: 0 }}% Usado",
"related_links": "Enlaces relacionados",
"document_created": "Documentos creados por {{ username }}",
"edit_user": "Editar {{ username }} usuario"
}
},
"usergroup": {
"search_groups": "Buscar en grupos",
"search_users": "Buscar en usuarios",
"you": "¡Eres tú!",
"default": {
"title": "Usuarios y Grupos",
"message": "Aquí puede ver información sobre usuarios y grupos."
}
},
"settings": {
"menu_personal_settings": "Ajustes personales",
"menu_user_account": "Cuenta de usuario",
"menu_two_factor_auth": "Autenticación en dos pasos",
"menu_opened_sessions": "Sesiones abiertas",
"menu_file_importer": "Importador de archivos en masa",
"menu_general_settings": "Ajustes generales",
"menu_workflow": "Flujo de trabajo",
"menu_users": "Usuarios",
"menu_groups": "Grupos",
"menu_vocabularies": "Vocabularios",
"menu_configuration": "Configuración",
"menu_inbox": "Escaneo bandeja de entrada",
"menu_monitoring": "Monitorización",
"user": {
"title": "Administración de usuarios",
"add_user": "Añadir usuario",
"username": "Usuario",
"create_date": "Fecha creación",
"totp_enabled": "Autenticación en dos pasos activada para esta cuenta",
"edit": {
"delete_user_title": "Eliminar usuario",
"delete_user_message": "¿Desea realmente eliminar este usuario? Todos los documentos, archivos y etiquetas asociados serán eliminados",
"edit_user_failed_title": "El usuario ya existe",
"edit_user_failed_message": "Este nombre de usuario ya fue escogido por otro usuario",
"edit_user_title": "Editar \"{{ username }}\"",
"add_user_title": "Añadir usuario",
"username": "Usuario",
"email": "Correo",
"groups": "Grupos",
"storage_quota": "Cuota almacenamiento",
"storage_quota_placeholder": "Cuota de almacenamiento (en MB)",
"password": "Contraseña",
"password_confirm": "Contraseña (confirmar)",
"disabled": "Usuario deshabilitado",
"password_reset_btn": "Enviar correo de reestablecimiento de contraseña a este usuario",
"password_lost_sent_title": "Correo de reestablecimiento enviado",
"password_lost_sent_message": "Un correo de reestablecimiento de contraseña a sido enviado a <strong>{{ username }}</strong>",
"disable_totp_btn": "Desactivar autenticación en dos pasos para este usuario",
"disable_totp_title": "Desactivar autenticación en dos pasos",
"disable_totp_message": "¿Desea realmente desactivar la autenticación en dos pasos para este usuario?"
}
},
"workflow": {
"title": "Configuración flujo de trabajo",
"add_workflow": "Añadir flujo de trabajo",
"name": "Nombre",
"create_date": "Fecha creación",
"edit": {
"delete_workflow_title": "Eliminar flujo de trabajo",
"delete_workflow_message": "¿Desea realmente eliminar este flujo de trabajo? Los flujos de trabajo ejecutándose actualmente no serán eliminados",
"edit_workflow_title": "Editar \"{{ name }}\"",
"add_workflow_title": "Añadir flujo de trabajo",
"name": "Nombre",
"name_placeholder": "Nombre del paso o descripción",
"drag_help": "Arrastre y suelte para reordenar el paso",
"type": "Tipo de paso",
"type_approve": "Aprobar",
"type_validate": "Validar",
"target": "Asignado a",
"target_help": "<strong>Aprobar:</strong> Aceptar o rechazar la revisión <br/><strong>Validar:</strong> Revisar y continuar con el flujo de trabajo",
"add_step": "Añadir un paso de flujo de trabajo",
"actions": "¿Qué ocurre después?",
"remove_action": "Eliminar acción",
"acl_info": "Solo los usuarios y grupos definidos aquí podrán iniciar este flujo de trabajo en un documento"
}
},
"security": {
"enable_totp": "Activar autenticación en dos pasos",
"enable_totp_message": "Asegúrese de tener una aplicación compatible con TOTP en su teléfono lista para agregar una nueva cuenta",
"title": "Autenticación en dos pasos",
"message_1": "La autenticación en dos pasos le permite añadir una capa de seguridad en su <strong>{{ appName }}</strong> cuenta.<br/>Antes de activar esta característica, compruebe que disponga de una app compatible con TOTP en su teléfono:",
"message_google_authenticator": "Para Android, iOS, y Blackberry: <a href=\"https://support.google.com/accounts/answer/1066447\" target=\"_blank\">Google Authenticator</a>",
"message_duo_mobile": "Para Android y iOS: <a href=\"https://guide.duo.com/third-party-accounts\" target=\"_blank\">Duo Mobile</a>",
"message_authenticator": "Para Windows Phone: <a href=\"https://www.microsoft.com/en-US/store/apps/Authenticator/9WZDNCRFJ3RJ\" target=\"_blank\">Authenticator</a>",
"message_2": "Estas aplicaciones generan automaticamente un código de validación que cambia cada cirto tiempo.<br/>Necesitará introducir el código cada vez que acceda a <strong>{{ appName }}</strong>.",
"secret_key": "Su clave secreta es: <strong>{{ secret }}</strong>",
"secret_key_warning": "Configure your TOTP app on your phone with this secret key now, you will not be able to access it later.",
"totp_enabled_message": "La autenticación en dos pasos esta habilitada par su cuenta.<br/>Cada vez que inicie sesión en <strong>{{ appName }}</strong>, se le pedirá el código de su teléfono.<br/>Si pierde su teléfono, no podrá volver a iniciar sesión, pero sesiones activas le permitirán regenerar la clave secreta.",
"disable_totp": {
"disable_totp": "Desactivar autenticación en dos pasos",
"message": "Su cuenta dejará de estar protegida con la autenticación en dos pasos.",
"confirm_password": "Confirme su contraseña",
"submit": "Desactivar autenticación en dos pasos"
},
"test_totp": "Por favor, introduzca el código mostrado en su teléfono :",
"test_code_success": "Código CORRECTO",
"test_code_fail": "El código no es válido, por favor, asegúrerse de que su teléfono esté debidamente configurado, o desactive la autenticación en dos pasos"
},
"group": {
"title": "Administración de grupos",
"add_group": "Añadir grupo",
"name": "Nombre",
"edit": {
"delete_group_title": "Eliminar grupo",
"delete_group_message": "¿Desea realmente eliminar este grupo?",
"edit_group_failed_title": "El grupo ya existe",
"edit_group_failed_message": "Este nombre de grupo ya fue escogido por otro grupo",
"edit_group_title": "Editar \"{{ name }}\"",
"add_group_title": "Añadir grupo",
"name": "Nombre",
"parent_group": "Grupo principal",
"search_group": "Buscar un grupo",
"members": "Miembros",
"new_member": "Nuevo miembro",
"search_user": "Buscar un usuario"
}
},
"account": {
"title": "Cuenta de usuario",
"password": "Contraseña",
"password_confirm": "Contraseña (confirmar)",
"updated": "Cuenta actualizada correctamente"
},
"config": {
"title_guest_access": "Acceso invitado",
"message_guest_access": "Acceso invitado es un modo en el que cualquiera puede acceder {{ appName }} sin contraseña.<br/>Como un usuario normal, el invitado solo puede acceder a sus documentos, y aquellos accesibles a través de permisos.<br/>",
"enable_guest_access": "Activar acceso invitado",
"disable_guest_access": "Desactivar acceso invitado",
"title_theme": "Personalizar apariencia",
"title_general": "Configuración general",
"default_language": "Idioma por defecto para nuevos documentos",
"application_name": "Nombre aplicación",
"main_color": "Color principal",
"custom_css": "CSS personalizado",
"custom_css_placeholder": "CSS personalizado para agregar después de la hoja de estilo principal",
"logo": "Logo (tamaño cuadrado)",
"background_image": "Imagen de fondo",
"uploading_image": "Subiendo la imagen...",
"title_smtp": "Configuración correo",
"smtp_hostname": "Host SMTP",
"smtp_port": "Puerto SMTP",
"smtp_from": "Correo remitente",
"smtp_username": "Usuario SMTP",
"smtp_password": "Contraseña SMTP",
"smtp_updated": "Configuración SMTP actualizada correctamente",
"webhooks": "Webhooks",
"webhooks_explain": "Los webhooks serán llamados cuando ocurra el evento especificado. La URL dada será el POST (editado) con un JSON que contendrá el nombre del evento e ID del recurso en cuestión.",
"webhook_event": "Evento",
"webhook_url": "URL",
"webhook_create_date": "Fecha creación",
"webhook_add": "Añadir Webhook"
},
"inbox": {
"title": "Escaneo bandeja de entrada",
"message": "Al activar esta opción, el sistema escaneará la bandeja especificada cada minuto buscando correos <strong>no leidos</strong>, y los importará automaticamente.<br/>Después de importar un correo, éste será marcado como leido.<br/>Ajustes de configuración para <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>.",
"enabled": "Activar escaneo de bandeja de entrada",
"hostname": "Host IMAP",
"port": "Puerto IMAP (143 o 993)",
"username": "Usuario IMAP",
"password": "Contraseña IMAP",
"tag": "Etiqueta añadida a documentos importado",
"test": "Comprobar parámetros",
"last_sync": "Última sincronización: {{ data.date | date: 'medium' }}, {{ data.count }} mensaje{{ data.count > 1 ? 's' : '' }} importado{{ data.count > 1 ? 's' : '' }}",
"test_success": "La conexión con la bandeja de entrada es correcta ({{ count }} mensaje{{ count > 1 ? 's' : '' }} <strong>no leido{{ count > 1 ? 's' : '' }}</strong>)",
"test_fail": "Ha ocurrido un error al conectar con la bandeja de entrada, por favor, compruebe los parámetros",
"saved": "Configuración IMAP guardada correctamente"
},
"monitoring": {
"background_tasks": "Tareas en segundo plano",
"queued_tasks": "Actualmente hay {{ count }} tarea{{ count > 1 ? 's' : '' }} en cola.",
"queued_tasks_explain": "Procesamiento de archivos, creación de miniaturas, actualización del índice y reconocimiento óptico de caracteres (OCR) son tareas en segundo plano. Gran cantidad de tareas sin procesar resultarán en búsquedas incompletas.",
"server_logs": "Registros del servidor",
"log_date": "Fecha",
"log_tag": "Etiqueta",
"log_message": "Mensaje",
"indexing": "Indexación",
"indexing_info": "Si observa discrepancias en los resultados de búsqueda, puede intentar realizar una reindexación completa. Los resultados de la búsqueda estarán incompletos hasta que se realice esta operación.",
"start_reindexing": "Iniciar reindexación completa",
"reindexing_started": "Reindexación iniciada, por favor, espere hasta que ya no queden tareas en segundo plano."
},
"session": {
"title": "Sesiones abiertas",
"created_date": "Fecha creación",
"last_connection_date": "Última fecha de conexión",
"user_agent": "De",
"current": "Actual",
"current_session": "Esta es la sesión actual",
"clear_message": "El resto de los dispositivos conectados a esta cuenta serán desconectados",
"clear": "Eliminar el resto de las sesiones"
},
"vocabulary": {
"title": "Entradas de vocabulario",
"choose_vocabulary": "Seleccione un vocabulario para editar",
"type": "Tipo",
"coverage": "Cobertura",
"rights": "Derechos",
"value": "Valor",
"order": "Órden",
"new_entry": "Nueva entrada"
},
"fileimporter": {
"title": "Importador de archivos en masa",
"advanced_users": "¡Para usuarios avanzados!",
"need_intro": "Si necesita:",
"need_1": "Importar un directorio de archivos de una vez",
"need_2": "Escanear un directorio en busca de nuevos archivos, e importados",
"line_1": "Vaya a <a href=\"https://github.com/sismics/docs/releases\">sismics/docs/releases</a> y descargue la herramienta 'file importer' para su sistema.",
"line_2": "Siga las <a href=\"https://github.com/sismics/docs/tree/master/docs-importer\">instrucciones aquí</a> para usar esta herramienta.",
"line_3": "Sus archivos serán importados en <a href=\"#/document\">Subida rápida</a>, luego podrá agruparlos en documentos.",
"download": "Descargar",
"instructions": "Instrucciones"
}
},
"feedback": {
"title": "Déjenos un comentario",
"message": "¿Alguna sugerencia o pregunta sobre Teedy? ¡Te escuchamos!",
"sent_title": "Comentario enviado",
"sent_message": "¡Gracias por su comentario! Nos ayudará a hacer Teedy mucho mejor."
},
"import": {
"title": "Importando",
"error_quota": "Límite de cuota alcanzado, contacte a su administrador para incrementarla",
"error_general": "Ha ocurrido un error al intentar importar sus archivos, por favor, asegúrese de que sea un archivo EML válido"
},
"app_share": {
"main": "Pedir enlace al documento para acceder a él",
"403": {
"title": "No autorizado",
"message": "El documento que esta intentando ver ya no se cuentra compartido"
}
},
"directive": {
"acledit": {
"acl_target": "Para",
"acl_permission": "Permiso",
"add_permission": "Añadir un permiso",
"search_user_group": "Buscar un usuario o grupo"
},
"auditlog": {
"log_created": "creado",
"log_updated": "actualizado",
"log_deleted": "eliminado",
"Acl": "ACL",
"Comment": "Comentario",
"Document": "Documento",
"File": "Archivo",
"Group": "Grupo",
"Route": "Flujo de trabajo",
"RouteModel": "Modelo flujo de trabajo",
"Tag": "Etiqueta",
"User": "Usuario",
"Webhook": "Webhook"
},
"selectrelation": {
"typeahead": "Introduzca el título de un documento"
},
"selecttag": {
"typeahead": "Introduzca una etiqueta"
},
"datepicker": {
"current": "Hoy",
"clear": "Borrar",
"close": "Hecho"
}
},
"filter": {
"filesize": {
"mb": "MB",
"kb": "KB"
}
},
"acl": {
"READ": "Puede leer",
"READWRITE": "Puede escribir",
"WRITE": "Puede escribir",
"USER": "Usuario",
"GROUP": "Grupo",
"SHARE": "Compartido"
},
"workflow_type": {
"VALIDATE": "Validación",
"APPROVE": "Aprobación"
},
"workflow_transition": {
"APPROVED": "Aprobado",
"REJECTED": "Rechazado",
"VALIDATED": "Validado"
},
"validation": {
"required": "Requerido",
"too_short": "Muy corto",
"too_long": "Muy largo",
"email": "Debe ser un correo válido",
"password_confirm": "Contraseña y Contraseña (confirmar) deben coincidir",
"number": "Número requerido",
"no_space": "Los espacios no estan permitidos"
},
"action_type": {
"ADD_TAG": "Añadir etiqueta",
"REMOVE_TAG": "Eliminar etiqueta",
"PROCESS_FILES": "Procesar archivos"
},
"pagination": {
"previous": "Anterior",
"next": "Siguiente",
"first": "Primero",
"last": "Último"
},
"onboarding": {
"step1": {
"title": "¿Primera vez?",
"description": "Si es su primera vez en Teedy, presione el botón Siguiente, de otro modo ciérreme si así lo desea."
},
"step2": {
"title": "Documentos",
"description": "Teedy esta organizado en documentos y cada uno contiene múltiples archivos."
},
"step3": {
"title": "Archivos",
"description": "Puede agregar archivos después de crear un documento, o antes de usar la zona de subida rápida."
},
"step4": {
"title": "Buscar",
"description": "Esta es la forma más rápida de buscar un documento. Está disponible una búsqueda avanzada con el botón lupa."
},
"step5": {
"title": "Etiquetas",
"description": "Los documentos pueden ser organizados en etiquetas (las cuales son como super-carpetas). Creelas aquí."
}
},
"ok": "OK",
"cancel": "Cancelar",
"share": "Compartir",
"unshare": "No compartir",
"close": "Cerrar",
"add": "Añadir",
"open": "Abrir",
"see": "Ver",
"save": "Guardar",
"export": "Exportar",
"edit": "Editar",
"delete": "Eliminar",
"rename": "Renombrar",
"loading": "Cargando...",
"send": "Enviar",
"enabled": "Activado",
"disabled": "Desactivado"
}

View File

@@ -325,7 +325,7 @@
"type_approve": "Approbation",
"type_validate": "Validation",
"target": "Assigné à",
"target_help": "<strong>Approbation :</strong> Accepter ou rejeter l'étape de workflow<br/><strong>Validation :</strong> Examiner et poursuivre le workflow",
"target_help": "<strong>Approbation :</strong> Accepter ou rejeter l\\'étape de workflow<br/><strong>Validation :</strong> Examiner et poursuivre le workflow",
"add_step": "Ajouter une étape de workflow",
"actions": "Qu'est-ce qui se passe après?",
"remove_action": "Supprimer l'action",
@@ -464,16 +464,16 @@
"need_2": "Analyser un répertoire à la recherche de nouveaux fichiers et les importer",
"line_1": "Allez sur <a href=\"https://github.com/sismics/docs/releases\">sismics/docs/releases</a> et téléchargez l'outil d'importation de fichiers pour votre système.",
"line_2": "Suivez les <a href=\"https://github.com/sismics/docs/tree/master/docs-importer\">instructions ici</a> pour utiliser cet outil.",
"line_3": "Vos fichiers seront importés dans <a href=\"#/document\">Envoi rapide</a>, vous pouvez ensuite les regrouper dans des documents.",
"line_3": "Vos fichiers seront importés dans des documents conformément à la configuration de l'importeur.",
"download": "Télécharger",
"instructions": "Instructions"
}
},
"feedback": {
"title": "Donnez-nous votre avis",
"message": "Vous avez des suggestions ou des questions à propos de Sismics Docs ? Nous vous écoutons !",
"message": "Vous avez des suggestions ou des questions à propos de Teedy ? Nous vous écoutons !",
"sent_title": "Avis envoyé",
"sent_message": "Merci pour votre avis ! Cela nous aidera à améliorer Sismics Docs."
"sent_message": "Merci pour votre avis ! Cela nous aidera à améliorer Teedy."
},
"import": {
"title": "Import en cours",
@@ -567,11 +567,11 @@
"onboarding": {
"step1": {
"title": "Première fois ici ?",
"description": "Si vous utilisez Sismics Docs pour la première fois, cliquez sur le bouton Suivant. Sinon, n'hésitez pas à me fermer."
"description": "Si vous utilisez Teedy pour la première fois, cliquez sur le bouton Suivant. Sinon, n'hésitez pas à me fermer."
},
"step2": {
"title": "Documents",
"description": "Sismics Docs est organisé en documents et chaque document contient plusieurs fichiers."
"description": "Teedy est organisé en documents et chaque document contient plusieurs fichiers."
},
"step3": {
"title": "Fichiers",
@@ -586,6 +586,8 @@
"description": "Les documents peuvent être organisés en tags (qui sont comme des super-dossiers). Créez-les ici."
}
},
"yes": "Oui",
"no": "Non",
"ok": "OK",
"cancel": "Annuler",
"share": "Partager",
@@ -599,6 +601,7 @@
"edit": "Modifier",
"delete": "Supprimer",
"rename": "Renommer",
"download": "Télécharger",
"loading": "Chargement...",
"send": "Envoyer",
"enabled": "Activé",

View File

@@ -404,9 +404,9 @@
},
"feedback": {
"title": "Оставьте нам ваше мнение",
"message": "Любое предложение или вопрос о Sismics Docs? Мы слушаем вас!",
"message": "Любое предложение или вопрос о Teedy? Мы слушаем вас!",
"sent_title": "Обратная связь отправлена",
"sent_message": "Спасибо за ваш отзыв! Это поможет нам улучшить работу Sismics Docs."
"sent_message": "Спасибо за ваш отзыв! Это поможет нам улучшить работу Teedy."
},
"import": {
"title": "Импорт",

View File

@@ -404,9 +404,9 @@
},
"feedback": {
"title": "欢迎提供反馈意见",
"message": "您是否对Sismics Docs有任何建议或疑问?我们愿意倾听您的反馈意见!",
"message": "您是否对Teedy有任何建议或疑问?我们愿意倾听您的反馈意见!",
"sent_title": "反馈意见已发送",
"sent_message": "非常感谢您的反馈意见!这将帮我们进一步改进Sismics Docs从而更好的为您提供服务。"
"sent_message": "非常感谢您的反馈意见!这将帮我们进一步改进Teedy从而更好的为您提供服务。"
},
"import": {
"title": "输入",

View File

@@ -404,9 +404,9 @@
},
"feedback": {
"title": "歡迎提供反饋意見",
"message": "對Sismics Docs文檔管理系統有任何建議或疑問?我們靜候您的反饋意見!",
"message": "對Teedy文檔管理系統有任何建議或疑問?我們靜候您的反饋意見!",
"sent_title": "反饋已發送",
"sent_message": "感謝您的反饋意見!這將幫助我們進一步優化Sismics Docs文檔管理系統以便更好的為您提供服務。"
"sent_message": "感謝您的反饋意見!這將幫助我們進一步優化Teedy文檔管理系統以便更好的為您提供服務。"
},
"import": {
"title": "輸入",

View File

@@ -1,6 +1,6 @@
{
"name": "Sismics Docs",
"short_name": "Sismics Docs",
"name": "Teedy",
"short_name": "Teedy",
"theme_color": "#2ab2dc",
"background_color": "#ffffff",
"display": "standalone",

View File

@@ -72,6 +72,42 @@
<select-tag tags="document.tags" ref="inputTags" ng-disabled="fileIsUploading"></select-tag>
</div>
</div>
<!-- Custom metadata -->
<div class="form-group"
ng ng-repeat="meta in document.metadata"
ng-class="{ 'has-error': !documentForm[meta.id].$valid && documentForm.$dirty }">
<label class="col-sm-2 control-label" for="inputTitle">{{ meta.name }}</label>
<div class="col-sm-10">
<input ng-if="meta.type == 'STRING'"
ng-maxlength="4000" class="form-control" type="text" id="input{{ meta.id }}"
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" />
<input ng-if="meta.type == 'INTEGER'"
ng-pattern="/^[0-9]*$/" class="form-control" type="text" id="input{{ meta.id }}"
name="{{ meta.id }}" ng-model="meta.value" autocomplete="off"
ng-disabled="fileIsUploading" />
<input ng-if="meta.type == 'FLOAT'"
ng-pattern="/^-?[0-9]*\.?[0-9]*$/" class="form-control" type="text" id="input{{ meta.id }}"
name="{{ meta.id }}" ng-model="meta.value" autocomplete="off"
ng-disabled="fileIsUploading" />
<input type="checkbox" ng-if="meta.type == 'BOOLEAN'"
id="input{{ meta.id }}" name="{{ meta.id }}"
ng-model="meta.value" ng-disabled="fileIsUploading" />
</div>
</div>
</fieldset>
<fieldset ng-init="additionalMetadataCollapsed = true">

Some files were not shown because too many files have changed in this diff Show More