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>.
**The default admin password is "admin". Don't forget to change it before going to production.**
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.
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.
FQDN: http://docs.sismics.com
IP: http://192.168.100.10
**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`
The data directory is `/data`. Don't forget to mount a volume on it.
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

@@ -15,7 +15,7 @@
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
android:theme="@style/AppTheme">
<activity
android:name=".activity.LoginActivity"
android:label="@string/app_name"

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,23 +9,22 @@
<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">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:layout_scrollFlags="enterAlways|scroll|snap" />
android:layout_height="wrap_content">
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="@style/AppTheme"
app:layout_scrollFlags="enterAlways|scroll|snap" />
</android.support.design.widget.AppBarLayout>
<fragment
android:id="@+id/main_fragment"

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
@@ -233,10 +218,28 @@ public class DocumentDao {
documentDb.setLanguage(document.getLanguage());
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

@@ -46,7 +46,13 @@ 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,8 +228,11 @@ 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) {
users.add(Json.createObjectBuilder()
.add("name", userDto.getUsername()));
// 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
@@ -237,8 +240,11 @@ public class AclResource extends BaseResource {
JsonArrayBuilder groups = Json.createArrayBuilder();
List<GroupDto> groupDtoList = groupDao.findByCriteria(new GroupCriteria().setSearch(search), sortCriteria);
for (GroupDto groupDto : groupDtoList) {
groups.add(Json.createObjectBuilder()
.add("name", groupDto.getName()));
// 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()

View File

@@ -234,7 +234,10 @@ public class DocumentResource extends BaseResource {
step.add("transitionable", getTargetIdList(null).contains(routeStepDto.getTargetId()));
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,7 +483,11 @@ public class DocumentResource extends BaseResource {
tagIdList.add(childrenTagDto.getId());
}
}
documentCriteria.getTagIdList().add(tagIdList);
if (params[0].startsWith("!")) {
documentCriteria.getExcludedTagIdList().add(tagIdList);
} else {
documentCriteria.getTagIdList().add(tagIdList);
}
}
break;
case "after":
@@ -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()) {
@@ -666,7 +683,7 @@ public class DocumentResource extends BaseResource {
if (!Constants.SUPPORTED_LANGUAGES.contains(language)) {
throw new ClientException("ValidationError", MessageFormat.format("{0} is not a supported language", language));
}
// Create the document
Document document = new Document();
document.setUserId(principal.getId());
@@ -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()) {
@@ -816,7 +844,14 @@ 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());
@@ -960,15 +995,30 @@ 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;
@@ -148,6 +150,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)
@@ -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
*
@@ -197,7 +208,13 @@ public class GroupResource extends BaseResource {
throw new ClientException("ForbiddenError", "The administrators group cannot be deleted");
}
}
// 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
*
@@ -449,6 +453,12 @@ public class UserResource extends BaseResource {
if (hasBaseFunction(BaseFunction.ADMIN) || principal.isGuest()) {
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();
@@ -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) {
@@ -525,6 +536,12 @@ public class UserResource extends BaseResource {
if (baseFunctionSet.contains(BaseFunction.ADMIN.name())) {
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();
@@ -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,8 +667,9 @@ 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();
for (String baseFunction : ((UserPrincipal) principal).getBaseFunctionSet()) {
@@ -882,6 +901,39 @@ public class UserResource extends BaseResource {
.add("status", "ok");
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,48 +145,51 @@ angular.module('docs').controller('DocumentDefault', function ($scope, $rootScop
// Onboarding
$translate('onboarding.step1.title').then(function () {
if (localStorage.onboardingDisplayed || $(window).width() < 1000) {
return;
}
localStorage.onboardingDisplayed = true;
$rootScope.onboardingEnabled = true;
$rootScope.onboardingSteps = [
{
title: $translate.instant('onboarding.step1.title'),
description: $translate.instant('onboarding.step1.description'),
position: 'centered',
width: 300
},
{
title: $translate.instant('onboarding.step2.title'),
description: $translate.instant('onboarding.step2.description'),
attachTo: '#document-add-btn',
position: 'right',
width: 300
},
{
title: $translate.instant('onboarding.step3.title'),
description: $translate.instant('onboarding.step3.description'),
attachTo: '#quick-upload-zone',
position: 'left',
width: 300
},
{
title: $translate.instant('onboarding.step4.title'),
description: $translate.instant('onboarding.step4.description'),
attachTo: '#search-box',
position: 'right',
width: 300
},
{
title: $translate.instant('onboarding.step5.title'),
description: $translate.instant('onboarding.step5.description'),
attachTo: '#navigation-tag',
position: "right",
width: 300
User.userInfo().then(function(userData) {
if (!userData.onboarding || $(window).width() < 1000) {
return;
}
];
Restangular.one('user').post('onboarded');
$rootScope.userInfo.onboarding = false;
$rootScope.onboardingEnabled = true;
$rootScope.onboardingSteps = [
{
title: $translate.instant('onboarding.step1.title'),
description: $translate.instant('onboarding.step1.description'),
position: 'centered',
width: 300
},
{
title: $translate.instant('onboarding.step2.title'),
description: $translate.instant('onboarding.step2.description'),
attachTo: '#document-add-btn',
position: 'right',
width: 300
},
{
title: $translate.instant('onboarding.step3.title'),
description: $translate.instant('onboarding.step3.description'),
attachTo: '#quick-upload-zone',
position: 'left',
width: 300
},
{
title: $translate.instant('onboarding.step4.title'),
description: $translate.instant('onboarding.step4.description'),
attachTo: '#search-box',
position: 'right',
width: 300
},
{
title: $translate.instant('onboarding.step5.title'),
description: $translate.instant('onboarding.step5.description'),
attachTo: '#navigation-tag',
position: "right",
width: 300
}
];
});
});
});

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);
}
@@ -92,7 +101,21 @@ 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) {
$state.go('document.view.content.file', { id: $stateParams.id, fileId: file.id })
$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

@@ -46,12 +46,17 @@ angular.module('docs').controller('SettingsGroupEdit', function($scope, $dialog,
$state.go('settings.group.edit', { name: group.name });
}
}, function (e) {
if (e.data.type === 'GroupAlreadyExists') {
var title = $translate.instant('settings.group.edit.edit_group_failed_title');
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);
}
if (e.data.type === 'GroupAlreadyExists') {
var title = $translate.instant('settings.group.edit.edit_group_failed_title');
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",

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