Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9867875ef2 |
@@ -3,3 +3,4 @@ node_modules/
|
|||||||
/.env
|
/.env
|
||||||
.idea
|
.idea
|
||||||
express/public/stylesheets
|
express/public/stylesheets
|
||||||
|
/draft/
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 Amebis, d.o.o.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -1,5 +1,166 @@
|
|||||||
# Terminološki portal
|
# Terminology Portal
|
||||||
|
|
||||||
## Navodila za namesitev
|
## Quick Overview
|
||||||
|
|
||||||
Navodila so zaradi dinamičnih sprememb v integriranih zunanjih komponentah še v pripravi in bodo pripravljena, ko bo celota stabilnejša.
|
An opensource Terminology portal which anybody can set up (hopefully without much trouble).
|
||||||
|
|
||||||
|
The main instance is available at [https://terminoloski.slovenscina.eu](https://terminoloski.slovenscina.eu).
|
||||||
|
|
||||||
|
You can use your own instance completely independently or link it with other such instances, including the main one, and have them share dictionaries and their entries.
|
||||||
|
|
||||||
|
Likewise, the included consultancy module can use its built-in, fully featured tools for your consultants to process terminology questions (default)
|
||||||
|
or simply be linked with the one at [ISJFR, ZRC SAZU](https://isjfr.zrc-sazu.si) so that their professional consultants will answer the questions.
|
||||||
|
|
||||||
|
## Setting up your own instance of the portal
|
||||||
|
|
||||||
|
_The guide is written for Ubuntu 22.04 LTS. Adjust according to your OS._
|
||||||
|
|
||||||
|
### 1. Install external dependencies first
|
||||||
|
|
||||||
|
1. Install the [Docker Engine](https://docs.docker.com/engine/install/).
|
||||||
|
2. Install the [Term Candidate Extraction API](https://github.com/clarinsi/rsdo_luscilnik).
|
||||||
|
3. Make sure you have a SMTP server/service available, which the portal will require.
|
||||||
|
4. Install a reverse proxy (e.g. [Nginx](https://nginx.org/en/)).
|
||||||
|
|
||||||
|
### 2. Reconfigure the docker engine
|
||||||
|
|
||||||
|
By default, docker engine doesn't perform log rotation, which will eventually lead to disk exhaustion.
|
||||||
|
Search indexes will prevent all write operations at 90% full which will make many operations on the portal fail until
|
||||||
|
disk usage falls under 85% again.
|
||||||
|
|
||||||
|
Also, by default, image build process is not optimized.
|
||||||
|
|
||||||
|
To address both of the above, it is suggested to add the following to the _/etc/docker/daemon.json_ :
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"log-driver": "local",
|
||||||
|
"features": {
|
||||||
|
"buildkit": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Even better, set `"log-driver": "journald"` instead, so the logs get sent straight to your journald.
|
||||||
|
Alternatively, set your own preferred log aggregator as per [the official documentation](https://docs.docker.com/config/containers/logging/configure/).
|
||||||
|
|
||||||
|
**You need to restart the engine for the settings to take effect. Run:** `sudo systemctl restart docker.service`
|
||||||
|
|
||||||
|
### 3. Get and configure the portal
|
||||||
|
|
||||||
|
1. Using [git](https://git-scm.com/) transfer the portal code into desired directory with `git clone https://github.com/clarinsi/rsdo_term_portal.git <directory path>`
|
||||||
|
2. Navigate into that directory and copy the _.env_ file from the _dev_ folder one level up (into the root directory of the portal).
|
||||||
|
3. Open the copied _.env_ file in a text editor and adjust the parameters of your portal.
|
||||||
|
|
||||||
|
### 4. (Advanced/optional) Adjust data storage locations
|
||||||
|
|
||||||
|
By default, all data is stored and persisted inside Docker's [volumes](https://docs.docker.com/storage/volumes/)
|
||||||
|
on your host's root partition.
|
||||||
|
If you have a setup where for any reason you want to store data elsewhere,
|
||||||
|
you can reconfigure the docker-compose.prod.yml to use [bind mounts](https://docs.docker.com/storage/bind-mounts/),
|
||||||
|
just be mindful not to break shared volume dependencies.
|
||||||
|
|
||||||
|
_Using bind mounts has not been tested yet and might cause additional complications
|
||||||
|
regarding file system permissions._
|
||||||
|
|
||||||
|
### 5. Start the portal service cluster
|
||||||
|
|
||||||
|
1. Inside the portal directory, run the following command in CLI: `docker compose -f docker-compose.prod.yml --profile concordancer-manager up -d --build`
|
||||||
|
2. It's going to take some time for all the resources to get downloaded and built.
|
||||||
|
Wait for control to be returned to CLI before continuing.
|
||||||
|
|
||||||
|
### 6. First time concordancer setup
|
||||||
|
|
||||||
|
1. Open a bash session inside the _concordancer-manager_ container by running:
|
||||||
|
`docker exec -it $(docker ps --format "{{.Names}}" | grep concordancer-manager) bash`
|
||||||
|
2. In that session, run: `dotnet Rsdo.Concordancer.SystemManager.dll createMasterDb --connectionString="Host=postgres;Username=concordancer;Password=<POSTGRES_CONCORDANCER_PASSWORD>;Database=postgres"`.
|
||||||
|
Replace `<POSTGRES_CONCORDANCER_PASSWORD>` with the value you set for it in the _.env_ file.
|
||||||
|
3. After that, also in that session, run: `dotnet Rsdo.Concordancer.SystemManager.dll importSloleks --sourceFile=/sloleks/Sloleks2.0.LMF/sloleks_clarin_2.0.xml`.
|
||||||
|
This command takes a bit more time than the previous one to finish.
|
||||||
|
4. Close the session _(Ctrl + D)_
|
||||||
|
|
||||||
|
### 7. Reverse proxy setup
|
||||||
|
|
||||||
|
1. Forward the majority of trafic to the the main port,
|
||||||
|
set appropriate headers, needed for normal functioning of the webserver
|
||||||
|
and increase the maximum allowed request body size.
|
||||||
|
Example location block for Nginx config:
|
||||||
|
|
||||||
|
```
|
||||||
|
location / {
|
||||||
|
proxy_set_header X-Forwarded-Host $host:$server_port;
|
||||||
|
proxy_set_header X-Forwarded-Server $host;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
client_max_body_size 1100M;
|
||||||
|
proxy_pass http://127.0.0.1:<EXPRESS_LISTEN_PORT>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace <EXPRESS_LISTEN_PORT> with whichever port you set as EXPRESS_LISTEN_PORT in the .env file,
|
||||||
|
or even change the IP address or protocol if you've done done some changes to docker-compose.prod.yml or other system wide configuration and generally know, what you're doing.
|
||||||
|
|
||||||
|
2. Forward concordancer client trafic to the _/korpus_ path
|
||||||
|
Example location block for Nginx config:
|
||||||
|
|
||||||
|
```
|
||||||
|
location /korpus/ {
|
||||||
|
proxy_pass http://127.0.0.1:<CONCORDANCER_LISTEN_PORT>/;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Same as above, except replace <CONCORDANCER_LISTEN_PORT> with the value of CONCORDANCER_LISTEN_PORT.
|
||||||
|
|
||||||
|
3. Don't forget setting up proper SSL termination, compression, ...
|
||||||
|
|
||||||
|
4. Also don't forget to make your changes take effect.
|
||||||
|
For Nginx, `sudo systemctl reload nginx.service` is usually the easiest way.
|
||||||
|
|
||||||
|
### 8. Done
|
||||||
|
|
||||||
|
Your portal should now be available at whichever address/domain your reverse proxy is set to listen to.
|
||||||
|
|
||||||
|
## Linking your instance with others
|
||||||
|
|
||||||
|
1. Using a web browser, navigate to your portal's main page.
|
||||||
|
2. In the upper right corner, change language to English.
|
||||||
|
(the instructions are written using english UI labels)
|
||||||
|
3. Log in as the portal admin user.
|
||||||
|
4. Navigate Menu -> Administration -> Links -> Add link.
|
||||||
|
5. Fill in all the fields:
|
||||||
|
|
||||||
|
- **Portal name** - your choice; it'll be displayed as the source of found entries, when the users use the portal's search tool.
|
||||||
|
|
||||||
|
- **Portal label** - a 2 letter code of your choice; same as portal name, except a shorter version.
|
||||||
|
|
||||||
|
- **URL (dictionaries sync)** - Format: <target_portal_origin>/api/v1/system/inter-instance-sync/dictionaries
|
||||||
|
|
||||||
|
Replace <target_portal_origin> with the origin of the portal, the dictionaries of which you wish to add to your portal.
|
||||||
|
|
||||||
|
To link the main instance of the portal, for example, you would enter:
|
||||||
|
https://terminoloski.slovenscina.eu/api/v1/system/inter-instance-sync/dictionaries
|
||||||
|
|
||||||
|
- **URL (dictionary entries sync)** - Format: <target_portal_origin>/api/v1/system/inter-instance-sync/dictionary/$SOURCE_ID/entries?lastSynced=$SINCE
|
||||||
|
|
||||||
|
<target_portal_origin> should be the same as for previous URL.
|
||||||
|
|
||||||
|
To link the main instance of the portal, for example, you would enter:
|
||||||
|
https://terminoloski.slovenscina.eu/api/v1/system/inter-instance-sync/dictionary/$SOURCE_ID/entries?lastSynced=$SINCE
|
||||||
|
|
||||||
|
6. Click _Add link_. You will be returned to the list of linked portals, where the newly created portal will now be listed.
|
||||||
|
7. Click _Dictionaries_ to see the list of all available dictionaries of the target portal. Enable the ones you wish to be added to your portal, click _Save_.
|
||||||
|
8. The selected dictionaries and all their entries will be automatically synchronized each night.
|
||||||
|
|
||||||
|
## Linking to ZRC SAZU Terminological counselling
|
||||||
|
|
||||||
|
If you wish professional counselors to answer your users' terminological questions,
|
||||||
|
you can do so by simply toggling a switch in the administration console.
|
||||||
|
|
||||||
|
1. As previously described, navigate to the Menu -> Administration -> Basic settings -> Consultancy.
|
||||||
|
2. Select _ZRC SAZU Terminological counselling_.
|
||||||
|
3. Click Save.
|
||||||
|
|
||||||
|
That's it.
|
||||||
|
Your questions will now be automatically forwarded to ZRC SAZU Terminological counselling
|
||||||
|
and their answers back to your portal.
|
||||||
|
|||||||
@@ -2,5 +2,5 @@ FROM alpine:3.16 AS builder
|
|||||||
WORKDIR /sloleks
|
WORKDIR /sloleks
|
||||||
RUN wget -qO- "https://www.clarin.si/repository/xmlui/bitstream/handle/11356/1230/Sloleks2.0.LMF.zip?sequence=3&isAllowed=y" | unzip -
|
RUN wget -qO- "https://www.clarin.si/repository/xmlui/bitstream/handle/11356/1230/Sloleks2.0.LMF.zip?sequence=3&isAllowed=y" | unzip -
|
||||||
|
|
||||||
FROM ghcr.io/clarinsi/rsdo-concordancer-api:v1.0.0
|
FROM ghcr.io/clarinsi/rsdo-concordancer-api:v1.0.3
|
||||||
COPY --from=builder /sloleks /sloleks
|
COPY --from=builder /sloleks /sloleks
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
FROM alpine:3.16 AS builder
|
||||||
|
WORKDIR /sloleks
|
||||||
|
RUN wget -qO- "https://www.clarin.si/repository/xmlui/bitstream/handle/11356/1230/Sloleks2.0.LMF.zip?sequence=3&isAllowed=y" | unzip -
|
||||||
|
|
||||||
|
FROM ghcr.io/clarinsi/rsdo-concordancer-api-term-portal:v1.0.3
|
||||||
|
COPY --from=builder /sloleks /sloleks
|
||||||
@@ -1,26 +1,66 @@
|
|||||||
# Initial values are default vaules.
|
# PORTAL PARAMETERS
|
||||||
# Change if needed.
|
|
||||||
|
|
||||||
|
# Initial values are default vaules.
|
||||||
|
# Changing at least all passwords and secrets is strongly advised.
|
||||||
|
# Preferably at least 16 chars longs and randomly generated.
|
||||||
|
|
||||||
|
|
||||||
|
# Initial values for the admin user (portal administrator role),
|
||||||
|
# that will be created on first startup.
|
||||||
|
PORTAL_ADMIN_INITIAL_EMAIL="admin@email.com"
|
||||||
|
PORTAL_ADMIN_INITIAL_PASSWORD="admin"
|
||||||
|
|
||||||
|
|
||||||
|
# The endpoint where you've made Term Candidate Extraction API available.
|
||||||
|
# It is required for the extraction module to work properly.
|
||||||
|
EXTRACTION_API_ORIGIN="http://rsdo.lhrs.feri.um.si:8080"
|
||||||
|
|
||||||
|
|
||||||
|
# The main port, which will be exposed on localhost of the of host running the Docker Engine.
|
||||||
|
# If you need to expose it on all of host's network interfaces,
|
||||||
|
# modify docker-compose.prod.yml in project's root directory where it is used.
|
||||||
EXPRESS_LISTEN_PORT=3000
|
EXPRESS_LISTEN_PORT=3000
|
||||||
|
# Set it to true for production and properly configure a reverse proxy in front of it.
|
||||||
EXPRESS_IS_BEHIND_PROXY=false
|
EXPRESS_IS_BEHIND_PROXY=false
|
||||||
|
# It is used for cookie signing.
|
||||||
EXPRESS_SECRET="weak_secret"
|
EXPRESS_SECRET="weak_secret"
|
||||||
|
|
||||||
|
|
||||||
|
# Used for administration.
|
||||||
POSTGRES_ADMIN_PASSWORD="weak_admin_password" # user: postgres
|
POSTGRES_ADMIN_PASSWORD="weak_admin_password" # user: postgres
|
||||||
|
# Used by the express webserver service.
|
||||||
POSTGRES_EXPRESS_PASSWORD="weak_express_password" # user: express
|
POSTGRES_EXPRESS_PASSWORD="weak_express_password" # user: express
|
||||||
|
# Used by the concordancer service.
|
||||||
POSTGRES_CONCORDANCER_PASSWORD="weak_concordancer_password" # user: concordancer
|
POSTGRES_CONCORDANCER_PASSWORD="weak_concordancer_password" # user: concordancer
|
||||||
|
# Exposed on host's localhost for administration purposes.
|
||||||
POSTGRES_LISTEN_PORT=5432
|
POSTGRES_LISTEN_PORT=5432
|
||||||
|
|
||||||
|
|
||||||
|
# Configuration for webserver to communicate with your SMTP provider.
|
||||||
SMTP_HOST="maildev"
|
SMTP_HOST="maildev"
|
||||||
SMTP_PORT=1025
|
SMTP_PORT=1025
|
||||||
SMTP_TLS_REJECT_UNAUTHORIZED=true
|
SMTP_USER=""
|
||||||
|
SMTP_PASSWORD=""
|
||||||
|
SMTP_SECURE=false # Most often only "true" for port 465.
|
||||||
|
SMTP_REQUIRE_TLS=false # If "true" and SMTP_SECURE is "false", only send messages if server supports STARTTLS.
|
||||||
|
SMTP_TLS_ALLOW_INVALID_CERTS=false
|
||||||
SMTP_FROM="Sender name <sender@server.com>"
|
SMTP_FROM="Sender name <sender@server.com>"
|
||||||
|
|
||||||
|
|
||||||
|
# Exposed on host's localhost for search index administration purposes.
|
||||||
|
# Used only if docker compose was ran with opensearch-dashboards profile.
|
||||||
OS_DASHBOARDS_LISTEN_PORT=3002
|
OS_DASHBOARDS_LISTEN_PORT=3002
|
||||||
|
|
||||||
|
|
||||||
|
# The second port, which will be exposed on localhost of the of host running the Docker Engine.
|
||||||
|
# Used by the concordancer client.
|
||||||
CONCORDANCER_LISTEN_PORT=3003
|
CONCORDANCER_LISTEN_PORT=3003
|
||||||
|
|
||||||
# Development only settings
|
|
||||||
MAILDEV_WEB_GUI_PORT=3001
|
|
||||||
|
|
||||||
# Production only settings
|
# Production only settings
|
||||||
|
# The origin, where the portal will be made available.
|
||||||
URL_ORIGIN="https://mywebportal.com"
|
URL_ORIGIN="https://mywebportal.com"
|
||||||
|
|
||||||
|
|
||||||
|
# Development only settings (not used in production)
|
||||||
|
MAILDEV_WEB_GUI_PORT=3001
|
||||||
|
|||||||
@@ -21,16 +21,23 @@ services:
|
|||||||
PGDATABASE: term_portal
|
PGDATABASE: term_portal
|
||||||
SMTP_HOST: "${SMTP_HOST:?}"
|
SMTP_HOST: "${SMTP_HOST:?}"
|
||||||
SMTP_PORT: "${SMTP_PORT:?}"
|
SMTP_PORT: "${SMTP_PORT:?}"
|
||||||
SMTP_TLS_REJECT_UNAUTHORIZED: "${SMTP_TLS_REJECT_UNAUTHORIZED:?}"
|
SMTP_USER: "${SMTP_USER?}"
|
||||||
|
SMTP_PASSWORD: "${SMTP_PASSWORD?}"
|
||||||
|
SMTP_SECURE: "${SMTP_SECURE:?}"
|
||||||
|
SMTP_REQUIRE_TLS: "${SMTP_REQUIRE_TLS:?}"
|
||||||
|
SMTP_TLS_ALLOW_INVALID_CERTS: "${SMTP_TLS_ALLOW_INVALID_CERTS:?}"
|
||||||
SMTP_FROM: "${SMTP_FROM:?}"
|
SMTP_FROM: "${SMTP_FROM:?}"
|
||||||
ORIGIN: "${URL_ORIGIN:?}"
|
ORIGIN: "${URL_ORIGIN:?}"
|
||||||
|
PORTAL_ADMIN_INITIAL_EMAIL: "${PORTAL_ADMIN_INITIAL_EMAIL:?}"
|
||||||
|
PORTAL_ADMIN_INITIAL_PASSWORD: "${PORTAL_ADMIN_INITIAL_PASSWORD:?}"
|
||||||
|
EXTRACTION_API_ORIGIN: "${EXTRACTION_API_ORIGIN:?}"
|
||||||
volumes:
|
volumes:
|
||||||
- express-data:/usr/src/app/data_files
|
- express-data:/usr/src/app/data_files
|
||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:${EXPRESS_LISTEN_PORT:?}:3000"
|
- "127.0.0.1:${EXPRESS_LISTEN_PORT:?}:3000"
|
||||||
|
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:15-alpine
|
image: postgres:15.1-alpine3.17
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_PASSWORD: "${POSTGRES_ADMIN_PASSWORD:?}"
|
POSTGRES_PASSWORD: "${POSTGRES_ADMIN_PASSWORD:?}"
|
||||||
@@ -58,18 +65,18 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
- cluster.name=term-portal
|
- cluster.name=term-portal
|
||||||
- node.name=node-1
|
- node.name=node-1
|
||||||
# - bootstrap.memory_lock=true # along with the memlock settings below, disables swapping
|
- bootstrap.memory_lock=true # along with the memlock settings below, disables swapping
|
||||||
# - "OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m" # minimum and maximum Java heap size, recommend setting both to 50% of system RAM
|
- "OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m" # minimum and maximum Java heap size, recommend setting both to 50% of system RAM
|
||||||
- "DISABLE_INSTALL_DEMO_CONFIG=true" # disables execution of install_demo_configuration.sh bundled with security plugin, which installs demo certificates and security configurations to OpenSearch
|
- "DISABLE_INSTALL_DEMO_CONFIG=true" # disables execution of install_demo_configuration.sh bundled with security plugin, which installs demo certificates and security configurations to OpenSearch
|
||||||
- "DISABLE_SECURITY_PLUGIN=true" # disables security plugin entirely in OpenSearch by setting plugins.security.disabled: true in opensearch.yml
|
- "DISABLE_SECURITY_PLUGIN=true" # disables security plugin entirely in OpenSearch by setting plugins.security.disabled: true in opensearch.yml
|
||||||
- "discovery.type=single-node" # disables bootstrap checks that are enabled when network.host is set to a non-loopback address
|
- "discovery.type=single-node" # disables bootstrap checks that are enabled when network.host is set to a non-loopback address
|
||||||
# ulimits:
|
ulimits:
|
||||||
# memlock:
|
memlock:
|
||||||
# soft: -1
|
soft: -1
|
||||||
# hard: -1
|
hard: -1
|
||||||
# nofile:
|
nofile:
|
||||||
# soft: 65536 # maximum number of open files for the OpenSearch user, set to at least 65536 on modern systems
|
soft: 65536 # maximum number of open files for the OpenSearch user, set to at least 65536 on modern systems
|
||||||
# hard: 65536
|
hard: 65536
|
||||||
volumes:
|
volumes:
|
||||||
- opensearch-data:/usr/share/opensearch/data
|
- opensearch-data:/usr/share/opensearch/data
|
||||||
|
|
||||||
@@ -88,8 +95,10 @@ services:
|
|||||||
- "127.0.0.1:${OS_DASHBOARDS_LISTEN_PORT:?}:5601"
|
- "127.0.0.1:${OS_DASHBOARDS_LISTEN_PORT:?}:5601"
|
||||||
|
|
||||||
concordancer:
|
concordancer:
|
||||||
# image: ghcr.io/clarinsi/rsdo-concordancer-api:v1.0.0
|
# image: ghcr.io/clarinsi/rsdo-concordancer-api-term-portal:v1.0.3
|
||||||
build: concordancer
|
build:
|
||||||
|
context: concordancer
|
||||||
|
dockerfile: Dockerfile.prod
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres
|
- postgres
|
||||||
- opensearch
|
- opensearch
|
||||||
@@ -106,7 +115,7 @@ services:
|
|||||||
|
|
||||||
# Disabled by default. Enable if needed.
|
# Disabled by default. Enable if needed.
|
||||||
concordancer-manager:
|
concordancer-manager:
|
||||||
image: ghcr.io/clarinsi/rsdo-concordancer-systemmanager:v1.0.0
|
image: ghcr.io/clarinsi/rsdo-concordancer-systemmanager:v1.0.3
|
||||||
profiles:
|
profiles:
|
||||||
- concordancer-manager
|
- concordancer-manager
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -137,7 +146,11 @@ services:
|
|||||||
PGDATABASE: term_portal
|
PGDATABASE: term_portal
|
||||||
SMTP_HOST: "${SMTP_HOST:?}"
|
SMTP_HOST: "${SMTP_HOST:?}"
|
||||||
SMTP_PORT: "${SMTP_PORT:?}"
|
SMTP_PORT: "${SMTP_PORT:?}"
|
||||||
SMTP_TLS_REJECT_UNAUTHORIZED: "${SMTP_TLS_REJECT_UNAUTHORIZED:?}"
|
SMTP_USER: "${SMTP_USER?}"
|
||||||
|
SMTP_PASSWORD: "${SMTP_PASSWORD?}"
|
||||||
|
SMTP_SECURE: "${SMTP_SECURE:?}"
|
||||||
|
SMTP_REQUIRE_TLS: "${SMTP_REQUIRE_TLS:?}"
|
||||||
|
SMTP_TLS_ALLOW_INVALID_CERTS: "${SMTP_TLS_ALLOW_INVALID_CERTS:?}"
|
||||||
SMTP_FROM: "${SMTP_FROM:?}"
|
SMTP_FROM: "${SMTP_FROM:?}"
|
||||||
entrypoint: ["scheduled/entrypoint.sh"]
|
entrypoint: ["scheduled/entrypoint.sh"]
|
||||||
command: ["crond", "-f", "-l", "2"]
|
command: ["crond", "-f", "-l", "2"]
|
||||||
|
|||||||
@@ -20,9 +20,16 @@ services:
|
|||||||
PGDATABASE: term_portal
|
PGDATABASE: term_portal
|
||||||
SMTP_HOST: "${SMTP_HOST:?}"
|
SMTP_HOST: "${SMTP_HOST:?}"
|
||||||
SMTP_PORT: "${SMTP_PORT:?}"
|
SMTP_PORT: "${SMTP_PORT:?}"
|
||||||
SMTP_TLS_REJECT_UNAUTHORIZED: "${SMTP_TLS_REJECT_UNAUTHORIZED:?}"
|
SMTP_USER: "${SMTP_USER?}"
|
||||||
|
SMTP_PASSWORD: "${SMTP_PASSWORD?}"
|
||||||
|
SMTP_SECURE: "${SMTP_SECURE:?}"
|
||||||
|
SMTP_REQUIRE_TLS: "${SMTP_REQUIRE_TLS:?}"
|
||||||
|
SMTP_TLS_ALLOW_INVALID_CERTS: "${SMTP_TLS_ALLOW_INVALID_CERTS:?}"
|
||||||
SMTP_FROM: "${SMTP_FROM:?}"
|
SMTP_FROM: "${SMTP_FROM:?}"
|
||||||
ORIGIN: http://localhost:${EXPRESS_LISTEN_PORT:?}
|
ORIGIN: http://localhost:${EXPRESS_LISTEN_PORT:?}
|
||||||
|
PORTAL_ADMIN_INITIAL_EMAIL: "${PORTAL_ADMIN_INITIAL_EMAIL:?}"
|
||||||
|
PORTAL_ADMIN_INITIAL_PASSWORD: "${PORTAL_ADMIN_INITIAL_PASSWORD:?}"
|
||||||
|
EXTRACTION_API_ORIGIN: "${EXTRACTION_API_ORIGIN:?}"
|
||||||
volumes:
|
volumes:
|
||||||
# When developing/debugging concordancer, disable express bind mount and node_modules volume and enable express-data.
|
# When developing/debugging concordancer, disable express bind mount and node_modules volume and enable express-data.
|
||||||
# - express-data:/usr/src/app/data_files
|
# - express-data:/usr/src/app/data_files
|
||||||
@@ -35,7 +42,7 @@ services:
|
|||||||
tty: true
|
tty: true
|
||||||
|
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:15-alpine
|
image: postgres:15.1-alpine3.17
|
||||||
# Use any of the two custom Dockerfiles below for debugging PL/pgSQL functions.
|
# Use any of the two custom Dockerfiles below for debugging PL/pgSQL functions.
|
||||||
# Disable docker-entrypoint-initdb.d bind mount when doing so.
|
# Disable docker-entrypoint-initdb.d bind mount when doing so.
|
||||||
# build:
|
# build:
|
||||||
@@ -95,7 +102,7 @@ services:
|
|||||||
- "127.0.0.1:${OS_DASHBOARDS_LISTEN_PORT:?}:5601"
|
- "127.0.0.1:${OS_DASHBOARDS_LISTEN_PORT:?}:5601"
|
||||||
|
|
||||||
concordancer:
|
concordancer:
|
||||||
# image: ghcr.io/clarinsi/rsdo-concordancer-api:v1.0.0
|
# image: ghcr.io/clarinsi/rsdo-concordancer-api:v1.0.3
|
||||||
build: concordancer
|
build: concordancer
|
||||||
profiles:
|
profiles:
|
||||||
- develop-concordancer
|
- develop-concordancer
|
||||||
@@ -114,7 +121,7 @@ services:
|
|||||||
- sloleks:/sloleks
|
- sloleks:/sloleks
|
||||||
|
|
||||||
concordancer-manager:
|
concordancer-manager:
|
||||||
image: ghcr.io/clarinsi/rsdo-concordancer-systemmanager:v1.0.0
|
image: ghcr.io/clarinsi/rsdo-concordancer-systemmanager:v1.0.3
|
||||||
profiles:
|
profiles:
|
||||||
- develop-concordancer
|
- develop-concordancer
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -146,7 +153,11 @@ services:
|
|||||||
# PGDATABASE: term_portal
|
# PGDATABASE: term_portal
|
||||||
# SMTP_HOST: "${SMTP_HOST:?}"
|
# SMTP_HOST: "${SMTP_HOST:?}"
|
||||||
# SMTP_PORT: "${SMTP_PORT:?}"
|
# SMTP_PORT: "${SMTP_PORT:?}"
|
||||||
# SMTP_TLS_REJECT_UNAUTHORIZED: "${SMTP_TLS_REJECT_UNAUTHORIZED:?}"
|
# SMTP_USER: "${SMTP_USER?}"
|
||||||
|
# SMTP_PASSWORD: "${SMTP_PASSWORD?}"
|
||||||
|
# SMTP_SECURE: "${SMTP_SECURE:?}"
|
||||||
|
# SMTP_REQUIRE_TLS: "${SMTP_REQUIRE_TLS:?}"
|
||||||
|
# SMTP_TLS_ALLOW_INVALID_CERTS: "${SMTP_TLS_ALLOW_INVALID_CERTS:?}"
|
||||||
# SMTP_FROM: "${SMTP_FROM:?}"
|
# SMTP_FROM: "${SMTP_FROM:?}"
|
||||||
# volumes:
|
# volumes:
|
||||||
# - /usr/src/app/node_modules
|
# - /usr/src/app/node_modules
|
||||||
|
|||||||
@@ -6,12 +6,15 @@ const helmet = require('helmet')
|
|||||||
const favicon = require('serve-favicon')
|
const favicon = require('serve-favicon')
|
||||||
const cookieParser = require('cookie-parser')
|
const cookieParser = require('cookie-parser')
|
||||||
const createError = require('http-errors')
|
const createError = require('http-errors')
|
||||||
const debug = require('debug')('termPortal:app')
|
const i18next = require('i18next')
|
||||||
|
const i18nextMiddleware = require('i18next-http-middleware')
|
||||||
|
// const debug = require('debug')('termPortal:app')
|
||||||
|
|
||||||
// Import own modules.
|
// Import own modules.
|
||||||
const { isBehindProxy, secret } = require('./config/keys')
|
const { isBehindProxy, secret } = require('./config/keys')
|
||||||
const helmetConfig = require('./config/helmet')
|
const helmetConfig = require('./config/helmet')
|
||||||
const session = require('./middleware/session')
|
const session = require('./middleware/session')
|
||||||
|
const i18n = require('./middleware/i18n')
|
||||||
const passport = require('./middleware/auth')
|
const passport = require('./middleware/auth')
|
||||||
const user = require('./middleware/user')
|
const user = require('./middleware/user')
|
||||||
const settings = require('./middleware/settings')
|
const settings = require('./middleware/settings')
|
||||||
@@ -37,6 +40,7 @@ app.locals.basedir = viewsPath
|
|||||||
// Other settings.
|
// Other settings.
|
||||||
const inDevEnv = app.get('env') === 'development'
|
const inDevEnv = app.get('env') === 'development'
|
||||||
if (isBehindProxy) app.set('trust proxy', 1) // Trust first proxy.
|
if (isBehindProxy) app.set('trust proxy', 1) // Trust first proxy.
|
||||||
|
app.locals.inDevEnv = inDevEnv
|
||||||
|
|
||||||
// Mount middleware.
|
// Mount middleware.
|
||||||
app.use(logger('dev'))
|
app.use(logger('dev'))
|
||||||
@@ -50,10 +54,19 @@ app.use(session)
|
|||||||
app.use(passport.initialize())
|
app.use(passport.initialize())
|
||||||
app.use(passport.session())
|
app.use(passport.session())
|
||||||
app.use(passport.authenticate('remember-me'))
|
app.use(passport.authenticate('remember-me'))
|
||||||
|
app.use(i18n.determineRequestLanguage)
|
||||||
|
app.use(i18nextMiddleware.handle(i18next))
|
||||||
app.use(user.enhance)
|
app.use(user.enhance)
|
||||||
app.use(settings.prepareRequiredSettings)
|
app.use(settings.prepareRequiredSettings)
|
||||||
app.use(enhanceLocals)
|
app.use(enhanceLocals)
|
||||||
|
|
||||||
|
if (inDevEnv) {
|
||||||
|
app.post(
|
||||||
|
'/locales/add/:lng/:ns',
|
||||||
|
i18nextMiddleware.missingKeyHandler(i18next)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Apparently express-debug can't be run inside another middleware and must be run in this file.
|
// Apparently express-debug can't be run inside another middleware and must be run in this file.
|
||||||
// To help you debug, temporarily uncomment the next line, but comment the helmet line due to strict CSP.
|
// To help you debug, temporarily uncomment the next line, but comment the helmet line due to strict CSP.
|
||||||
// if (app.get('env') === 'development') require('express-debug')(app)
|
// if (app.get('env') === 'development') require('express-debug')(app)
|
||||||
@@ -75,7 +88,8 @@ app.use((req, res, next) => next(createError(404)))
|
|||||||
|
|
||||||
// Error handler.
|
// Error handler.
|
||||||
app.use((err, req, res, next) => {
|
app.use((err, req, res, next) => {
|
||||||
if (err.status !== 404) debug(err)
|
// eslint-disable-next-line no-console
|
||||||
|
if (err.status !== 404) console.error(err)
|
||||||
|
|
||||||
// Set error info to be displayed to user depending on environment.
|
// Set error info to be displayed to user depending on environment.
|
||||||
let message, error
|
let message, error
|
||||||
@@ -86,8 +100,8 @@ app.use((err, req, res, next) => {
|
|||||||
} else {
|
} else {
|
||||||
message =
|
message =
|
||||||
err.status === 404
|
err.status === 404
|
||||||
? 'Stran ne obstaja'
|
? req.t('Stran ne obstaja')
|
||||||
: 'Prišlo je do strežniške napake. Poskusite kasneje.'
|
: req.t('Prišlo je do strežniške napake. Poskusite kasneje.')
|
||||||
error = {}
|
error = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +113,7 @@ app.use((err, req, res, next) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Render the error page.
|
// Render the error page.
|
||||||
res.render('error', { title: 'Napaka', message, error })
|
res.render('error', { title: req.t('Napaka'), message, error })
|
||||||
})
|
})
|
||||||
|
|
||||||
module.exports = app
|
module.exports = app
|
||||||
|
|||||||
@@ -4,12 +4,16 @@
|
|||||||
* Module dependencies.
|
* Module dependencies.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Temporary noop function to be used until i18n is fully in place.
|
||||||
|
global.__ = str => str
|
||||||
|
|
||||||
const db = require('../models/db')
|
const db = require('../models/db')
|
||||||
const cache = require('../models/cache')
|
const cache = require('../models/cache')
|
||||||
const searchEngine = require('../models/search-engine')
|
const searchEngine = require('../models/search-engine')
|
||||||
const email = require('../models/email')
|
const email = require('../models/email')
|
||||||
|
const init = require('../config/init')
|
||||||
const { seedDummyData } = require('../models/comment')
|
const { seedDummyData } = require('../models/comment')
|
||||||
const { initDemoData } = require('../models/demo-paginacija')
|
// const { initDemoData } = require('../models/demo-paginacija')
|
||||||
const app = require('../app')
|
const app = require('../app')
|
||||||
const debug = require('debug')('termPortal:server')
|
const debug = require('debug')('termPortal:server')
|
||||||
const http = require('http')
|
const http = require('http')
|
||||||
@@ -37,7 +41,8 @@ const server = http.createServer(app)
|
|||||||
db.waitForConnection(),
|
db.waitForConnection(),
|
||||||
cache.waitForConnection(),
|
cache.waitForConnection(),
|
||||||
searchEngine.waitForConnection(),
|
searchEngine.waitForConnection(),
|
||||||
email.waitForConnection()
|
email.waitForConnection(),
|
||||||
|
init()
|
||||||
])
|
])
|
||||||
await searchEngine.initEntryIndex()
|
await searchEngine.initEntryIndex()
|
||||||
await searchEngine.initConsultancyEntryIndex()
|
await searchEngine.initConsultancyEntryIndex()
|
||||||
@@ -102,5 +107,7 @@ function onError(error) {
|
|||||||
function onListening() {
|
function onListening() {
|
||||||
const addr = server.address()
|
const addr = server.address()
|
||||||
const bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port
|
const bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('Started listening')
|
||||||
debug('Listening on ' + bind)
|
debug('Listening on ' + bind)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
const { mkdir } = require('fs/promises')
|
||||||
|
const { TEMP_EXPORT_PATH } = require('./settings')
|
||||||
|
|
||||||
|
module.exports = async () => {
|
||||||
|
await mkdir(TEMP_EXPORT_PATH, { recursive: true })
|
||||||
|
}
|
||||||
@@ -4,8 +4,14 @@ module.exports = {
|
|||||||
cookiesSecure: process.env.COOKIES_SECURE === 'true',
|
cookiesSecure: process.env.COOKIES_SECURE === 'true',
|
||||||
smtpHost: process.env.SMTP_HOST,
|
smtpHost: process.env.SMTP_HOST,
|
||||||
smtpPort: process.env.SMTP_PORT,
|
smtpPort: process.env.SMTP_PORT,
|
||||||
smtpTlsRejectUnauthorized:
|
smtpUser: process.env.SMTP_USER,
|
||||||
process.env.SMTP_TLS_REJECT_UNAUTHORIZED === 'true',
|
smtpPassword: process.env.SMTP_PASSWORD,
|
||||||
|
smtpSecure: process.env.SMTP_SECURE === 'true',
|
||||||
|
smtpRequireTls: process.env.SMTP_REQUIRE_TLS === 'true',
|
||||||
|
smtpAllowInvalidCerts: process.env.SMTP_TLS_ALLOW_INVALID_CERTS === 'true',
|
||||||
smtpFrom: process.env.SMTP_FROM,
|
smtpFrom: process.env.SMTP_FROM,
|
||||||
origin: process.env.ORIGIN
|
origin: process.env.ORIGIN,
|
||||||
|
portalAdminInitialEmail: process.env.PORTAL_ADMIN_INITIAL_EMAIL,
|
||||||
|
portalAdminInitialPassword: process.env.PORTAL_ADMIN_INITIAL_PASSWORD,
|
||||||
|
extractionApiOrigin: process.env.EXTRACTION_API_ORIGIN
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,11 @@ exports.DEFAULT_HITS_PER_PAGE = 10
|
|||||||
|
|
||||||
exports.EDITOR_MAX_HITS = 10000
|
exports.EDITOR_MAX_HITS = 10000
|
||||||
|
|
||||||
// If you change this one, don't forget to also update the volume mount in docker-compose.prod.yml.
|
// If you change this one, don't forget to also update the volume mount in docker-compose.prod.yml
|
||||||
exports.DATA_FILES_PATH = 'data_files'
|
// and nodemon ignore flag in package.json scripts.
|
||||||
|
const DATA_FILES_PATH = 'data_files'
|
||||||
|
exports.DATA_FILES_PATH = DATA_FILES_PATH
|
||||||
|
|
||||||
|
exports.TEMP_EXPORT_PATH = `${DATA_FILES_PATH}/export_temp`
|
||||||
|
|
||||||
exports.MAX_EXTRACTIONS_PER_USER = 5
|
exports.MAX_EXTRACTIONS_PER_USER = 5
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
const ConsultancyEntry = require('../../../models/consultancy-entry')
|
const ConsultancyEntry = require('../../../models/consultancy-entry')
|
||||||
|
const Domain = require('../../../models/domain')
|
||||||
const User = require('../../../models/user')
|
const User = require('../../../models/user')
|
||||||
const { promisify } = require('util')
|
const { promisify } = require('util')
|
||||||
|
const i18next = require('i18next')
|
||||||
const {
|
const {
|
||||||
deleteConsultancyEntryFromIndex
|
deleteConsultancyEntryFromIndex
|
||||||
} = require('../../../models/search-engine')
|
} = require('../../../models/search-engine')
|
||||||
const email = require('../../../models/email')
|
const email = require('../../../models/email')
|
||||||
const helper = require('../../../models/helpers')
|
const helper = require('../../../models/helpers')
|
||||||
|
const { getInstanceSetting } = require('../../../models/helpers')
|
||||||
|
const { searchConsultancyEntryIndex } = require('../../../models/search-engine')
|
||||||
|
const { prepareConsultancyEntries } = require('../../../models/helpers/search')
|
||||||
|
const { DEFAULT_HITS_PER_PAGE } = require('../../../config/settings')
|
||||||
|
const generateQuery = require('../../../models/helpers/search/generate-query')
|
||||||
|
|
||||||
const consultancy = {}
|
const consultancy = {}
|
||||||
|
|
||||||
@@ -23,6 +30,43 @@ consultancy.listNewEntries = async (req, res) => {
|
|||||||
res.send(data)
|
res.send(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
consultancy.sendPaginationData = async (req, res) => {
|
||||||
|
let requestType = req.query.type
|
||||||
|
const isAdminPage = req.query.isAdmin === 'true'
|
||||||
|
let page = +req.query.p || 1
|
||||||
|
|
||||||
|
if (page < 1) {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isAdminPage) {
|
||||||
|
if (
|
||||||
|
req.user &&
|
||||||
|
(req.user.hasRole('portal admin') ||
|
||||||
|
req.user.hasRole('consultancy admin'))
|
||||||
|
) {
|
||||||
|
// Everything is allowed, nothing to do here
|
||||||
|
} else if (req.user && req.user.hasRole('consultant')) {
|
||||||
|
if (!(requestType === 'in progress' || requestType === 'published')) {
|
||||||
|
return res.status(400).send()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// for now, public can only see published entries
|
||||||
|
requestType = 'published'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return await consultancyRequestItems(
|
||||||
|
req,
|
||||||
|
res,
|
||||||
|
requestType || 'published',
|
||||||
|
'components/consultancy/api/consultancy-item-rendered',
|
||||||
|
isAdminPage,
|
||||||
|
!isAdminPage, // -> In current implementation, it is just the inverse of isAdminPage
|
||||||
|
page
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
consultancy.createQuestion = async (req, res) => {
|
consultancy.createQuestion = async (req, res) => {
|
||||||
const q = req.body
|
const q = req.body
|
||||||
const consultancyEntry = {}
|
const consultancyEntry = {}
|
||||||
@@ -35,7 +79,7 @@ consultancy.createQuestion = async (req, res) => {
|
|||||||
|
|
||||||
const { description } = q
|
const { description } = q
|
||||||
if (!description) {
|
if (!description) {
|
||||||
return res.status(400).send('description is a required parameter!')
|
return res.status(400).send('Description is a required parameter!')
|
||||||
}
|
}
|
||||||
consultancyEntry.description = description
|
consultancyEntry.description = description
|
||||||
|
|
||||||
@@ -52,24 +96,46 @@ consultancy.createQuestion = async (req, res) => {
|
|||||||
consultancyEntry[key] = helper.removeHtmlTags(consultancyEntry[key]).trim()
|
consultancyEntry[key] = helper.removeHtmlTags(consultancyEntry[key]).trim()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const isOwnConsultancyEnabled =
|
||||||
|
(await getInstanceSetting('consultancy_type')) === 'own'
|
||||||
|
|
||||||
|
let domainNameSl
|
||||||
|
if (consultancyEntry.domainPrimaryIdInitial) {
|
||||||
|
domainNameSl = (
|
||||||
|
await Domain.fetchById(consultancyEntry.domainPrimaryIdInitial)
|
||||||
|
).nameSl
|
||||||
|
} else {
|
||||||
|
domainNameSl = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
let emails
|
||||||
|
let subjectText
|
||||||
|
if (isOwnConsultancyEnabled) {
|
||||||
const questionId = await ConsultancyEntry.createQuestion(consultancyEntry)
|
const questionId = await ConsultancyEntry.createQuestion(consultancyEntry)
|
||||||
await ConsultancyEntry.indexIntoSearchEngine(questionId, true)
|
await ConsultancyEntry.indexIntoSearchEngine(questionId, true)
|
||||||
|
subjectText = req.t('Ustvarjeno novo vprašanje v svetovalnici')
|
||||||
// TODO SEND EMAIL
|
emails = await ConsultancyEntry.fetchConsultancyAdminEmails()
|
||||||
// TODOOOOOOOOO
|
} else {
|
||||||
|
subjectText = req.t('Novo vprašanje za Terminološko svetovalnico')
|
||||||
const emails = await ConsultancyEntry.fetchConsultancyAdminEmails()
|
emails = await getInstanceSetting('zrc_email')
|
||||||
|
}
|
||||||
|
|
||||||
const renderAsync = promisify(req.app.render.bind(req.app))
|
const renderAsync = promisify(req.app.render.bind(req.app))
|
||||||
const emailHtml = await renderAsync('email/consultancy-creation-notify', {
|
const emailHtml = await renderAsync('email/consultancy-creation-notify', {
|
||||||
propertyToPassGoesHere: 'test1234'
|
nameAndSurname: `${res.locals.user.firstName} ${res.locals.user.lastName}`,
|
||||||
|
email: res.locals.user.email,
|
||||||
|
domain: domainNameSl,
|
||||||
|
institution: consultancyEntry.institution,
|
||||||
|
question: consultancyEntry.description,
|
||||||
|
existingSolutions: consultancyEntry.existingSolutions,
|
||||||
|
examplesOfUse: consultancyEntry.examplesOfUse
|
||||||
})
|
})
|
||||||
|
// TODO i18n - What language are the email title and content (we already have email translated)
|
||||||
await email.send({
|
await email.send({
|
||||||
to: emails,
|
to: emails,
|
||||||
subject: 'Ustvarjeno novo vprašanje v svetovalnici',
|
subject: subjectText,
|
||||||
html: emailHtml
|
html: emailHtml
|
||||||
})
|
})
|
||||||
/// /////////////////
|
|
||||||
|
|
||||||
res.status(201).send()
|
res.status(201).send()
|
||||||
}
|
}
|
||||||
@@ -179,9 +245,10 @@ consultancy.assign = async (req, res) => {
|
|||||||
const renderAsync = promisify(req.app.render.bind(req.app))
|
const renderAsync = promisify(req.app.render.bind(req.app))
|
||||||
const emailHtml = await renderAsync('email/consultancy-assigned')
|
const emailHtml = await renderAsync('email/consultancy-assigned')
|
||||||
|
|
||||||
|
// TODO i18n - What language are the email title and content (we already have email translated)
|
||||||
await email.send({
|
await email.send({
|
||||||
to: emails,
|
to: emails,
|
||||||
subject: 'Novo terminološko vprašanje',
|
subject: req.t('Novo terminološko vprašanje'),
|
||||||
html: emailHtml
|
html: emailHtml
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -234,9 +301,10 @@ consultancy.sendToReview = async (req, res) => {
|
|||||||
const renderAsync = promisify(req.app.render.bind(req.app))
|
const renderAsync = promisify(req.app.render.bind(req.app))
|
||||||
const emailHtml = await renderAsync('email/consultancy-item-review')
|
const emailHtml = await renderAsync('email/consultancy-item-review')
|
||||||
|
|
||||||
|
// TODO i18n - What language are the email title and content (we already have email translated)
|
||||||
await email.send({
|
await email.send({
|
||||||
to: emails,
|
to: emails,
|
||||||
subject: 'Potrditev objave',
|
subject: req.t('Potrditev objave'),
|
||||||
html: emailHtml
|
html: emailHtml
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -254,6 +322,19 @@ consultancy.publish = async (req, res) => {
|
|||||||
if (!entry.title) {
|
if (!entry.title) {
|
||||||
return res.status(400).send('Answer not completed')
|
return res.status(400).send('Answer not completed')
|
||||||
}
|
}
|
||||||
|
const author = await User.fetchUser(entry.authorId)
|
||||||
|
const portalName = await getInstanceSetting(`portal_name_${author.language}`)
|
||||||
|
const renderAsync = promisify(req.app.render.bind(req.app))
|
||||||
|
const emailHtml = await renderAsync('email/consultancy-publish-notify', {
|
||||||
|
portalName
|
||||||
|
})
|
||||||
|
await email.send({
|
||||||
|
to: author.email,
|
||||||
|
subject: i18next.t('Objava terminološkega vprašanja', {
|
||||||
|
lng: author.language
|
||||||
|
}),
|
||||||
|
html: emailHtml
|
||||||
|
})
|
||||||
|
|
||||||
await ConsultancyEntry.publish(questionId, answerAuthors)
|
await ConsultancyEntry.publish(questionId, answerAuthors)
|
||||||
await ConsultancyEntry.indexIntoSearchEngine(questionId, true)
|
await ConsultancyEntry.indexIntoSearchEngine(questionId, true)
|
||||||
@@ -267,7 +348,9 @@ consultancy.updateQuestion = async (req, res) => {
|
|||||||
if (!id) return res.status(400).send({})
|
if (!id) return res.status(400).send({})
|
||||||
|
|
||||||
if (questionTitle === '' || question === '' || answer === '') {
|
if (questionTitle === '' || question === '' || answer === '') {
|
||||||
return res.status(422).send('Polja naslov, vprašanje in mnenje so obvezna!')
|
return res
|
||||||
|
.status(422)
|
||||||
|
.send(req.t('Polja naslov, vprašanje in mnenje so obvezna!'))
|
||||||
}
|
}
|
||||||
|
|
||||||
const entry = await ConsultancyEntry.fetchById(id)
|
const entry = await ConsultancyEntry.fetchById(id)
|
||||||
@@ -318,4 +401,157 @@ consultancy.deleteQuestion = async (req, res) => {
|
|||||||
res.send()
|
res.send()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function consultancyRequestItems(
|
||||||
|
req,
|
||||||
|
res,
|
||||||
|
type,
|
||||||
|
url,
|
||||||
|
isAdminPage = false,
|
||||||
|
privilegeToSeAll = true, // this method seperates consultancy main from admin, so all results get visible TO ALL REGISTERED USERS, not just admins
|
||||||
|
page
|
||||||
|
) {
|
||||||
|
const searchString = req.query.q?.trim() ?? ''
|
||||||
|
|
||||||
|
/// //////////////////////////////////////////
|
||||||
|
// let allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains()
|
||||||
|
|
||||||
|
/// filter selected domains for the prompt ///
|
||||||
|
// Not duplicates of this code arise...
|
||||||
|
// const pdList = intoDbArray(req.query.pd, 'always')
|
||||||
|
|
||||||
|
// allPrimaryDomains = allPrimaryDomains.map(entry => {
|
||||||
|
// if (pdList.includes(`${entry.id}`)) {
|
||||||
|
// entry.selected = true
|
||||||
|
// }
|
||||||
|
// return entry
|
||||||
|
// })
|
||||||
|
/// //////////////////////////////////////////
|
||||||
|
|
||||||
|
let assignedConsultant
|
||||||
|
|
||||||
|
if (
|
||||||
|
!privilegeToSeAll &&
|
||||||
|
req.user &&
|
||||||
|
!(req.user.hasRole('portal admin') || req.user.hasRole('consultancy admin'))
|
||||||
|
) {
|
||||||
|
assignedConsultant = req.user.id
|
||||||
|
} else {
|
||||||
|
assignedConsultant = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const filters = {
|
||||||
|
status: type,
|
||||||
|
assignedConsultant,
|
||||||
|
primaryDomain: req.query.pd
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isAdminPage) {
|
||||||
|
filters.assignedConsultant = undefined
|
||||||
|
filters.status = 'published' // guard
|
||||||
|
}
|
||||||
|
|
||||||
|
const hitsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
|
||||||
|
|
||||||
|
// const page = +req.query.p > 0 ? +req.query.p : 1
|
||||||
|
|
||||||
|
const hitsQuery = generateQuery.consultancy(
|
||||||
|
searchString,
|
||||||
|
filters,
|
||||||
|
hitsPerPage,
|
||||||
|
page
|
||||||
|
)
|
||||||
|
|
||||||
|
const hits = await searchConsultancyEntryIndex(hitsQuery)
|
||||||
|
|
||||||
|
const numberOfAllHits = hits.body.hits.total.value
|
||||||
|
const numberOfAllPages = Math.ceil(numberOfAllHits / hitsPerPage)
|
||||||
|
|
||||||
|
let entries = prepareConsultancyEntries(hits)
|
||||||
|
// console.log({ entries, numberOfAllHits, numberOfAllPages })
|
||||||
|
|
||||||
|
entries = entries.map(entry => {
|
||||||
|
entry.primaryDomain = entry.primaryDomain
|
||||||
|
? entry.primaryDomain.nameSl
|
||||||
|
: req.t('nedefinirano')
|
||||||
|
|
||||||
|
if (entry.assignedConsultants) {
|
||||||
|
entry.firstName = entry.assignedConsultants[0]?.firstName
|
||||||
|
entry.lastName = entry.assignedConsultants[0]?.lastName
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.assignedConsultants && entry.assignedConsultants.length > 1) {
|
||||||
|
entry.sharedAuthors = []
|
||||||
|
for (let i = 1; i < entry.assignedConsultants.length; i++) {
|
||||||
|
// skip first element (moderator)
|
||||||
|
entry.sharedAuthors.push(
|
||||||
|
`${entry.assignedConsultants[i].firstName} ${entry.assignedConsultants[i].lastName}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.timeCreated) {
|
||||||
|
// TODO i18n date format
|
||||||
|
const date = new Date(entry.timeCreated)
|
||||||
|
|
||||||
|
entry.formattedTimeCreated = `${date.getDate()}. ${
|
||||||
|
date.getMonth() + 1
|
||||||
|
}. ${date.getFullYear()}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry
|
||||||
|
})
|
||||||
|
|
||||||
|
// const entryList = await mapEntryList(inProgressEntryList)
|
||||||
|
const userList = await User.fetchConsultants()
|
||||||
|
|
||||||
|
if (!isAdminPage) {
|
||||||
|
entries.map(entry => {
|
||||||
|
const MAX_CHARACTER_LENGTH = 200
|
||||||
|
let appendAnswer = ''
|
||||||
|
let appendQuestion = ''
|
||||||
|
if (entry.answer && entry.answer.length > MAX_CHARACTER_LENGTH) {
|
||||||
|
appendAnswer = '...'
|
||||||
|
}
|
||||||
|
if (entry.question && entry.question.length > MAX_CHARACTER_LENGTH) {
|
||||||
|
appendQuestion = '...'
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.answerSummary = `${entry.answer.slice(
|
||||||
|
0,
|
||||||
|
MAX_CHARACTER_LENGTH
|
||||||
|
)}${appendAnswer}`
|
||||||
|
|
||||||
|
entry.question = `${entry.question.slice(
|
||||||
|
0,
|
||||||
|
MAX_CHARACTER_LENGTH
|
||||||
|
)}${appendQuestion}`
|
||||||
|
|
||||||
|
return entry
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
res.append('page', page)
|
||||||
|
res.append('number-of-all-pages', numberOfAllPages)
|
||||||
|
res.render(url, {
|
||||||
|
entries, // entryList,
|
||||||
|
userList,
|
||||||
|
numberOfAllPages,
|
||||||
|
isAdminPage,
|
||||||
|
section: consultancyAdminPageMapper(type),
|
||||||
|
queryCount: numberOfAllHits
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// mapping required due to the inconsistent naming convention
|
||||||
|
function consultancyAdminPageMapper(type) {
|
||||||
|
if (type === 'in progress') {
|
||||||
|
return { inProgress: true }
|
||||||
|
}
|
||||||
|
if (type === 'review') {
|
||||||
|
return { prepared: true }
|
||||||
|
}
|
||||||
|
|
||||||
|
return { type: true }
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = consultancy
|
module.exports = consultancy
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
const { rm } = require('fs/promises')
|
||||||
const Dictionary = require('../../../models/dictionary')
|
const Dictionary = require('../../../models/dictionary')
|
||||||
const Entry = require('../../../models/entry')
|
const Entry = require('../../../models/entry')
|
||||||
|
const Extraction = require('../../../models/extraction')
|
||||||
const {
|
const {
|
||||||
searchEntryIndex,
|
searchEntryIndex,
|
||||||
deleteEntryFromIndex,
|
deleteEntryFromIndex,
|
||||||
@@ -9,6 +11,7 @@ const genEditorAllQuery = require('../../../models/helpers/search/generate-query
|
|||||||
const { prepareEditorEntries } = require('../../../models/helpers/search')
|
const { prepareEditorEntries } = require('../../../models/helpers/search')
|
||||||
const { DEFAULT_HITS_PER_PAGE } = require('../../../config/settings')
|
const { DEFAULT_HITS_PER_PAGE } = require('../../../config/settings')
|
||||||
const { minEntriesRequirementCheckAndAct } = require('../../helpers/dictionary')
|
const { minEntriesRequirementCheckAndAct } = require('../../helpers/dictionary')
|
||||||
|
const { getExportFilesPath } = require('../../../models/helpers/dictionary')
|
||||||
|
|
||||||
const dictionary = {}
|
const dictionary = {}
|
||||||
|
|
||||||
@@ -115,6 +118,8 @@ dictionary.delete = async (req, res) => {
|
|||||||
const dictionaryId = +req.params.dictionaryId
|
const dictionaryId = +req.params.dictionaryId
|
||||||
await Dictionary.delete(dictionaryId)
|
await Dictionary.delete(dictionaryId)
|
||||||
await deleteDictionaryEntriesFromIndex(dictionaryId)
|
await deleteDictionaryEntriesFromIndex(dictionaryId)
|
||||||
|
const exportFilesPath = getExportFilesPath(dictionaryId)
|
||||||
|
await rm(exportFilesPath, { recursive: true, force: true })
|
||||||
|
|
||||||
res.end()
|
res.end()
|
||||||
}
|
}
|
||||||
@@ -167,6 +172,56 @@ dictionary.listDomainLabels = async (req, res) => {
|
|||||||
res.send({ page, numberOfAllPages, results })
|
res.send({ page, numberOfAllPages, results })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dictionary.listFilteredDomainLabels = async (req, res) => {
|
||||||
|
const resultsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
|
||||||
|
const { dictionaryId } = req.params
|
||||||
|
const page = +req.query.p > 0 ? +req.query.p : 1
|
||||||
|
|
||||||
|
let { q } = req.query
|
||||||
|
|
||||||
|
if (!q) {
|
||||||
|
q = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const { pages_total: numberOfAllPages, results } =
|
||||||
|
await Dictionary.fetchFilteredPaginationDomainLabels(
|
||||||
|
dictionaryId,
|
||||||
|
q,
|
||||||
|
resultsPerPage,
|
||||||
|
page
|
||||||
|
)
|
||||||
|
|
||||||
|
res.append('page', page)
|
||||||
|
res.append('number-of-all-pages', numberOfAllPages)
|
||||||
|
res.render('utilities/response-pug-wrapper/domainLabelLister', {
|
||||||
|
dictionary: { id: dictionaryId },
|
||||||
|
numberOfAllPages,
|
||||||
|
results
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
dictionary.listSecondaryDomainData = async (req, res) => {
|
||||||
|
const resultsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
|
||||||
|
let { q } = req.query
|
||||||
|
const page = +req.query.p > 0 ? +req.query.p : 1
|
||||||
|
|
||||||
|
if (!q) {
|
||||||
|
q = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const { pages_total: numberOfAllPages, results } =
|
||||||
|
await Dictionary.fetchFilteredSecondaryDomains(q, resultsPerPage, page)
|
||||||
|
|
||||||
|
res.append('page', page)
|
||||||
|
res.append('number-of-all-pages', numberOfAllPages)
|
||||||
|
|
||||||
|
res.render('utilities/response-pug-wrapper/secondaryDomainLister', {
|
||||||
|
page,
|
||||||
|
numberOfAllPages,
|
||||||
|
results
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
dictionary.listSecondaryDomains = async (req, res) => {
|
dictionary.listSecondaryDomains = async (req, res) => {
|
||||||
const resultsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
|
const resultsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
|
||||||
const page = +req.query.p > 0 ? +req.query.p : 1
|
const page = +req.query.p > 0 ? +req.query.p : 1
|
||||||
@@ -176,14 +231,92 @@ dictionary.listSecondaryDomains = async (req, res) => {
|
|||||||
res.send({ page, numberOfAllPages, results })
|
res.send({ page, numberOfAllPages, results })
|
||||||
}
|
}
|
||||||
|
|
||||||
dictionary.extractionImport = async (req, res) => {
|
dictionary.showImportFromFileForm = async (req, res) => {
|
||||||
// TODO Import logic (Luka's task)
|
const resultsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
|
||||||
// const { id: dictionaryId, extractionId } = req.params
|
const { dictionaryId } = req.params
|
||||||
// const { from, to } = req.query
|
const page = +req.query.p > 0 ? +req.query.p : 1
|
||||||
// const fromIndex = +from > 1 ? Math.floor(from) - 1 : 0
|
const { pages_total: numberOfAllPages, results } =
|
||||||
// const toIndex = Number.isInteger(+to) ? Math.abs(to) : undefined
|
await Dictionary.fetchAllImports(dictionaryId, resultsPerPage, page)
|
||||||
// console.log({ dictionaryId, extractionId, fromIndex, toIndex })
|
|
||||||
res.send('IMPORTING')
|
res.send({ page, numberOfAllPages, results })
|
||||||
|
}
|
||||||
|
|
||||||
|
dictionary.showExportToFileForm = async (req, res) => {
|
||||||
|
const resultsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
|
||||||
|
const { dictionaryId } = req.params
|
||||||
|
const page = +req.query.p > 0 ? +req.query.p : 1
|
||||||
|
const { pages_total: numberOfAllPages, results } =
|
||||||
|
await Dictionary.fetchExports(dictionaryId, resultsPerPage, page)
|
||||||
|
|
||||||
|
res.send({ page, numberOfAllPages, results })
|
||||||
|
}
|
||||||
|
|
||||||
|
dictionary.importFromExtraction = async (req, res) => {
|
||||||
|
const { id: dictionaryId, extractionId } = req.params
|
||||||
|
const { from, to } = req.body
|
||||||
|
const fromIndex = +from > 1 ? Math.floor(from) - 1 : 0
|
||||||
|
const toIndex = Number.isInteger(+(to === '' ? undefined : to))
|
||||||
|
? Math.abs(to)
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
// TODO Authentication, authorization, validation.
|
||||||
|
|
||||||
|
const termCandidatesToImport = await Extraction.fetchTermCandidatesSlice(
|
||||||
|
extractionId,
|
||||||
|
fromIndex,
|
||||||
|
toIndex
|
||||||
|
)
|
||||||
|
|
||||||
|
await Dictionary.importFromExtraction(
|
||||||
|
dictionaryId,
|
||||||
|
req.user.id,
|
||||||
|
termCandidatesToImport
|
||||||
|
)
|
||||||
|
|
||||||
|
await Dictionary.indexIntoSearchEngine(dictionaryId)
|
||||||
|
|
||||||
|
res.end()
|
||||||
|
}
|
||||||
|
|
||||||
|
dictionary.exportBegin = async (req, res) => {
|
||||||
|
const dictionaryId = req.params.id
|
||||||
|
const exportParams = {
|
||||||
|
isValidFilter:
|
||||||
|
req.body.isValidFilter === 'on' ? undefined : req.body.isValidFilter,
|
||||||
|
isPublishedFilter:
|
||||||
|
req.body.isPublishedFilter === 'on'
|
||||||
|
? undefined
|
||||||
|
: req.body.isPublishedFilter,
|
||||||
|
statusFilter:
|
||||||
|
req.body.statusFilter === 'complete'
|
||||||
|
? 'complete'
|
||||||
|
: req.body.statusFilter === 'inEdit'
|
||||||
|
? 'in_edit'
|
||||||
|
: undefined,
|
||||||
|
isTerminologyReviewedFilter:
|
||||||
|
req.body.isTerminologyReviewedFilter === 'on'
|
||||||
|
? undefined
|
||||||
|
: req.body.isTerminologyReviewedFilter,
|
||||||
|
isLanguageReviewedFilter:
|
||||||
|
req.body.isLanguageReviewedFilter === 'on'
|
||||||
|
? undefined
|
||||||
|
: req.body.isLanguageReviewedFilter,
|
||||||
|
exportFileFormat: req.body.exportFileFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
const exportId = await Dictionary.beginExport(dictionaryId, exportParams)
|
||||||
|
|
||||||
|
res.end()
|
||||||
|
|
||||||
|
// Explicitcly catch any errors after the response has been sent
|
||||||
|
// since the the final error handler won't be able to send another.
|
||||||
|
try {
|
||||||
|
// TODO Consider delegating processing of export to a seperate process or at least a seperate thread (much like importing, extraction, ...).
|
||||||
|
await Dictionary.processExport(exportId)
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = dictionary
|
module.exports = dictionary
|
||||||
|
|||||||
@@ -1,22 +1,27 @@
|
|||||||
const { unlink, rm, mkdir } = require('fs/promises')
|
const { unlink, rm, mkdir, writeFile } = require('fs/promises')
|
||||||
const { promisify } = require('util')
|
const { promisify } = require('util')
|
||||||
const { URLSearchParams } = require('url')
|
const { URLSearchParams } = require('url')
|
||||||
const multer = require('multer')
|
const multer = require('multer')
|
||||||
|
const i18next = require('i18next')
|
||||||
const validator = require('validator')
|
const validator = require('validator')
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const {
|
const {
|
||||||
getExtractionFilesPath,
|
getExtractionFilesPath,
|
||||||
getDocumentsPath,
|
getDocumentsPath,
|
||||||
getStopTermsPath,
|
getStopTermsPath,
|
||||||
getConllusPath
|
getConllusPath,
|
||||||
|
getFileStats
|
||||||
} = require('../../../models/helpers/extraction')
|
} = require('../../../models/helpers/extraction')
|
||||||
const { checkIfcanBegin } = require('../../helpers/extraction')
|
const { checkIfcanBegin } = require('../../helpers/extraction')
|
||||||
const Extraction = require('../../../models/extraction')
|
const Extraction = require('../../../models/extraction')
|
||||||
const Domain = require('../../../models/domain')
|
const Domain = require('../../../models/domain')
|
||||||
const email = require('../../../models/email')
|
const email = require('../../../models/email')
|
||||||
const { intoDbArray } = require('../../../models/helpers')
|
const { intoDbArray } = require('../../../models/helpers')
|
||||||
const { origin } = require('../../../config/keys')
|
const { origin, extractionApiOrigin } = require('../../../config/keys')
|
||||||
const { DEFAULT_HITS_PER_PAGE } = require('../../../config/settings')
|
const {
|
||||||
|
DEFAULT_HITS_PER_PAGE,
|
||||||
|
TEMP_EXPORT_PATH
|
||||||
|
} = require('../../../config/settings')
|
||||||
|
|
||||||
const MAX_FILE_NAME_LENGTH = 100
|
const MAX_FILE_NAME_LENGTH = 100
|
||||||
const MAX_FILE_SIZE = 10 ** 9 // 1 GB
|
const MAX_FILE_SIZE = 10 ** 9 // 1 GB
|
||||||
@@ -77,16 +82,19 @@ extraction.docsList = async (req, res) => {
|
|||||||
extraction.docsUpdate = async (req, res) => {
|
extraction.docsUpdate = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
await parseExtractionFileBody(req, res)
|
await parseExtractionFileBody(req, res)
|
||||||
|
const fileStats = await getFileStats(req.file.path)
|
||||||
|
res.send(fileStats)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (
|
if (
|
||||||
error instanceof multer.MulterError &&
|
error instanceof multer.MulterError &&
|
||||||
error.code === 'LIMIT_FILE_SIZE'
|
error.code === 'LIMIT_FILE_SIZE'
|
||||||
) {
|
) {
|
||||||
throw Error('File too large. Must not be over 1 GB.')
|
const customError = Error('File too large. Must not be over 1 GB.')
|
||||||
|
customError.displayInProd = true
|
||||||
|
throw customError
|
||||||
}
|
}
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
res.end()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extraction.docDelete = async (req, res) => {
|
extraction.docDelete = async (req, res) => {
|
||||||
@@ -108,16 +116,19 @@ extraction.stopTermsList = async (req, res) => {
|
|||||||
extraction.stopTermsUpdate = async (req, res) => {
|
extraction.stopTermsUpdate = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
await parseExtractionFileBody(req, res)
|
await parseExtractionFileBody(req, res)
|
||||||
|
const fileStats = await getFileStats(req.file.path)
|
||||||
|
res.send(fileStats)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (
|
if (
|
||||||
error instanceof multer.MulterError &&
|
error instanceof multer.MulterError &&
|
||||||
error.code === 'LIMIT_FILE_SIZE'
|
error.code === 'LIMIT_FILE_SIZE'
|
||||||
) {
|
) {
|
||||||
throw Error('File too large. Must not be over 1 GB.')
|
const customError = Error('File too large. Must not be over 1 GB.')
|
||||||
|
customError.displayInProd = true
|
||||||
|
throw customError
|
||||||
}
|
}
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
res.end()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extraction.stopTermDelete = async (req, res) => {
|
extraction.stopTermDelete = async (req, res) => {
|
||||||
@@ -141,7 +152,7 @@ extraction.ossSearch = [
|
|||||||
...(ossParams.keywords && { kljucneBesede: ossParams.keywords }),
|
...(ossParams.keywords && { kljucneBesede: ossParams.keywords }),
|
||||||
...(ossParams.domainUdk && { udk: ossParams.domainUdk })
|
...(ossParams.domainUdk && { udk: ossParams.domainUdk })
|
||||||
})
|
})
|
||||||
const searchApiUrl = `http://rsdo.lhrs.feri.um.si:8080/oss/steviloBesedilPoIskanju?${searchParams}`
|
const searchApiUrl = `${extractionApiOrigin}/oss/steviloBesedilPoIskanju?${searchParams}`
|
||||||
|
|
||||||
const { data: documentCount } = await axios.get(searchApiUrl)
|
const { data: documentCount } = await axios.get(searchApiUrl)
|
||||||
const canSave = documentCount && documentCount <= MAX_OSS_DOCUMENT_COUNT
|
const canSave = documentCount && documentCount <= MAX_OSS_DOCUMENT_COUNT
|
||||||
@@ -183,6 +194,9 @@ extraction.begin = async (req, res) => {
|
|||||||
|
|
||||||
res.send(timeStarted)
|
res.send(timeStarted)
|
||||||
|
|
||||||
|
// Explicitcly catch any errors after the response has been sent
|
||||||
|
// since the the final error handler won't be able to send another.
|
||||||
|
try {
|
||||||
if (ossParams) {
|
if (ossParams) {
|
||||||
// TODO This next method is only a temporary solution.
|
// TODO This next method is only a temporary solution.
|
||||||
// TODO It should be called before response and its execution delegated to a seperate process or at least a seperate thread.
|
// TODO It should be called before response and its execution delegated to a seperate process or at least a seperate thread.
|
||||||
@@ -195,16 +209,24 @@ extraction.begin = async (req, res) => {
|
|||||||
|
|
||||||
const extractionLink = new URL('/luscenje', origin)
|
const extractionLink = new URL('/luscenje', origin)
|
||||||
const renderAsync = promisify(req.app.render.bind(req.app))
|
const renderAsync = promisify(req.app.render.bind(req.app))
|
||||||
const authorEmail = await Extraction.fetchAuthorEmail(extractionId)
|
const { email: authorEmail, language: authorLanguage } =
|
||||||
const emailHtml = await renderAsync('email/extraction-done', {
|
await Extraction.fetchAuthorData(extractionId)
|
||||||
|
const emailHtml = await renderAsync(
|
||||||
|
`email/extraction-done_${authorLanguage}`,
|
||||||
|
{
|
||||||
extractionName,
|
extractionName,
|
||||||
extractionLink
|
extractionLink
|
||||||
})
|
}
|
||||||
|
)
|
||||||
await email.send({
|
await email.send({
|
||||||
to: authorEmail,
|
to: authorEmail,
|
||||||
subject: 'Luščenje končano',
|
subject: i18next.t('Luščenje končano', { lng: authorLanguage }),
|
||||||
html: emailHtml
|
html: emailHtml
|
||||||
})
|
})
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extraction.duplicate = async (req, res) => {
|
extraction.duplicate = async (req, res) => {
|
||||||
@@ -214,13 +236,36 @@ extraction.duplicate = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extraction.termCandidatesExport = async (req, res) => {
|
extraction.termCandidatesExport = async (req, res) => {
|
||||||
// TODO CSV logic (Luka's task)
|
// TODO More formats (CSV, TSV, TXT, ...)
|
||||||
// const extractionId = req.params.id
|
|
||||||
// const { from, to } = req.query
|
const extractionId = req.params.id
|
||||||
// const fromIndex = +from > 1 ? Math.floor(from) - 1 : 0
|
const { from, to } = req.query
|
||||||
// const toIndex = Number.isInteger(+to) ? Math.abs(to) : undefined
|
const fromIndex = +from > 1 ? Math.floor(from) - 1 : 0
|
||||||
// console.log({ extractionId, fromIndex, toIndex })
|
const toIndex = Number.isInteger(+(to === '' ? undefined : to))
|
||||||
res.download('public/images/help-amebis-logo-pug-demo.png')
|
? Math.abs(to)
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
// TODO Authentication, authorization, validation.
|
||||||
|
|
||||||
|
const termCandidatesToExport = await Extraction.fetchTermCandidatesSlice(
|
||||||
|
extractionId,
|
||||||
|
fromIndex,
|
||||||
|
toIndex
|
||||||
|
)
|
||||||
|
|
||||||
|
const exportFileName = `term_candidates_${extractionId}`
|
||||||
|
const exportFilePath = `${TEMP_EXPORT_PATH}/${exportFileName}`
|
||||||
|
await writeFile(
|
||||||
|
exportFilePath,
|
||||||
|
JSON.stringify({ terminoloski_kandidati: termCandidatesToExport })
|
||||||
|
)
|
||||||
|
|
||||||
|
const downloadAsync = promisify(res.download.bind(res))
|
||||||
|
try {
|
||||||
|
await downloadAsync(exportFilePath, 'term_candidates.json')
|
||||||
|
} finally {
|
||||||
|
await unlink(exportFilePath)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extraction.listFinishedForUser = async (req, res) => {
|
extraction.listFinishedForUser = async (req, res) => {
|
||||||
@@ -251,8 +296,11 @@ function extractionFileFilter(req, file, cb) {
|
|||||||
fileType = 'stopTerms'
|
fileType = 'stopTerms'
|
||||||
break
|
break
|
||||||
|
|
||||||
default:
|
default: {
|
||||||
return cb(Error('Invalid API endpoint'))
|
const customError = Error('Invalid API endpoint')
|
||||||
|
customError.displayInProd = true
|
||||||
|
return cb(customError)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const filenamePartsArray = file.originalname.split('.')
|
const filenamePartsArray = file.originalname.split('.')
|
||||||
@@ -264,30 +312,32 @@ function extractionFileFilter(req, file, cb) {
|
|||||||
(fileType === 'stopTerms' &&
|
(fileType === 'stopTerms' &&
|
||||||
fileExtension !== VALID_STOP_TERMS_FILE_EXTENSION)
|
fileExtension !== VALID_STOP_TERMS_FILE_EXTENSION)
|
||||||
) {
|
) {
|
||||||
return cb(Error('Invalid file type'))
|
const customError = Error('Invalid file type')
|
||||||
|
customError.displayInProd = true
|
||||||
|
return cb(customError)
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileName = filenamePartsArray.join('.')
|
const fileName = filenamePartsArray.join('.')
|
||||||
if (!fileName || fileName.length > MAX_FILE_NAME_LENGTH) {
|
if (!fileName || fileName.length > MAX_FILE_NAME_LENGTH) {
|
||||||
return cb(
|
const customError = Error(
|
||||||
Error(
|
|
||||||
`Filename must be between 1 and ${MAX_FILE_NAME_LENGTH} characters long.`
|
`Filename must be between 1 and ${MAX_FILE_NAME_LENGTH} characters long.`
|
||||||
)
|
)
|
||||||
)
|
customError.displayInProd = true
|
||||||
|
return cb(customError)
|
||||||
}
|
}
|
||||||
if (!validator.isAlphanumeric(fileName[0], 'sl-SI', { ignore: '_' })) {
|
if (!validator.isAlphanumeric(fileName[0], 'sl-SI', { ignore: '_' })) {
|
||||||
return cb(
|
const customError = Error(
|
||||||
Error(
|
|
||||||
'Filename must begin with an alphanumeric character or an underscore.'
|
'Filename must begin with an alphanumeric character or an underscore.'
|
||||||
)
|
)
|
||||||
)
|
customError.displayInProd = true
|
||||||
|
return cb(customError)
|
||||||
}
|
}
|
||||||
if (!validator.isAlphanumeric(fileName, 'sl-SI', { ignore: ' _-.' })) {
|
if (!validator.isAlphanumeric(fileName, 'sl-SI', { ignore: ' _-.' })) {
|
||||||
return cb(
|
const customError = Error(
|
||||||
Error(
|
|
||||||
'Filename can only contain alphanumeric characters, spaces, underscores, minuses and periods.'
|
'Filename can only contain alphanumeric characters, spaces, underscores, minuses and periods.'
|
||||||
)
|
)
|
||||||
)
|
customError.displayInProd = true
|
||||||
|
return cb(customError)
|
||||||
}
|
}
|
||||||
|
|
||||||
req.fileType = fileType
|
req.fileType = fileType
|
||||||
|
|||||||
@@ -66,6 +66,27 @@ exports.listMainEntries = async (req, res) => {
|
|||||||
{}
|
{}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// duplicated code below for filtering foreignent, optimize later
|
||||||
|
const categoriesLabels = Object.keys(entriesByCategory)
|
||||||
|
for (
|
||||||
|
let categoryIndex = 0;
|
||||||
|
categoryIndex < categoriesLabels.length;
|
||||||
|
categoryIndex++
|
||||||
|
) {
|
||||||
|
entriesByCategory[categoriesLabels[categoryIndex]] = entriesByCategory[
|
||||||
|
categoriesLabels[categoryIndex]
|
||||||
|
].map(entry => {
|
||||||
|
entry.foreignEntries = entry.foreignEntries?.filter(foreignEntry => {
|
||||||
|
if (filters.targetLanguages.length > 0) {
|
||||||
|
return filters.targetLanguages.includes(`${foreignEntry.lang.id}`)
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return entry
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// res.send({ page, numberOfAllPages, entries })
|
// res.send({ page, numberOfAllPages, entries })
|
||||||
res.append('page', page)
|
res.append('page', page)
|
||||||
res.append('number-of-all-pages', numberOfAllPages)
|
res.append('number-of-all-pages', numberOfAllPages)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ const { DEFAULT_HITS_PER_PAGE } = require('../config/settings')
|
|||||||
const generateQuery = require('../models/helpers/search/generate-query')
|
const generateQuery = require('../models/helpers/search/generate-query')
|
||||||
const { searchConsultancyEntryIndex } = require('../models/search-engine')
|
const { searchConsultancyEntryIndex } = require('../models/search-engine')
|
||||||
const { prepareConsultancyEntries } = require('../models/helpers/search')
|
const { prepareConsultancyEntries } = require('../models/helpers/search')
|
||||||
|
const { getInstanceSetting, intoDbArray } = require('../models/helpers')
|
||||||
// const { minEntriesRequirementCheckAndAct } = require('./helpers/dictionary')
|
// const { minEntriesRequirementCheckAndAct } = require('./helpers/dictionary')
|
||||||
|
|
||||||
const consultancy = {}
|
const consultancy = {}
|
||||||
@@ -16,6 +17,9 @@ const consultancyAdmin = {}
|
|||||||
consultancy.index = async (req, res) => {
|
consultancy.index = async (req, res) => {
|
||||||
req.indexHitPageAmount = '5'
|
req.indexHitPageAmount = '5'
|
||||||
|
|
||||||
|
res.locals.isOwnConsultancyEnabled =
|
||||||
|
(await getInstanceSetting('consultancy_type')) === 'own'
|
||||||
|
|
||||||
return await consultancyRequest(
|
return await consultancyRequest(
|
||||||
req,
|
req,
|
||||||
res,
|
res,
|
||||||
@@ -25,17 +29,24 @@ consultancy.index = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
consultancy.search = async (req, res) => {
|
consultancy.search = async (req, res) => {
|
||||||
|
res.locals.isOwnConsultancyEnabled =
|
||||||
|
(await getInstanceSetting('consultancy_type')) === 'own'
|
||||||
|
|
||||||
|
res.locals.queryKey = req.query.q
|
||||||
|
|
||||||
return await consultancyRequest(
|
return await consultancyRequest(
|
||||||
req,
|
req,
|
||||||
res,
|
res,
|
||||||
'published',
|
'published',
|
||||||
'pages/consultancy/search'
|
'pages/consultancy/search',
|
||||||
|
req.t('Odgovori')
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
consultancy.specificQuestion = async (req, res) => {
|
consultancy.specificQuestion = async (req, res) => {
|
||||||
const { id } = req.params
|
const { id } = req.params
|
||||||
|
|
||||||
|
// TODO i18n TIME FORMAT
|
||||||
const entry = await ConsultancyEntry.fetchByIdWithFormattedTime(id)
|
const entry = await ConsultancyEntry.fetchByIdWithFormattedTime(id)
|
||||||
// const author = await User.fetchUser(entry.authorId)
|
// const author = await User.fetchUser(entry.authorId)
|
||||||
|
|
||||||
@@ -44,12 +55,13 @@ consultancy.specificQuestion = async (req, res) => {
|
|||||||
entry.answerAuthors = entry.answerAuthors.filter(author => author !== '')
|
entry.answerAuthors = entry.answerAuthors.filter(author => author !== '')
|
||||||
|
|
||||||
let authorString
|
let authorString
|
||||||
|
// TODO I18n
|
||||||
if (entry.answerAuthors.length === 1) {
|
if (entry.answerAuthors.length === 1) {
|
||||||
authorString = 'Avtor'
|
authorString = req.t('Avtor')
|
||||||
} else if (entry.answerAuthors.length === 2) {
|
} else if (entry.answerAuthors.length === 2) {
|
||||||
authorString = 'Avtorja'
|
authorString = req.t('Avtorja')
|
||||||
} else {
|
} else {
|
||||||
authorString = 'Avtorji'
|
authorString = req.t('Avtorji')
|
||||||
}
|
}
|
||||||
|
|
||||||
entry.domain = allPrimaryDomains.filter(
|
entry.domain = allPrimaryDomains.filter(
|
||||||
@@ -62,18 +74,26 @@ consultancy.specificQuestion = async (req, res) => {
|
|||||||
entry.domain = false
|
entry.domain = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
res.locals.isOwnConsultancyEnabled =
|
||||||
|
(await getInstanceSetting('consultancy_type')) === 'own'
|
||||||
|
|
||||||
res.render('pages/consultancy/item-details', {
|
res.render('pages/consultancy/item-details', {
|
||||||
allPrimaryDomains,
|
allPrimaryDomains,
|
||||||
authorString,
|
authorString,
|
||||||
entry
|
entry,
|
||||||
|
title: req.t('Odgovor')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
consultancy.new = async (req, res) => {
|
consultancy.new = async (req, res) => {
|
||||||
const allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains()
|
const allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains()
|
||||||
|
|
||||||
|
res.locals.isOwnConsultancyEnabled =
|
||||||
|
(await getInstanceSetting('consultancy_type')) === 'own'
|
||||||
|
|
||||||
res.render('pages/consultancy/ask', {
|
res.render('pages/consultancy/ask', {
|
||||||
allPrimaryDomains
|
allPrimaryDomains,
|
||||||
|
title: req.t('Novo vprašanje')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,6 +103,7 @@ consultancyAdmin.new = async (req, res) => {
|
|||||||
res,
|
res,
|
||||||
'new',
|
'new',
|
||||||
'pages/consultancy/admin/index',
|
'pages/consultancy/admin/index',
|
||||||
|
req.t('Novo'),
|
||||||
true,
|
true,
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
@@ -95,7 +116,8 @@ consultancyAdmin.users = async (req, res) => {
|
|||||||
|
|
||||||
res.render('pages/consultancy/admin/users', {
|
res.render('pages/consultancy/admin/users', {
|
||||||
allPrimaryDomains,
|
allPrimaryDomains,
|
||||||
users
|
users,
|
||||||
|
title: req.t('Svetovalci')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,6 +127,7 @@ consultancyAdmin.rejected = async (req, res) => {
|
|||||||
res,
|
res,
|
||||||
'rejected',
|
'rejected',
|
||||||
'pages/consultancy/admin/rejected',
|
'pages/consultancy/admin/rejected',
|
||||||
|
req.t('Zavrnjeno'),
|
||||||
true,
|
true,
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
@@ -116,8 +139,10 @@ consultancyAdmin.published = async (req, res) => {
|
|||||||
res,
|
res,
|
||||||
'published',
|
'published',
|
||||||
'pages/consultancy/admin/published',
|
'pages/consultancy/admin/published',
|
||||||
|
req.t('Objavljeno'),
|
||||||
true,
|
true,
|
||||||
false
|
false,
|
||||||
|
'published'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,6 +152,7 @@ consultancyAdmin.prepared = async (req, res) => {
|
|||||||
res,
|
res,
|
||||||
'review',
|
'review',
|
||||||
'pages/consultancy/admin/prepared',
|
'pages/consultancy/admin/prepared',
|
||||||
|
req.t('Pripravljeno'),
|
||||||
true,
|
true,
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
@@ -142,6 +168,7 @@ consultancyAdmin.inProgress = async (req, res) => {
|
|||||||
res,
|
res,
|
||||||
'in progress',
|
'in progress',
|
||||||
'pages/consultancy/admin/in-progress',
|
'pages/consultancy/admin/in-progress',
|
||||||
|
req.t('V delu'),
|
||||||
true,
|
true,
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
@@ -171,6 +198,7 @@ consultancyAdmin.edit = async (req, res) => {
|
|||||||
} else if (editors.filter(editors => editors.id === req.user.id) < 1) {
|
} else if (editors.filter(editors => editors.id === req.user.id) < 1) {
|
||||||
return res.send('You do not have permsisions to edit this answer')
|
return res.send('You do not have permsisions to edit this answer')
|
||||||
}
|
}
|
||||||
|
// TODO i18n TIME FORMAT
|
||||||
const entry = await ConsultancyEntry.fetchByIdWithFormattedTime(id)
|
const entry = await ConsultancyEntry.fetchByIdWithFormattedTime(id)
|
||||||
const allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains()
|
const allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains()
|
||||||
const author = await User.fetchUser(entry.authorId)
|
const author = await User.fetchUser(entry.authorId)
|
||||||
@@ -186,7 +214,8 @@ consultancyAdmin.edit = async (req, res) => {
|
|||||||
author,
|
author,
|
||||||
isPublished,
|
isPublished,
|
||||||
// TODO Luka: I suspect this will not work as intended on staging or production environments. Test.
|
// TODO Luka: I suspect this will not work as intended on staging or production environments. Test.
|
||||||
urlPrefix: req.protocol + '://' + req.get('host')
|
urlPrefix: req.protocol + '://' + req.get('host'),
|
||||||
|
title: req.t('Urejanje')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,14 +227,14 @@ function dateMap(obj) {
|
|||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
|
|
||||||
async function mapDomainIdToDomainNameSlovene(obj) {
|
async function mapDomainIdToDomainNameSlovene(obj, t) {
|
||||||
try {
|
try {
|
||||||
const area = await Domain.fetchById(
|
const area = await Domain.fetchById(
|
||||||
obj.domainPrimaryId ? obj.domainPrimaryId : obj.domainPrimaryIdInitial
|
obj.domainPrimaryId ? obj.domainPrimaryId : obj.domainPrimaryIdInitial
|
||||||
)
|
)
|
||||||
obj.area = area.nameSl
|
obj.area = area.nameSl
|
||||||
} catch {
|
} catch {
|
||||||
obj.area = 'Ni področja'
|
obj.area = t('Ni področja')
|
||||||
}
|
}
|
||||||
|
|
||||||
return obj
|
return obj
|
||||||
@@ -227,14 +256,14 @@ function mapInitialValuesAsEmpty(obj) {
|
|||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
|
|
||||||
async function mapEntryList(list) {
|
async function mapEntryList(list, t) {
|
||||||
return await Promise.all(
|
return await Promise.all(
|
||||||
list.map(entry => {
|
list.map(entry => {
|
||||||
let entity = utils.compose(dateMap, mapInitialValuesAsEmpty)(entry)
|
let entity = utils.compose(dateMap, mapInitialValuesAsEmpty)(entry)
|
||||||
|
|
||||||
// TODO Each mapDomainIdToDomainNameSlovene call leads to one DB query.
|
// TODO Each mapDomainIdToDomainNameSlovene call leads to one DB query.
|
||||||
// TODO Test if and what scenarios can lead to too many calls and how it can be avoided.
|
// TODO Test if and what scenarios can lead to too many calls and how it can be avoided.
|
||||||
entity = utils.composeAsync(mapDomainIdToDomainNameSlovene)(entry)
|
entity = utils.composeAsync(mapDomainIdToDomainNameSlovene)(entry, t)
|
||||||
|
|
||||||
return entity
|
return entity
|
||||||
})
|
})
|
||||||
@@ -283,11 +312,26 @@ async function consultancyRequest(
|
|||||||
res,
|
res,
|
||||||
type,
|
type,
|
||||||
url,
|
url,
|
||||||
|
title = req.t('Svetovanje'),
|
||||||
isAdminPage = false,
|
isAdminPage = false,
|
||||||
privilegeToSeAll = true // this method seperates consultancy main from admin, so all results get visible TO ALL REGISTERED USERS, not just admins
|
privilegeToSeAll = true // this method seperates consultancy main from admin, so all results get visible TO ALL REGISTERED USERS, not just admins
|
||||||
) {
|
) {
|
||||||
const searchString = req.query.q?.trim() ?? ''
|
const searchString = req.query.q?.trim() ?? ''
|
||||||
|
|
||||||
|
let allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains()
|
||||||
|
|
||||||
|
/// filter selected domains for the prompt ///
|
||||||
|
// Not duplicates of this code arise...
|
||||||
|
const pdList = intoDbArray(req.query.pd, 'always')
|
||||||
|
|
||||||
|
allPrimaryDomains = allPrimaryDomains.map(entry => {
|
||||||
|
if (pdList.includes(`${entry.id}`)) {
|
||||||
|
entry.selected = true
|
||||||
|
}
|
||||||
|
return entry
|
||||||
|
})
|
||||||
|
/// //////////////////////////////////////////
|
||||||
|
|
||||||
let assignedConsultant
|
let assignedConsultant
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -333,11 +377,11 @@ async function consultancyRequest(
|
|||||||
|
|
||||||
let entries = prepareConsultancyEntries(hits)
|
let entries = prepareConsultancyEntries(hits)
|
||||||
// console.log({ entries, numberOfAllHits, numberOfAllPages })
|
// console.log({ entries, numberOfAllHits, numberOfAllPages })
|
||||||
|
// TODO I18n - nameSl
|
||||||
entries = entries.map(entry => {
|
entries = entries.map(entry => {
|
||||||
entry.primaryDomain = entry.primaryDomain
|
entry.primaryDomain = entry.primaryDomain
|
||||||
? entry.primaryDomain.nameSl
|
? entry.primaryDomain.nameSl
|
||||||
: 'nedefinirano'
|
: req.t('nedefinirano')
|
||||||
|
|
||||||
if (entry.assignedConsultants) {
|
if (entry.assignedConsultants) {
|
||||||
entry.firstName = entry.assignedConsultants[0]?.firstName
|
entry.firstName = entry.assignedConsultants[0]?.firstName
|
||||||
@@ -365,8 +409,6 @@ async function consultancyRequest(
|
|||||||
return entry
|
return entry
|
||||||
})
|
})
|
||||||
|
|
||||||
const allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains()
|
|
||||||
|
|
||||||
// const entryList = await mapEntryList(inProgressEntryList)
|
// const entryList = await mapEntryList(inProgressEntryList)
|
||||||
const userList = await User.fetchConsultants()
|
const userList = await User.fetchConsultants()
|
||||||
|
|
||||||
@@ -401,7 +443,9 @@ async function consultancyRequest(
|
|||||||
entries, // entryList,
|
entries, // entryList,
|
||||||
userList,
|
userList,
|
||||||
numberOfAllPages,
|
numberOfAllPages,
|
||||||
queryCount: numberOfAllHits
|
queryCount: numberOfAllHits,
|
||||||
|
consultancyPageType: type,
|
||||||
|
title
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ const Comment = require('../models/comment')
|
|||||||
const genEditorAllQuery = require('../models/helpers/search/generate-query/editor/all')
|
const genEditorAllQuery = require('../models/helpers/search/generate-query/editor/all')
|
||||||
const { searchEntryIndex } = require('../models/search-engine')
|
const { searchEntryIndex } = require('../models/search-engine')
|
||||||
const { prepareEditorEntries } = require('../models/helpers/search')
|
const { prepareEditorEntries } = require('../models/helpers/search')
|
||||||
const { getInstanceSetting } = require('../models/helpers')
|
const { getInstanceSetting, intoDbArray } = require('../models/helpers')
|
||||||
|
const { getExportFilesPath } = require('../models/helpers/dictionary')
|
||||||
const { DEFAULT_HITS_PER_PAGE, DATA_FILES_PATH } = require('../config/settings')
|
const { DEFAULT_HITS_PER_PAGE, DATA_FILES_PATH } = require('../config/settings')
|
||||||
const {
|
const {
|
||||||
statusChangeCheckAndAct,
|
statusChangeCheckAndAct,
|
||||||
@@ -17,6 +18,7 @@ const {
|
|||||||
} = require('./helpers/dictionary')
|
} = require('./helpers/dictionary')
|
||||||
const SFDSuggestionImporter = require('./helpers/search-filter-data-suggestion-importer')
|
const SFDSuggestionImporter = require('./helpers/search-filter-data-suggestion-importer')
|
||||||
const Extraction = require('../models/extraction')
|
const Extraction = require('../models/extraction')
|
||||||
|
const { isGeneratorFunction } = require('util/types')
|
||||||
|
|
||||||
const importFileBodyParser = multer({
|
const importFileBodyParser = multer({
|
||||||
dest: `${DATA_FILES_PATH}/dict_import_temp`,
|
dest: `${DATA_FILES_PATH}/dict_import_temp`,
|
||||||
@@ -39,7 +41,7 @@ dictionary.list = async (req, res) => {
|
|||||||
dictionaries = await Dictionary.fetchAllByUser(req.user.id)
|
dictionaries = await Dictionary.fetchAllByUser(req.user.id)
|
||||||
}
|
}
|
||||||
res.render('pages/dictionaries/list', {
|
res.render('pages/dictionaries/list', {
|
||||||
title: 'Seznam slovarjev',
|
title: req.t('Seznam slovarjev'),
|
||||||
dictionaries
|
dictionaries
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -51,11 +53,11 @@ dictionary.new = async (req, res) => {
|
|||||||
await Promise.all([
|
await Promise.all([
|
||||||
Dictionary.fetchAllPrimaryDomains(),
|
Dictionary.fetchAllPrimaryDomains(),
|
||||||
Dictionary.fetchAllApprovedSecondaryDomains(),
|
Dictionary.fetchAllApprovedSecondaryDomains(),
|
||||||
Dictionary.fetchAllLanguages(language)
|
Dictionary.fetchAllLanguages(language, true)
|
||||||
])
|
])
|
||||||
|
|
||||||
res.render('pages/dictionaries/new', {
|
res.render('pages/dictionaries/new', {
|
||||||
title: 'Nov slovar',
|
title: req.t('Nov slovar'),
|
||||||
allPrimaryDomains,
|
allPrimaryDomains,
|
||||||
allSecondaryDomains,
|
allSecondaryDomains,
|
||||||
allLanguages
|
allLanguages
|
||||||
@@ -82,7 +84,7 @@ dictionary.editDescription = async (req, res) => {
|
|||||||
])
|
])
|
||||||
|
|
||||||
res.render('pages/dictionaries/description', {
|
res.render('pages/dictionaries/description', {
|
||||||
title: 'Ime in opis',
|
title: req.t('Osnovni podatki'),
|
||||||
allPrimaryDomains,
|
allPrimaryDomains,
|
||||||
allSecondaryDomains,
|
allSecondaryDomains,
|
||||||
dictionary,
|
dictionary,
|
||||||
@@ -125,7 +127,7 @@ dictionary.editUsers = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
res.render(viewPath, {
|
res.render(viewPath, {
|
||||||
title: 'Uporabniki',
|
title: req.t('Uporabniki'),
|
||||||
dictionary,
|
dictionary,
|
||||||
userRights,
|
userRights,
|
||||||
entriesCount,
|
entriesCount,
|
||||||
@@ -142,6 +144,7 @@ dictionary.updateUsers = async (req, res) => {
|
|||||||
|
|
||||||
const newDictStatus = await determineNewStatus(isPublished)
|
const newDictStatus = await determineNewStatus(isPublished)
|
||||||
|
|
||||||
|
// TODO I18n - nameSl
|
||||||
const { nameSl, status: oldDictStatus } = await Dictionary.fetchEditUsers(
|
const { nameSl, status: oldDictStatus } = await Dictionary.fetchEditUsers(
|
||||||
dictionaryId
|
dictionaryId
|
||||||
)
|
)
|
||||||
@@ -172,12 +175,13 @@ dictionary.updateUsers = async (req, res) => {
|
|||||||
|
|
||||||
dictionary.editStructure = async (req, res) => {
|
dictionary.editStructure = async (req, res) => {
|
||||||
// TODO Once english language is implemented, gather selected language (sl/en) from request ~ (cookies?)
|
// TODO Once english language is implemented, gather selected language (sl/en) from request ~ (cookies?)
|
||||||
|
// TODO I18n - nameSl
|
||||||
const language = 'name_sl'
|
const language = 'name_sl'
|
||||||
const { dictionaryId } = req.params
|
const { dictionaryId } = req.params
|
||||||
const [dictionary, associatedLanguages, allLanguages] = await Promise.all([
|
const [dictionary, associatedLanguages, allLanguages] = await Promise.all([
|
||||||
Dictionary.fetchEditStructure(dictionaryId),
|
Dictionary.fetchEditStructure(dictionaryId),
|
||||||
Dictionary.fetchLanguages(dictionaryId),
|
Dictionary.fetchLanguages(dictionaryId),
|
||||||
Dictionary.fetchAllLanguages(language)
|
Dictionary.fetchAllLanguages(language, true)
|
||||||
])
|
])
|
||||||
|
|
||||||
let viewPath
|
let viewPath
|
||||||
@@ -190,7 +194,7 @@ dictionary.editStructure = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
res.render(viewPath, {
|
res.render(viewPath, {
|
||||||
title: 'Struktura slovarskega sestavka',
|
title: req.t('Struktura slovarskega sestavka'),
|
||||||
dictionary,
|
dictionary,
|
||||||
associatedLanguages,
|
associatedLanguages,
|
||||||
allLanguages
|
allLanguages
|
||||||
@@ -223,7 +227,7 @@ dictionary.editAdvanced = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
res.render(viewPath, {
|
res.render(viewPath, {
|
||||||
title: 'Napredno',
|
title: req.t('Napredno'),
|
||||||
dictionary: { id: req.params.dictionaryId },
|
dictionary: { id: req.params.dictionaryId },
|
||||||
dictionaryName
|
dictionaryName
|
||||||
})
|
})
|
||||||
@@ -251,7 +255,7 @@ dictionary.comments = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
res.render(viewPath, {
|
res.render(viewPath, {
|
||||||
title: 'Komentarji',
|
title: req.t('Komentarji'),
|
||||||
numberOfAllPages,
|
numberOfAllPages,
|
||||||
dictionary: { id: req.params.dictionaryId },
|
dictionary: { id: req.params.dictionaryId },
|
||||||
comments,
|
comments,
|
||||||
@@ -261,8 +265,10 @@ dictionary.comments = async (req, res) => {
|
|||||||
|
|
||||||
dictionary.showImportFromFileForm = async (req, res) => {
|
dictionary.showImportFromFileForm = async (req, res) => {
|
||||||
const { dictionaryId } = req.params
|
const { dictionaryId } = req.params
|
||||||
const [imports, dictionaryName] = await Promise.all([
|
const resultsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
|
||||||
Dictionary.fetchAllImports(dictionaryId),
|
const [{ pages_total: numberOfAllPages, results }, dictionaryName] =
|
||||||
|
await Promise.all([
|
||||||
|
Dictionary.fetchAllImports(dictionaryId, resultsPerPage, 1),
|
||||||
Dictionary.fetchName(dictionaryId)
|
Dictionary.fetchName(dictionaryId)
|
||||||
])
|
])
|
||||||
let viewPath
|
let viewPath
|
||||||
@@ -275,9 +281,10 @@ dictionary.showImportFromFileForm = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
res.render(viewPath, {
|
res.render(viewPath, {
|
||||||
title: 'Uvoz iz datoteke',
|
title: req.t('Uvoz iz datoteke'),
|
||||||
dictionary: { id: dictionaryId },
|
dictionary: { id: dictionaryId },
|
||||||
imports,
|
numberOfAllPages,
|
||||||
|
results,
|
||||||
dictionaryName
|
dictionaryName
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -289,7 +296,7 @@ dictionary.listAdminDictionaries = async (req, res) => {
|
|||||||
await Dictionary.fetchAllAdminDictionaries(resultsPerPage, 1)
|
await Dictionary.fetchAllAdminDictionaries(resultsPerPage, 1)
|
||||||
|
|
||||||
res.render('pages/admin/dictionaries-list', {
|
res.render('pages/admin/dictionaries-list', {
|
||||||
title: 'Struktura slovarjev',
|
title: req.t('Seznam slovarjev'),
|
||||||
numberOfAllPages,
|
numberOfAllPages,
|
||||||
results
|
results
|
||||||
})
|
})
|
||||||
@@ -312,7 +319,7 @@ dictionary.adminEditDescription = async (req, res) => {
|
|||||||
])
|
])
|
||||||
|
|
||||||
res.render('pages/admin/dictionary-description', {
|
res.render('pages/admin/dictionary-description', {
|
||||||
title: 'Podatki',
|
title: req.t('Osnovni podatki'),
|
||||||
allPrimaryDomains,
|
allPrimaryDomains,
|
||||||
allSecondaryDomains,
|
allSecondaryDomains,
|
||||||
dictionary,
|
dictionary,
|
||||||
@@ -364,11 +371,11 @@ dictionary.showImportFromExtractionForm = async (req, res) => {
|
|||||||
switch (req.baseUrl) {
|
switch (req.baseUrl) {
|
||||||
case '/slovarji':
|
case '/slovarji':
|
||||||
viewPath = 'pages/dictionaries/extraction-import'
|
viewPath = 'pages/dictionaries/extraction-import'
|
||||||
title = 'Uvoz'
|
title = req.t('Uvoz iz luščilnika')
|
||||||
break
|
break
|
||||||
case '/admin':
|
case '/admin':
|
||||||
viewPath = 'pages/admin/dictionary-extraction-import'
|
viewPath = 'pages/admin/dictionary-extraction-import'
|
||||||
title = 'Uvoz luščenje'
|
title = req.t('Uvoz iz luščilnika')
|
||||||
}
|
}
|
||||||
|
|
||||||
res.render(viewPath, {
|
res.render(viewPath, {
|
||||||
@@ -381,7 +388,12 @@ dictionary.showImportFromExtractionForm = async (req, res) => {
|
|||||||
|
|
||||||
dictionary.showExportToFileForm = async (req, res) => {
|
dictionary.showExportToFileForm = async (req, res) => {
|
||||||
const { dictionaryId } = req.params
|
const { dictionaryId } = req.params
|
||||||
const dictionaryName = await Dictionary.fetchName(dictionaryId)
|
const resultsPerPage = req.user?.hitsPerPage || DEFAULT_HITS_PER_PAGE
|
||||||
|
const [dictionaryName, { pages_total: numberOfAllPages, results }] =
|
||||||
|
await Promise.all([
|
||||||
|
Dictionary.fetchName(dictionaryId),
|
||||||
|
Dictionary.fetchExports(dictionaryId, resultsPerPage, 1)
|
||||||
|
])
|
||||||
let viewPath
|
let viewPath
|
||||||
switch (req.baseUrl) {
|
switch (req.baseUrl) {
|
||||||
case '/slovarji':
|
case '/slovarji':
|
||||||
@@ -390,11 +402,12 @@ dictionary.showExportToFileForm = async (req, res) => {
|
|||||||
case '/admin':
|
case '/admin':
|
||||||
viewPath = 'pages/admin/dictionary-export'
|
viewPath = 'pages/admin/dictionary-export'
|
||||||
}
|
}
|
||||||
|
|
||||||
res.render(viewPath, {
|
res.render(viewPath, {
|
||||||
title: 'Izvoz',
|
title: req.t('Izvoz'),
|
||||||
dictionary: { id: req.params.dictionaryId },
|
dictionary: { id: dictionaryId },
|
||||||
dictionaryName
|
dictionaryName,
|
||||||
|
numberOfAllPages,
|
||||||
|
results
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -418,7 +431,7 @@ dictionary.editDomainLabels = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
res.render(viewPath, {
|
res.render(viewPath, {
|
||||||
title: 'Področne oznake',
|
title: req.t('Področne oznake'),
|
||||||
dictionary: { id: dictionaryId },
|
dictionary: { id: dictionaryId },
|
||||||
numberOfAllPages,
|
numberOfAllPages,
|
||||||
results,
|
results,
|
||||||
@@ -448,7 +461,7 @@ dictionary.showContent = async (req, res) => {
|
|||||||
const terms = prepareEditorEntries(hits)
|
const terms = prepareEditorEntries(hits)
|
||||||
|
|
||||||
res.render('pages/dictionaries/content', {
|
res.render('pages/dictionaries/content', {
|
||||||
title: 'Vsebina slovarja',
|
title: req.t('Vsebina slovarja'),
|
||||||
terms,
|
terms,
|
||||||
canPublishEntriesInEdit,
|
canPublishEntriesInEdit,
|
||||||
dictionaryName,
|
dictionaryName,
|
||||||
@@ -466,7 +479,7 @@ dictionary.showSecondaryDomains = async (req, res) => {
|
|||||||
await Dictionary.fetchAllSecondaryDomains(resultsPerPage, 1)
|
await Dictionary.fetchAllSecondaryDomains(resultsPerPage, 1)
|
||||||
|
|
||||||
res.render('pages/admin/areas', {
|
res.render('pages/admin/areas', {
|
||||||
title: 'Podpodročja',
|
title: req.t('Področne oznake'),
|
||||||
numberOfAllPages,
|
numberOfAllPages,
|
||||||
results
|
results
|
||||||
})
|
})
|
||||||
@@ -501,12 +514,27 @@ dictionary.dictionaryList = async (req, res) => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const numberOfAllHits = parseInt(
|
const numberOfAllHits = parseInt(
|
||||||
(await Dictionary.fetchAllDictionariesCount()).count
|
(await Dictionary.fetchAllDictionariesPublishedCount()).count
|
||||||
)
|
)
|
||||||
const numberOfAllPages = Math.ceil(numberOfAllHits / hitsPerPage)
|
const numberOfAllPages = Math.ceil(numberOfAllHits / hitsPerPage)
|
||||||
|
|
||||||
const allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains()
|
const allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains()
|
||||||
|
|
||||||
|
/*
|
||||||
|
/// filter selected domains for the prompt ///
|
||||||
|
// Not duplicates of this code arise...
|
||||||
|
const pdList = intoDbArray(req.query.pd, 'always')
|
||||||
|
|
||||||
|
allPrimaryDomains = allPrimaryDomains.map(entry => {
|
||||||
|
if (pdList.includes(`${entry.id}`)) {
|
||||||
|
entry.selected = true
|
||||||
|
}
|
||||||
|
return entry
|
||||||
|
})
|
||||||
|
|
||||||
|
/// //////////////////////////////////////////
|
||||||
|
*/
|
||||||
|
|
||||||
dictionaries = dictionaries.map(e => {
|
dictionaries = dictionaries.map(e => {
|
||||||
if (!e.portalcode) {
|
if (!e.portalcode) {
|
||||||
e.portalcode = defaultportalcode
|
e.portalcode = defaultportalcode
|
||||||
@@ -518,7 +546,7 @@ dictionary.dictionaryList = async (req, res) => {
|
|||||||
const isDictionaryListPage = true
|
const isDictionaryListPage = true
|
||||||
|
|
||||||
res.render('pages/dictionaries/dictlist', {
|
res.render('pages/dictionaries/dictlist', {
|
||||||
title: 'Seznam slovarjev',
|
title: req.t('Seznam slovarjev'),
|
||||||
dictionaries,
|
dictionaries,
|
||||||
allPrimaryDomains,
|
allPrimaryDomains,
|
||||||
numberOfAllPages,
|
numberOfAllPages,
|
||||||
@@ -532,6 +560,7 @@ dictionary.dictionaryList = async (req, res) => {
|
|||||||
dictionary.dictionaryDetails = async (req, res) => {
|
dictionary.dictionaryDetails = async (req, res) => {
|
||||||
const { absolutePrevPath, sentFromEntryId } = req.query
|
const { absolutePrevPath, sentFromEntryId } = req.query
|
||||||
const dictId = req.params.dictionaryId
|
const dictId = req.params.dictionaryId
|
||||||
|
const title = req.t('O slovarju')
|
||||||
const {
|
const {
|
||||||
allPrimaryDomains,
|
allPrimaryDomains,
|
||||||
sourceLanguages,
|
sourceLanguages,
|
||||||
@@ -555,7 +584,7 @@ dictionary.dictionaryDetails = async (req, res) => {
|
|||||||
|
|
||||||
// check if it is a local dictionary
|
// check if it is a local dictionary
|
||||||
if (!dictionaryData.portalname && !dictionaryData.portalcode) {
|
if (!dictionaryData.portalname && !dictionaryData.portalcode) {
|
||||||
dictionaryData[0].portalname = await getInstanceSetting('portal_name')
|
dictionaryData[0].portalname = await getInstanceSetting('portal_name_sl')
|
||||||
dictionaryData[0].portalcode = await getInstanceSetting('portal_code')
|
dictionaryData[0].portalcode = await getInstanceSetting('portal_code')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -579,7 +608,7 @@ dictionary.dictionaryDetails = async (req, res) => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const structData = {
|
const structData = {
|
||||||
prevWindowTitle: 'Nazaj',
|
prevWindowTitle: req.t('Nazaj'),
|
||||||
dictName: dictionaryData[0].dictionarysl,
|
dictName: dictionaryData[0].dictionarysl,
|
||||||
portalCode: dictionaryData[0].portalcode,
|
portalCode: dictionaryData[0].portalcode,
|
||||||
portalName: dictionaryData[0].portalname,
|
portalName: dictionaryData[0].portalname,
|
||||||
@@ -589,13 +618,14 @@ dictionary.dictionaryDetails = async (req, res) => {
|
|||||||
languages: reducedData.languages ? reducedData.languages.join(', ') : ''
|
languages: reducedData.languages ? reducedData.languages.join(', ') : ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO I18n
|
||||||
if (reducedData.author) {
|
if (reducedData.author) {
|
||||||
if (reducedData.author.length > 2) {
|
if (reducedData.author.length > 2) {
|
||||||
structData.authorLabel = 'Avtorji'
|
structData.authorLabel = req.t('Avtorji')
|
||||||
} else if (reducedData.author.length === 2) {
|
} else if (reducedData.author.length === 2) {
|
||||||
structData.authorLabel = 'Avtorja'
|
structData.authorLabel = req.t('Avtorja')
|
||||||
} else if (reducedData.author.length === 1) {
|
} else if (reducedData.author.length === 1) {
|
||||||
structData.authorLabel = 'Avtor'
|
structData.authorLabel = req.t('Avtor')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -623,7 +653,8 @@ dictionary.dictionaryDetails = async (req, res) => {
|
|||||||
finalData,
|
finalData,
|
||||||
numberOfAllPages,
|
numberOfAllPages,
|
||||||
comments,
|
comments,
|
||||||
commentCount
|
commentCount,
|
||||||
|
title
|
||||||
}) // todo
|
}) // todo
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -680,8 +711,28 @@ dictionary.importFromFile = async (req, res) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dictionary.exportDownload = async (req, res) => {
|
||||||
|
// TODO Add authentication and authorization.
|
||||||
|
|
||||||
|
const { exportId } = req.params
|
||||||
|
const { exportStatus, dictionaryId, nameString, timeString, fileFormat } =
|
||||||
|
await Dictionary.fetchExportDownloadMetadata(exportId)
|
||||||
|
if (exportStatus !== 'finished') {
|
||||||
|
throw Error("Can't request file for unfinished export")
|
||||||
|
}
|
||||||
|
const exportFilesPath = getExportFilesPath(dictionaryId)
|
||||||
|
const exportFilePath = `${exportFilesPath}/${exportId}`
|
||||||
|
const exportFileName = `${nameString}_${timeString}.${fileFormat}`
|
||||||
|
|
||||||
|
res.download(exportFilePath, exportFileName)
|
||||||
|
}
|
||||||
|
|
||||||
function importFileFilter(req, file, cb) {
|
function importFileFilter(req, file, cb) {
|
||||||
if (file.mimetype !== 'text/xml') return cb(Error('Invalid file type'))
|
if (file.mimetype !== 'text/xml') {
|
||||||
|
const customError = Error('Invalid file type')
|
||||||
|
customError.displayInProd = true
|
||||||
|
return cb(customError)
|
||||||
|
}
|
||||||
cb(null, true)
|
cb(null, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,10 @@ extraction.list = async (req, res) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
res.render('pages/extraction/list', { title: 'Luščenje seznam', extractions })
|
res.render('pages/extraction/list', {
|
||||||
|
title: req.t('Seznam luščenj'),
|
||||||
|
extractions
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
extraction.create = async (req, res) => {
|
extraction.create = async (req, res) => {
|
||||||
@@ -39,7 +42,7 @@ extraction.create = async (req, res) => {
|
|||||||
return res.redirect(303, 'back')
|
return res.redirect(303, 'back')
|
||||||
}
|
}
|
||||||
|
|
||||||
const extractionName = `Luščenje ${extractionCount + 1}`
|
const extractionName = req.t('Luščenje') + `${extractionCount + 1}`
|
||||||
const { extractionType } = req.body
|
const { extractionType } = req.body
|
||||||
|
|
||||||
let extractionId
|
let extractionId
|
||||||
@@ -81,7 +84,7 @@ extraction.edit = async (req, res) => {
|
|||||||
extraction.keywords = intoDbArray(params.keywords, 'always')
|
extraction.keywords = intoDbArray(params.keywords, 'always')
|
||||||
|
|
||||||
res.render('pages/extraction/edit-oss', {
|
res.render('pages/extraction/edit-oss', {
|
||||||
title: 'KAS + dokumenti',
|
title: req.t('Besedila'),
|
||||||
id: extractionId,
|
id: extractionId,
|
||||||
extraction,
|
extraction,
|
||||||
allPrimaryDomains,
|
allPrimaryDomains,
|
||||||
@@ -93,7 +96,7 @@ extraction.edit = async (req, res) => {
|
|||||||
Extraction.fetchAllStopTermsFilesStats(extractionId)
|
Extraction.fetchAllStopTermsFilesStats(extractionId)
|
||||||
])
|
])
|
||||||
res.render('pages/extraction/edit-own', {
|
res.render('pages/extraction/edit-own', {
|
||||||
title: 'Besedila',
|
title: req.t('Besedila'),
|
||||||
id: extractionId,
|
id: extractionId,
|
||||||
extraction,
|
extraction,
|
||||||
extractionDocuments,
|
extractionDocuments,
|
||||||
@@ -117,6 +120,7 @@ extraction.docsEdit = async (req, res) => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
res.render('pages/extraction/docs-edit', {
|
res.render('pages/extraction/docs-edit', {
|
||||||
|
title: req.t('Besedila'),
|
||||||
id: extractionId,
|
id: extractionId,
|
||||||
extractionDocuments
|
extractionDocuments
|
||||||
})
|
})
|
||||||
@@ -129,6 +133,7 @@ extraction.stopTermsEdit = async (req, res) => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
res.render('pages/extraction/stop-terms-edit', {
|
res.render('pages/extraction/stop-terms-edit', {
|
||||||
|
title: req.t('Stop termini'),
|
||||||
id: extractionId,
|
id: extractionId,
|
||||||
stopTermsFiles
|
stopTermsFiles
|
||||||
})
|
})
|
||||||
@@ -145,6 +150,7 @@ extraction.listTermCandidates = async (req, res) => {
|
|||||||
const firstPageOfTermCandidates = termCandidates.slice(0, hitsPerPage)
|
const firstPageOfTermCandidates = termCandidates.slice(0, hitsPerPage)
|
||||||
|
|
||||||
res.render('pages/extraction/term-candidates', {
|
res.render('pages/extraction/term-candidates', {
|
||||||
|
title: req.t('Terminološki kandidati'),
|
||||||
extractionId,
|
extractionId,
|
||||||
termCandidatesJson,
|
termCandidatesJson,
|
||||||
firstPageOfTermCandidates,
|
firstPageOfTermCandidates,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
/* global __ */
|
||||||
|
|
||||||
const { getInstanceSetting } = require('../../models/helpers')
|
const { getInstanceSetting } = require('../../models/helpers')
|
||||||
const Dictionary = require('../../models/dictionary')
|
const Dictionary = require('../../models/dictionary')
|
||||||
const cache = require('../../models/cache')
|
const cache = require('../../models/cache')
|
||||||
@@ -56,6 +58,7 @@ exports.minEntriesRequirementCheckAndAct = {
|
|||||||
if (!isBelowMinEntriesThreshold || wasEmailAlreadySent) return
|
if (!isBelowMinEntriesThreshold || wasEmailAlreadySent) return
|
||||||
|
|
||||||
// Prepare and send notification emails.
|
// Prepare and send notification emails.
|
||||||
|
// TODO I18n - nameSl
|
||||||
const [nameSl, adminEmails, dictionariesAdminEmails] = await Promise.all([
|
const [nameSl, adminEmails, dictionariesAdminEmails] = await Promise.all([
|
||||||
Dictionary.fetchName(dictionaryId),
|
Dictionary.fetchName(dictionaryId),
|
||||||
Dictionary.fetchAdminEmails(dictionaryId),
|
Dictionary.fetchAdminEmails(dictionaryId),
|
||||||
@@ -68,14 +71,16 @@ exports.minEntriesRequirementCheckAndAct = {
|
|||||||
|
|
||||||
const type = 'delete'
|
const type = 'delete'
|
||||||
const renderAsync = promisify(appRef.render.bind(appRef))
|
const renderAsync = promisify(appRef.render.bind(appRef))
|
||||||
|
// TODO I18n - nameSl
|
||||||
const emailHtml = await renderAsync('email/dictionary-status-change', {
|
const emailHtml = await renderAsync('email/dictionary-status-change', {
|
||||||
type,
|
type,
|
||||||
nameSl
|
nameSl
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// TODO i18n - What language are the email title and content (we already have email translated)
|
||||||
await email.send({
|
await email.send({
|
||||||
to: allEmails,
|
to: allEmails,
|
||||||
subject: 'Obvestilo o številu gesel',
|
subject: __('Obvestilo o številu gesel'),
|
||||||
html: emailHtml
|
html: emailHtml
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -118,6 +123,7 @@ exports.determineNewStatus = async isPublished => {
|
|||||||
// Exports actions related to checking and acting on dictionary status changes.
|
// Exports actions related to checking and acting on dictionary status changes.
|
||||||
exports.statusChangeCheckAndAct = {
|
exports.statusChangeCheckAndAct = {
|
||||||
// Notify dictionaries admins by email on dictionary status changes.
|
// Notify dictionaries admins by email on dictionary status changes.
|
||||||
|
// TODO I18n - nameSl
|
||||||
async updateUsers(
|
async updateUsers(
|
||||||
dictionaryId,
|
dictionaryId,
|
||||||
isPublishedNew,
|
isPublishedNew,
|
||||||
@@ -133,7 +139,7 @@ exports.statusChangeCheckAndAct = {
|
|||||||
const dictionariesAdminEmails =
|
const dictionariesAdminEmails =
|
||||||
await Dictionary.fetchDictionariesAdminEmails()
|
await Dictionary.fetchDictionariesAdminEmails()
|
||||||
const type = 'unpublish'
|
const type = 'unpublish'
|
||||||
|
// TODO I18n - nameSl
|
||||||
await renderAndSendStatusChangeEmails(
|
await renderAndSendStatusChangeEmails(
|
||||||
appRef,
|
appRef,
|
||||||
type,
|
type,
|
||||||
@@ -151,6 +157,7 @@ exports.statusChangeCheckAndAct = {
|
|||||||
const type =
|
const type =
|
||||||
isApprovalRequired === 'T' ? 'publish-approval' : 'publish-no-approval'
|
isApprovalRequired === 'T' ? 'publish-approval' : 'publish-no-approval'
|
||||||
|
|
||||||
|
// TODO I18n - nameSl
|
||||||
await renderAndSendStatusChangeEmails(
|
await renderAndSendStatusChangeEmails(
|
||||||
appRef,
|
appRef,
|
||||||
type,
|
type,
|
||||||
@@ -171,6 +178,7 @@ exports.statusChangeCheckAndAct = {
|
|||||||
user
|
user
|
||||||
) {
|
) {
|
||||||
if (statusOld === 'reviewed' && statusNew !== 'reviewed') {
|
if (statusOld === 'reviewed' && statusNew !== 'reviewed') {
|
||||||
|
// TODO I18n - nameSl
|
||||||
const [nameSl, adminEmails] = await Promise.all([
|
const [nameSl, adminEmails] = await Promise.all([
|
||||||
Dictionary.fetchName(dictionaryId),
|
Dictionary.fetchName(dictionaryId),
|
||||||
Dictionary.fetchAdminEmails(dictionaryId)
|
Dictionary.fetchAdminEmails(dictionaryId)
|
||||||
@@ -189,6 +197,7 @@ exports.statusChangeCheckAndAct = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Helper function used by statusChangeCheckAndAct methods.
|
// Helper function used by statusChangeCheckAndAct methods.
|
||||||
|
// TODO I18n - nameSl
|
||||||
async function renderAndSendStatusChangeEmails(
|
async function renderAndSendStatusChangeEmails(
|
||||||
appRef,
|
appRef,
|
||||||
type,
|
type,
|
||||||
@@ -203,9 +212,10 @@ async function renderAndSendStatusChangeEmails(
|
|||||||
nameSl
|
nameSl
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// TODO i18n - What language are the email title and content (we already have email translated)
|
||||||
await email.send({
|
await email.send({
|
||||||
to: targetEmails,
|
to: targetEmails,
|
||||||
subject: 'Sprememba stanja slovarja',
|
subject: __('Sprememba stanja slovarja'),
|
||||||
html: emailHtml
|
html: emailHtml
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,12 +8,15 @@ helper.initialize = async () => {
|
|||||||
const initializers = {}
|
const initializers = {}
|
||||||
|
|
||||||
// TODO Once english language is implemented, gather selected language (sl/en) from request ~ (cookies?)
|
// TODO Once english language is implemented, gather selected language (sl/en) from request ~ (cookies?)
|
||||||
|
// TODO i18n name_sl
|
||||||
const language = 'name_sl'
|
const language = 'name_sl'
|
||||||
|
|
||||||
// TODO Consider parallelizing following queries. Single vs pooled clients?
|
// TODO Consider parallelizing following queries. Single vs pooled clients?
|
||||||
initializers.allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains()
|
initializers.allPrimaryDomains = await Dictionary.fetchAllPrimaryDomains()
|
||||||
initializers.sourceLanguages = await Dictionary.fetchAllLanguages(language)
|
initializers.sourceLanguages = await Dictionary.fetchAllLanguages(language)
|
||||||
initializers.targetLanguages = initializers.sourceLanguages.filter(
|
initializers.targetLanguages = (
|
||||||
|
await Dictionary.fetchAllLanguages(language)
|
||||||
|
).filter(
|
||||||
// drop slovene language
|
// drop slovene language
|
||||||
l => l.id !== 32
|
l => l.id !== 32
|
||||||
)
|
)
|
||||||
@@ -22,7 +25,7 @@ helper.initialize = async () => {
|
|||||||
|
|
||||||
initializers.portals = []
|
initializers.portals = []
|
||||||
initializers.portals.push({
|
initializers.portals.push({
|
||||||
name: await getInstanceSetting('portal_name'),
|
name: await getInstanceSetting('portal_name_sl'),
|
||||||
code: await getInstanceSetting('portal_code')
|
code: await getInstanceSetting('portal_code')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,16 @@ const Entry = require('../models/entry')
|
|||||||
const { getInstanceSetting } = require('../models/helpers')
|
const { getInstanceSetting } = require('../models/helpers')
|
||||||
const Dictionary = require('../models/dictionary')
|
const Dictionary = require('../models/dictionary')
|
||||||
const Comment = require('../models/comment')
|
const Comment = require('../models/comment')
|
||||||
const { searchEntryIndex } = require('../models/search-engine')
|
const {
|
||||||
|
searchEntryIndex,
|
||||||
|
searchConsultancyEntryIndex
|
||||||
|
} = require('../models/search-engine')
|
||||||
const { intoDbArray } = require('../models/helpers')
|
const { intoDbArray } = require('../models/helpers')
|
||||||
const {
|
const {
|
||||||
prepareEntries,
|
prepareEntries,
|
||||||
prepareAggregation,
|
prepareAggregation,
|
||||||
prepareSeachFilterData
|
prepareSeachFilterData,
|
||||||
|
prepareConsultancyEntries
|
||||||
} = require('../models/helpers/search')
|
} = require('../models/helpers/search')
|
||||||
const generateQuery = require('../models/helpers/search/generate-query')
|
const generateQuery = require('../models/helpers/search/generate-query')
|
||||||
const { DEFAULT_HITS_PER_PAGE } = require('../config/settings')
|
const { DEFAULT_HITS_PER_PAGE } = require('../config/settings')
|
||||||
@@ -16,6 +20,7 @@ const User = require('../models/user')
|
|||||||
|
|
||||||
// TODO Luka (note to self): Measure performance, consider caching.
|
// TODO Luka (note to self): Measure performance, consider caching.
|
||||||
exports.index = async (req, res) => {
|
exports.index = async (req, res) => {
|
||||||
|
const { language } = req
|
||||||
const {
|
const {
|
||||||
allPrimaryDomains,
|
allPrimaryDomains,
|
||||||
sourceLanguages,
|
sourceLanguages,
|
||||||
@@ -30,8 +35,10 @@ exports.index = async (req, res) => {
|
|||||||
englishLanguageEnabled
|
englishLanguageEnabled
|
||||||
)
|
)
|
||||||
|
|
||||||
const portalName = await getInstanceSetting('portal_name')
|
const portalName = await getInstanceSetting(`portal_name_${language}`)
|
||||||
const portalDescription = await getInstanceSetting('portal_description')
|
const portalDescription = await getInstanceSetting(
|
||||||
|
`portal_description_${language}`
|
||||||
|
)
|
||||||
|
|
||||||
const isRoot = true
|
const isRoot = true
|
||||||
|
|
||||||
@@ -53,7 +60,9 @@ exports.search = async (req, res) => {
|
|||||||
|
|
||||||
if (!searchString) return res.redirect('/')
|
if (!searchString) return res.redirect('/')
|
||||||
|
|
||||||
const {
|
const title = req.t('Iskanje')
|
||||||
|
|
||||||
|
let {
|
||||||
allPrimaryDomains,
|
allPrimaryDomains,
|
||||||
sourceLanguages,
|
sourceLanguages,
|
||||||
targetLanguages,
|
targetLanguages,
|
||||||
@@ -81,6 +90,32 @@ exports.search = async (req, res) => {
|
|||||||
true
|
true
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Consultancy
|
||||||
|
|
||||||
|
const hitsQueryConsultancy = generateQuery.consultancy(
|
||||||
|
searchString,
|
||||||
|
{
|
||||||
|
status: 'published',
|
||||||
|
primaryDomain: filters.primaryDomains[0]
|
||||||
|
},
|
||||||
|
hitsPerPage,
|
||||||
|
page
|
||||||
|
)
|
||||||
|
|
||||||
|
const hitsConsultancy = await searchConsultancyEntryIndex(
|
||||||
|
hitsQueryConsultancy
|
||||||
|
)
|
||||||
|
|
||||||
|
const consultancyHits = hitsConsultancy.body.hits.total.value
|
||||||
|
|
||||||
|
const consultancyURL = `/svetovanje/iskanje?q=${searchString}${
|
||||||
|
filters.primaryDomains.length > 0 ? '&pd=' + filters.primaryDomains[0] : '' // consultancy filtering only supports one domain
|
||||||
|
}`
|
||||||
|
|
||||||
|
// console.log(entries)
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
const [hits, aggregationRaw] = await Promise.all([
|
const [hits, aggregationRaw] = await Promise.all([
|
||||||
searchEntryIndex(hitsQuery),
|
searchEntryIndex(hitsQuery),
|
||||||
searchEntryIndex(aggregateQuery)
|
searchEntryIndex(aggregateQuery)
|
||||||
@@ -143,6 +178,30 @@ exports.search = async (req, res) => {
|
|||||||
sources: false
|
sources: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keep selected items on refresh
|
||||||
|
sourceLanguages = addSelectedIdentifierToFilters(
|
||||||
|
sourceLanguages,
|
||||||
|
filters.sourceLanguages
|
||||||
|
)
|
||||||
|
|
||||||
|
targetLanguages = addSelectedIdentifierToFilters(
|
||||||
|
targetLanguages,
|
||||||
|
filters.targetLanguages
|
||||||
|
)
|
||||||
|
allPrimaryDomains = addSelectedIdentifierToFilters(
|
||||||
|
allPrimaryDomains,
|
||||||
|
filters.primaryDomains
|
||||||
|
)
|
||||||
|
|
||||||
|
allDictionaryNames = addSelectedIdentifierToFilters(
|
||||||
|
allDictionaryNames,
|
||||||
|
filters.dictionaries
|
||||||
|
)
|
||||||
|
|
||||||
|
/// //////////////
|
||||||
|
|
||||||
|
portals = addSelectedIdentifierToPortals(portals, filters.sources)
|
||||||
|
|
||||||
if (count < 1) {
|
if (count < 1) {
|
||||||
return res.render('pages/search/no-results', {
|
return res.render('pages/search/no-results', {
|
||||||
allPrimaryDomains,
|
allPrimaryDomains,
|
||||||
@@ -154,6 +213,8 @@ exports.search = async (req, res) => {
|
|||||||
entriesByCategory,
|
entriesByCategory,
|
||||||
searchFilterData,
|
searchFilterData,
|
||||||
disabledSideMenuFilters,
|
disabledSideMenuFilters,
|
||||||
|
consultancyHits,
|
||||||
|
consultancyURL,
|
||||||
// allAggregation,
|
// allAggregation,
|
||||||
numberOfAllHits,
|
numberOfAllHits,
|
||||||
numberOfAllPages,
|
numberOfAllPages,
|
||||||
@@ -161,6 +222,74 @@ exports.search = async (req, res) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// duplicated code below for filtering foreignentries, optimize later
|
||||||
|
// Below code filters target languages based on source and target filters for each category
|
||||||
|
const categoriesLabels = Object.keys(entriesByCategory)
|
||||||
|
for (
|
||||||
|
let categoryIndex = 0;
|
||||||
|
categoryIndex < categoriesLabels.length;
|
||||||
|
categoryIndex++
|
||||||
|
) {
|
||||||
|
entriesByCategory[categoriesLabels[categoryIndex]] = entriesByCategory[
|
||||||
|
categoriesLabels[categoryIndex]
|
||||||
|
].map(entry => {
|
||||||
|
entry.foreignEntries = entry.foreignEntries?.filter(foreignEntry => {
|
||||||
|
if (filters.targetLanguages.length > 0) {
|
||||||
|
if (aggregation.sourceLanguages.length === 1) {
|
||||||
|
filters.sourceLanguages = [aggregation.sourceLanguages[0].id]
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
filters.targetLanguages.includes(`${foreignEntry.lang.id}`) ||
|
||||||
|
filters.sourceLanguages?.includes(`${foreignEntry.lang.id}`)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return entry
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable target languages logic
|
||||||
|
|
||||||
|
if (
|
||||||
|
filters.sourceLanguages.length > 1 || // more or equal than 2 source languages
|
||||||
|
(filters.sourceLanguages.length < 1 && // No filters, but more than 1 source language filters returned
|
||||||
|
aggregation.sourceLanguages.length > 1) ||
|
||||||
|
(aggregation.targetLanguages.length === 1 &&
|
||||||
|
aggregation.sourceLanguages.length === 1 &&
|
||||||
|
aggregation.targetLanguages[0].id === aggregation.sourceLanguages[0].id) //
|
||||||
|
) {
|
||||||
|
// predicate 1 Disable if 2 or more filters in Source languages are selected
|
||||||
|
// predicate 2 If none are selected, then check if displayed filters are more than 2
|
||||||
|
aggregation.targetLanguages = []
|
||||||
|
filters.targetLanguages = []
|
||||||
|
targetLanguages = []
|
||||||
|
searchFilterData.targetLanguages = []
|
||||||
|
disabledSideMenuFilters.targetLanguages = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove source language from target language
|
||||||
|
filters.sourceLanguages.forEach(entry => {
|
||||||
|
if (
|
||||||
|
aggregation.targetLanguages.filter(toFilter => toFilter.id === entry)
|
||||||
|
.length > 0
|
||||||
|
) {
|
||||||
|
aggregation.targetLanguages = aggregation.targetLanguages.filter(
|
||||||
|
toFilter => toFilter.id !== entry
|
||||||
|
)
|
||||||
|
searchFilterData.targetLanguages =
|
||||||
|
searchFilterData.targetLanguages.filter(
|
||||||
|
toFilter => toFilter.id !== entry
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (aggregation.targetLanguages.length < 0) {
|
||||||
|
searchFilterData.targetLanguages = []
|
||||||
|
disabledSideMenuFilters.targetLanguages = true
|
||||||
|
}
|
||||||
|
|
||||||
// TODO Add a page title?
|
// TODO Add a page title?
|
||||||
res.render('pages/search/results', {
|
res.render('pages/search/results', {
|
||||||
allPrimaryDomains,
|
allPrimaryDomains,
|
||||||
@@ -172,15 +301,19 @@ exports.search = async (req, res) => {
|
|||||||
entriesByCategory,
|
entriesByCategory,
|
||||||
searchFilterData,
|
searchFilterData,
|
||||||
disabledSideMenuFilters,
|
disabledSideMenuFilters,
|
||||||
|
consultancyHits,
|
||||||
|
consultancyURL,
|
||||||
// allAggregation,
|
// allAggregation,
|
||||||
numberOfAllHits,
|
numberOfAllHits,
|
||||||
numberOfAllPages,
|
numberOfAllPages,
|
||||||
page
|
page,
|
||||||
|
title
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.entryDetails = async (req, res) => {
|
exports.entryDetails = async (req, res) => {
|
||||||
const termId = req.params.entryId
|
const termId = req.params.entryId
|
||||||
|
const title = req.t('Termin')
|
||||||
|
|
||||||
/* const [entry, domainLabels] = await Promise.all([
|
/* const [entry, domainLabels] = await Promise.all([
|
||||||
Entry.fetchFullWithOrderedForeignLanguages(termId),
|
Entry.fetchFullWithOrderedForeignLanguages(termId),
|
||||||
@@ -188,12 +321,16 @@ exports.entryDetails = async (req, res) => {
|
|||||||
]) */
|
]) */
|
||||||
|
|
||||||
const entry = await Entry.fetchFullWithOrderedForeignLanguages(termId)
|
const entry = await Entry.fetchFullWithOrderedForeignLanguages(termId)
|
||||||
|
|
||||||
/* const entryData = {
|
/* const entryData = {
|
||||||
entry
|
entry
|
||||||
// allDomainLabelsJoined: domainLabels.map(e => e.name).join(', ')
|
// allDomainLabelsJoined: domainLabels.map(e => e.name).join(', ')
|
||||||
} */
|
} */
|
||||||
|
|
||||||
|
// If external url, just redirect
|
||||||
|
if (entry.external_url) {
|
||||||
|
return res.redirect(entry.external_url)
|
||||||
|
}
|
||||||
|
|
||||||
// unnecessary legacy assigment, refactor when time is available
|
// unnecessary legacy assigment, refactor when time is available
|
||||||
const entryData = entry
|
const entryData = entry
|
||||||
|
|
||||||
@@ -228,8 +365,8 @@ exports.entryDetails = async (req, res) => {
|
|||||||
///
|
///
|
||||||
|
|
||||||
// check if it is a local dictionary
|
// check if it is a local dictionary
|
||||||
if (!dictionaryData.portalname && !dictionaryData.portalcode) {
|
if (!dictionaryData.portalnamesl && !dictionaryData.portalcode) {
|
||||||
dictionaryData[0].portalname = await getInstanceSetting('portal_name')
|
dictionaryData[0].portalname = await getInstanceSetting('portal_name_sl')
|
||||||
dictionaryData[0].portalcode = await getInstanceSetting('portal_code')
|
dictionaryData[0].portalcode = await getInstanceSetting('portal_code')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,7 +395,7 @@ exports.entryDetails = async (req, res) => {
|
|||||||
// struct data contains important data and re-maps for unification (maybe refactor later)
|
// struct data contains important data and re-maps for unification (maybe refactor later)
|
||||||
const structData = {
|
const structData = {
|
||||||
termId: termId,
|
termId: termId,
|
||||||
prevWindowTitle: 'Iskanje',
|
prevWindowTitle: req.t('Iskanje'),
|
||||||
prevHref: '/iskanje',
|
prevHref: '/iskanje',
|
||||||
portalCode: dictionaryData[0].portalcode,
|
portalCode: dictionaryData[0].portalcode,
|
||||||
portalName: dictionaryData[0].portalname,
|
portalName: dictionaryData[0].portalname,
|
||||||
@@ -270,13 +407,14 @@ exports.entryDetails = async (req, res) => {
|
|||||||
languages: reducedData.languages ? reducedData.languages.join(', ') : ''
|
languages: reducedData.languages ? reducedData.languages.join(', ') : ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO I18n
|
||||||
if (reducedData.author) {
|
if (reducedData.author) {
|
||||||
if (reducedData.author.length > 2) {
|
if (reducedData.author.length > 2) {
|
||||||
structData.authorLabel = 'Avtorji'
|
structData.authorLabel = req.t('Avtorji')
|
||||||
} else if (reducedData.author.length === 2) {
|
} else if (reducedData.author.length === 2) {
|
||||||
structData.authorLabel = 'Avtorja'
|
structData.authorLabel = req.t('Avtorja')
|
||||||
} else if (reducedData.author.length === 1) {
|
} else if (reducedData.author.length === 1) {
|
||||||
structData.authorLabel = 'Avtor'
|
structData.authorLabel = req.t('Avtor')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,27 +459,63 @@ exports.entryDetails = async (req, res) => {
|
|||||||
selectedDomainLabelsForEntryString,
|
selectedDomainLabelsForEntryString,
|
||||||
numberOfAllPages,
|
numberOfAllPages,
|
||||||
comments,
|
comments,
|
||||||
commentCount
|
commentCount,
|
||||||
|
title
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.myProfile = async (req, res) => {
|
exports.myProfile = async (req, res) => {
|
||||||
res.render('pages/profile/my-profile', { title: 'Moj račun' })
|
res.render('pages/profile/my-profile', { title: req.t('Osnovni podatki') })
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.deleteProfile = async (req, res) => {
|
||||||
|
res.render('pages/profile/delete-profile', { title: req.t('Izbriši račun') })
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.changePassword = async (req, res) => {
|
exports.changePassword = async (req, res) => {
|
||||||
res.render('pages/profile/change-password', { title: 'Spremeni geslo' })
|
res.render('pages/profile/change-password', {
|
||||||
|
title: req.t('Spremeni geslo')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.resetPassword = async (req, res) => {
|
||||||
|
const { token } = req.query
|
||||||
|
|
||||||
|
// console.log(token)
|
||||||
|
res.render('pages/reset-password/reset-password', {
|
||||||
|
// title: 'Pozabljeno geslo'
|
||||||
|
isValidToken: token === '123',
|
||||||
|
token
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.userSettings = async (req, res) => {
|
exports.userSettings = async (req, res) => {
|
||||||
const hitsPerPageArr = await User.fetchAllowedHitsPerPage()
|
const hitsPerPageArr = await User.fetchAllowedHitsPerPage()
|
||||||
res.render('pages/profile/change-profile-settings', {
|
res.render('pages/profile/change-profile-settings', {
|
||||||
title: 'Nastavitve računa',
|
title: req.t('Nastavitve računa'),
|
||||||
hitsPerPageArr,
|
hitsPerPageArr,
|
||||||
hitsForUser: req.user?.hitsPerPage
|
hitsForUser: req.user?.hitsPerPage
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.changeUserLanguage = async (req, res) => {
|
||||||
|
const { languageCode } = req.params
|
||||||
|
const validCodes = ['sl', 'en']
|
||||||
|
|
||||||
|
if (!validCodes.includes(languageCode)) {
|
||||||
|
throw Error(`Invalid language: ${languageCode}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { user } = req
|
||||||
|
if (user) {
|
||||||
|
await User.updateLanguage(user.id, languageCode)
|
||||||
|
} else {
|
||||||
|
req.session.language = languageCode
|
||||||
|
}
|
||||||
|
|
||||||
|
res.redirect('/')
|
||||||
|
}
|
||||||
|
|
||||||
function mergeDomains(
|
function mergeDomains(
|
||||||
domainList,
|
domainList,
|
||||||
aggregationFn = (acc, n) => {
|
aggregationFn = (acc, n) => {
|
||||||
@@ -384,3 +558,23 @@ function filterResults(entries) {
|
|||||||
|
|
||||||
return [termLst, ftermLst, otherLst]
|
return [termLst, ftermLst, otherLst]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addSelectedIdentifierToFilters(source, selectedIDs) {
|
||||||
|
return source.map(entry => {
|
||||||
|
if (selectedIDs.includes(`${entry.id}`)) {
|
||||||
|
entry.selected = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function addSelectedIdentifierToPortals(source, selectedIDs) {
|
||||||
|
return source.map(entry => {
|
||||||
|
if (selectedIDs.includes(`${entry.code}`)) {
|
||||||
|
entry.selected = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ portal.instanceSettings = async (req, res) => {
|
|||||||
const portal = await Portal.fetchInstanceSettings()
|
const portal = await Portal.fetchInstanceSettings()
|
||||||
|
|
||||||
res.render('pages/admin/portal', {
|
res.render('pages/admin/portal', {
|
||||||
title: 'Nastavitve portala',
|
title: req.t('Nastavitve portala'),
|
||||||
portal
|
portal
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -24,7 +24,7 @@ portal.instanceDictSettings = async (req, res) => {
|
|||||||
const dictionary = await Portal.fetchInstanceDictSettings()
|
const dictionary = await Portal.fetchInstanceDictSettings()
|
||||||
|
|
||||||
res.render('pages/admin/settings-dictionaries', {
|
res.render('pages/admin/settings-dictionaries', {
|
||||||
title: 'Nastavitve slovarjev',
|
title: req.t('Nastavitve slovarjev'),
|
||||||
dictionary
|
dictionary
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -39,7 +39,7 @@ portal.updateInstanceDictSettings = async (req, res) => {
|
|||||||
portal.instanceConsultancySettings = async (req, res) => {
|
portal.instanceConsultancySettings = async (req, res) => {
|
||||||
const consultancy = await Portal.fetchInstanceConsultancySettings()
|
const consultancy = await Portal.fetchInstanceConsultancySettings()
|
||||||
res.render('pages/admin/portal-consultancy-settings', {
|
res.render('pages/admin/portal-consultancy-settings', {
|
||||||
title: 'Nastavitve svetovalnice',
|
title: req.t('Nastavitve svetovalnice'),
|
||||||
consultancy
|
consultancy
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -53,7 +53,7 @@ portal.updateInstanceConusltacySettings = async (req, res) => {
|
|||||||
|
|
||||||
portal.new = async (req, res) => {
|
portal.new = async (req, res) => {
|
||||||
res.render('pages/admin/new-connection', {
|
res.render('pages/admin/new-connection', {
|
||||||
title: 'Nova povezava'
|
title: req.t('Nova povezava')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ portal.list = async (req, res) => {
|
|||||||
const allLinkedPortals = await Portal.fetchAll()
|
const allLinkedPortals = await Portal.fetchAll()
|
||||||
|
|
||||||
res.render('pages/admin/connections-list', {
|
res.render('pages/admin/connections-list', {
|
||||||
title: 'Seznam povezav',
|
title: req.t('Seznam povezav'),
|
||||||
allLinkedPortals
|
allLinkedPortals
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -70,7 +70,10 @@ portal.fetchPortal = async (req, res) => {
|
|||||||
const portalId = req.params.portalId
|
const portalId = req.params.portalId
|
||||||
const portal = await Portal.fetchPortal(portalId)
|
const portal = await Portal.fetchPortal(portalId)
|
||||||
|
|
||||||
res.render('pages/admin/portal-edit', { title: 'Uredi povezavo', portal })
|
res.render('pages/admin/portal-edit', {
|
||||||
|
title: req.t('Uredi povezavo'),
|
||||||
|
portal
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
portal.updatePortal = async (req, res) => {
|
portal.updatePortal = async (req, res) => {
|
||||||
@@ -88,7 +91,7 @@ portal.fetchSelectedLinkedDictionaries = async (req, res) => {
|
|||||||
await Portal.fetchSelectedLinkedDictionaries(linkedId, resultsPerPage, 1)
|
await Portal.fetchSelectedLinkedDictionaries(linkedId, resultsPerPage, 1)
|
||||||
|
|
||||||
res.render('pages/admin/portal-list-dict', {
|
res.render('pages/admin/portal-list-dict', {
|
||||||
title: 'Slovarji portala',
|
title: req.t('Slovarji portala'),
|
||||||
linkedId,
|
linkedId,
|
||||||
numberOfAllPages,
|
numberOfAllPages,
|
||||||
results
|
results
|
||||||
@@ -108,7 +111,7 @@ portal.fetchAllLinkedDictionaries = async (req, res) => {
|
|||||||
await Portal.fetchAllLinkedDictionaries(resultsPerPage, 1)
|
await Portal.fetchAllLinkedDictionaries(resultsPerPage, 1)
|
||||||
|
|
||||||
res.render('pages/admin/portals-all-linked-dictionaries', {
|
res.render('pages/admin/portals-all-linked-dictionaries', {
|
||||||
title: 'Povezani',
|
title: req.t('Povezani slovarji'),
|
||||||
numberOfAllPages,
|
numberOfAllPages,
|
||||||
results
|
results
|
||||||
})
|
})
|
||||||
@@ -130,7 +133,7 @@ portal.comments = async (req, res) => {
|
|||||||
1
|
1
|
||||||
)
|
)
|
||||||
res.render('pages/admin/comments', {
|
res.render('pages/admin/comments', {
|
||||||
title: 'Komentarji',
|
title: req.t('Komentarji'),
|
||||||
numberOfAllPages,
|
numberOfAllPages,
|
||||||
dictionary: { id: req.params.dictionaryId },
|
dictionary: { id: req.params.dictionaryId },
|
||||||
comments
|
comments
|
||||||
|
|||||||
@@ -12,7 +12,10 @@ const user = {}
|
|||||||
|
|
||||||
user.register = async (req, res) => {
|
user.register = async (req, res) => {
|
||||||
// TODO Add validation.
|
// TODO Add validation.
|
||||||
const userId = await User.create(req.body)
|
const userId = await User.create({
|
||||||
|
...req.body,
|
||||||
|
language: req.session.language
|
||||||
|
})
|
||||||
const activationToken = (await RandomBytesAsync(32)).toString('hex')
|
const activationToken = (await RandomBytesAsync(32)).toString('hex')
|
||||||
await User.saveActivationToken(userId, activationToken)
|
await User.saveActivationToken(userId, activationToken)
|
||||||
const { email: userEmail, username } = req.body
|
const { email: userEmail, username } = req.body
|
||||||
@@ -20,16 +23,16 @@ user.register = async (req, res) => {
|
|||||||
activationLink.searchParams.set('token', activationToken)
|
activationLink.searchParams.set('token', activationToken)
|
||||||
activationLink = activationLink.href
|
activationLink = activationLink.href
|
||||||
const renderAsync = promisify(req.app.render.bind(req.app))
|
const renderAsync = promisify(req.app.render.bind(req.app))
|
||||||
const emailHtml = await renderAsync('email/user-activation', {
|
const emailHtml = await renderAsync(`email/user-activation_${req.language}`, {
|
||||||
username,
|
username,
|
||||||
activationLink
|
activationLink
|
||||||
})
|
})
|
||||||
await email.send({
|
await email.send({
|
||||||
to: userEmail,
|
to: userEmail,
|
||||||
subject: 'Aktivacija računa',
|
subject: req.t('Aktivacija računa'),
|
||||||
html: emailHtml
|
html: emailHtml
|
||||||
})
|
})
|
||||||
res.send('Registracija uspešna')
|
res.send(req.t('Registracija uspešna'))
|
||||||
}
|
}
|
||||||
|
|
||||||
user.activateAccount = async (req, res) => {
|
user.activateAccount = async (req, res) => {
|
||||||
@@ -39,6 +42,12 @@ user.activateAccount = async (req, res) => {
|
|||||||
await User.activateAccount(user)
|
await User.activateAccount(user)
|
||||||
const loginAsync = promisify(req.login.bind(req))
|
const loginAsync = promisify(req.login.bind(req))
|
||||||
await loginAsync(user)
|
await loginAsync(user)
|
||||||
|
|
||||||
|
if (req.session.language) {
|
||||||
|
await User.updateLanguage(user.id, req.session.language)
|
||||||
|
delete req.session.language
|
||||||
|
}
|
||||||
|
|
||||||
res.redirect('/')
|
res.redirect('/')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,8 +55,9 @@ user.login = async (req, res, next) => {
|
|||||||
passport.authenticate(
|
passport.authenticate(
|
||||||
'local',
|
'local',
|
||||||
{
|
{
|
||||||
badRequestMessage:
|
badRequestMessage: req.t(
|
||||||
'Nepravilno uporabniško ime, elektronski naslov ali geslo.'
|
'Nepravilno uporabniško ime, elektronski naslov ali geslo.'
|
||||||
|
)
|
||||||
},
|
},
|
||||||
async (err, user, info) => {
|
async (err, user, info) => {
|
||||||
if (err) return next(err)
|
if (err) return next(err)
|
||||||
@@ -69,7 +79,12 @@ user.login = async (req, res, next) => {
|
|||||||
res.cookie('remember_me', rememberMeToken, rememberMeCookieSettings)
|
res.cookie('remember_me', rememberMeToken, rememberMeCookieSettings)
|
||||||
}
|
}
|
||||||
|
|
||||||
res.send('Prijava uspešna')
|
if (req.session.language) {
|
||||||
|
await User.updateLanguage(user.id, req.session.language)
|
||||||
|
delete req.session.language
|
||||||
|
}
|
||||||
|
|
||||||
|
res.send(req.t('Prijava uspešna'))
|
||||||
}
|
}
|
||||||
)(req, res, next)
|
)(req, res, next)
|
||||||
}
|
}
|
||||||
@@ -82,6 +97,8 @@ user.logout = async (req, res) => {
|
|||||||
await User.clearRememberMeToken(rememberMeToken)
|
await User.clearRememberMeToken(rememberMeToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
req.session.language = req.user.language
|
||||||
|
|
||||||
req.logout()
|
req.logout()
|
||||||
// Manually clear session.passport due to bug in current passport version.
|
// Manually clear session.passport due to bug in current passport version.
|
||||||
delete req.session.passport.user
|
delete req.session.passport.user
|
||||||
@@ -96,7 +113,7 @@ user.list = async (req, res) => {
|
|||||||
1
|
1
|
||||||
)
|
)
|
||||||
res.render('pages/admin/user-list', {
|
res.render('pages/admin/user-list', {
|
||||||
title: 'Seznam slovarjeva',
|
title: req.t('Seznam uporabnikov'),
|
||||||
numberOfAllPages,
|
numberOfAllPages,
|
||||||
results
|
results
|
||||||
})
|
})
|
||||||
@@ -104,7 +121,10 @@ user.list = async (req, res) => {
|
|||||||
|
|
||||||
user.listAllWithPortalRoles = async (req, res) => {
|
user.listAllWithPortalRoles = async (req, res) => {
|
||||||
const users = await User.fetchAllWithPortalRoles()
|
const users = await User.fetchAllWithPortalRoles()
|
||||||
res.render('pages/admin/user-portals', { title: 'Seznam slovarjev', users })
|
res.render('pages/admin/user-portals', {
|
||||||
|
title: req.t('Skrbniki portala'),
|
||||||
|
users
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
user.findByUsernameOrEmail = async (req, res) => {
|
user.findByUsernameOrEmail = async (req, res) => {
|
||||||
@@ -125,7 +145,7 @@ user.adminEdit = async (req, res) => {
|
|||||||
User.fetchUserRoles(userId)
|
User.fetchUserRoles(userId)
|
||||||
])
|
])
|
||||||
res.render('pages/admin/user-edit', {
|
res.render('pages/admin/user-edit', {
|
||||||
title: 'Urejanje uporabnikov',
|
title: req.t('Uporabnik'),
|
||||||
userData,
|
userData,
|
||||||
userRoles
|
userRoles
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ passport.deserializeUser(async (id, done) => {
|
|||||||
|
|
||||||
passport.use(
|
passport.use(
|
||||||
new LocalStrategy(
|
new LocalStrategy(
|
||||||
{ usernameField: 'usernameOrEmail' },
|
{ passReqToCallback: true, usernameField: 'usernameOrEmail' },
|
||||||
async (usernameOrEmail, password, done) => {
|
async (req, usernameOrEmail, password, done) => {
|
||||||
try {
|
try {
|
||||||
const { rows } = await db.query(
|
const { rows } = await db.query(
|
||||||
'SELECT id, status, bcrypt_hash FROM "user" WHERE username = $1 OR email = $1',
|
'SELECT id, status, bcrypt_hash FROM "user" WHERE username = $1 OR email = $1',
|
||||||
@@ -40,14 +40,17 @@ passport.use(
|
|||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return done(null, false, {
|
return done(null, false, {
|
||||||
message: 'Nepravilno uporabniško ime, elektronski naslov ali geslo.'
|
message: req.t(
|
||||||
|
'Nepravilno uporabniško ime, elektronski naslov ali geslo.'
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.status !== 'active') {
|
if (user.status !== 'active') {
|
||||||
return done(null, false, {
|
return done(null, false, {
|
||||||
message:
|
message: req.t(
|
||||||
'Uporabniški račun še ni aktiviran. Kliknite aktivacijsko povezavo, katero smo vam poslali po elektronski pošti.'
|
'Uporabniški račun še ni aktiviran. Kliknite aktivacijsko povezavo, katero smo vam poslali po elektronski pošti.'
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,7 +60,9 @@ passport.use(
|
|||||||
)
|
)
|
||||||
if (!isCorrectPassword) {
|
if (!isCorrectPassword) {
|
||||||
return done(null, false, {
|
return done(null, false, {
|
||||||
message: 'Nepravilno uporabniško ime, elektronski naslov ali geslo.'
|
message: req.t(
|
||||||
|
'Nepravilno uporabniško ime, elektronski naslov ali geslo.'
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
const i18next = require('i18next')
|
||||||
|
const i18nextMiddleware = require('i18next-http-middleware')
|
||||||
|
const i18nextBackend = require('i18next-fs-backend')
|
||||||
|
|
||||||
|
// If you change this one, don't forget to also update the nodemon ignore flag in package.json scripts.
|
||||||
|
const LOCALES_PATH = 'public/locales'
|
||||||
|
const DEFAULT_LANGUAGE = 'sl'
|
||||||
|
|
||||||
|
exports.determineRequestLanguage = (req, res, next) => {
|
||||||
|
const language =
|
||||||
|
req.user?.language || req.session.language || DEFAULT_LANGUAGE
|
||||||
|
|
||||||
|
req.determinedLanguage = language
|
||||||
|
res.locals.determinedLanguage = language
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
||||||
|
const customLanguageDetector = {
|
||||||
|
name: 'ownDetector',
|
||||||
|
lookup(req) {
|
||||||
|
return req.determinedLanguage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const languageDetector = new i18nextMiddleware.LanguageDetector()
|
||||||
|
languageDetector.addDetector(customLanguageDetector)
|
||||||
|
|
||||||
|
const inDevEnv = process.env.NODE_ENV === 'development'
|
||||||
|
i18next
|
||||||
|
.use(languageDetector)
|
||||||
|
.use(i18nextBackend)
|
||||||
|
.init({
|
||||||
|
// debug: true,
|
||||||
|
supportedLngs: ['sl', 'en', 'dev'],
|
||||||
|
preload: ['sl', 'en'],
|
||||||
|
ns: ['core', 'extended'],
|
||||||
|
defaultNS: 'core',
|
||||||
|
// TODO Reenable separators once keys are refactored (also in frontend init in scripts.js)
|
||||||
|
nsSeparator: false,
|
||||||
|
keySeparator: false,
|
||||||
|
...(inDevEnv && { saveMissing: true }),
|
||||||
|
...(!inDevEnv && { fallbackLng: false }),
|
||||||
|
detection: { order: ['ownDetector'] },
|
||||||
|
backend: {
|
||||||
|
loadPath: `${LOCALES_PATH}/{{lng}}/{{ns}}.json`,
|
||||||
|
addPath: `${LOCALES_PATH}/{{lng}}/{{ns}}.json`
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -2,5 +2,6 @@ const { getInstanceSetting } = require('../models/helpers')
|
|||||||
|
|
||||||
exports.enhanceLocals = async (req, res, next) => {
|
exports.enhanceLocals = async (req, res, next) => {
|
||||||
res.locals.portalCode = await getInstanceSetting('portal_code')
|
res.locals.portalCode = await getInstanceSetting('portal_code')
|
||||||
|
|
||||||
next()
|
next()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,13 @@ user.enhance = (req, res, next) => {
|
|||||||
next()
|
next()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
user.isAuthenticated = (req, res, next) => {
|
||||||
|
if (req.isAuthenticated()) return next()
|
||||||
|
|
||||||
|
if (req.isAjax) return res.status(400).end()
|
||||||
|
res.redirect(req.baseUrl || '/')
|
||||||
|
}
|
||||||
|
|
||||||
user.isDictionaryAdmin = (req, res, next) => {
|
user.isDictionaryAdmin = (req, res, next) => {
|
||||||
const { dictionaryId } = req.params
|
const { dictionaryId } = req.params
|
||||||
const isAdmin = req.user.hasDictionaryRole(dictionaryId, 'administration')
|
const isAdmin = req.user.hasDictionaryRole(dictionaryId, 'administration')
|
||||||
@@ -21,7 +28,7 @@ user.isDictionaryAdmin = (req, res, next) => {
|
|||||||
if (isAdmin) return next()
|
if (isAdmin) return next()
|
||||||
|
|
||||||
if (req.isAjax) return res.status(400).end()
|
if (req.isAjax) return res.status(400).end()
|
||||||
res.redirect(req.baseUrl)
|
res.redirect(req.baseUrl || '/')
|
||||||
}
|
}
|
||||||
|
|
||||||
user.isDictionaryEditor = (req, res, next) => {
|
user.isDictionaryEditor = (req, res, next) => {
|
||||||
@@ -31,7 +38,18 @@ user.isDictionaryEditor = (req, res, next) => {
|
|||||||
if (isEditor) return next()
|
if (isEditor) return next()
|
||||||
|
|
||||||
if (req.isAjax) return res.status(400).end()
|
if (req.isAjax) return res.status(400).end()
|
||||||
res.redirect(req.baseUrl)
|
res.redirect(req.baseUrl || '/')
|
||||||
|
}
|
||||||
|
|
||||||
|
user.canContentEdit = (req, res, next) => {
|
||||||
|
const { dictionaryId } = req.params
|
||||||
|
const isEditor = req.user.hasAnyDictionaryRole(dictionaryId)
|
||||||
|
const isPortalAdmin = req.user.hasRole('portal admin')
|
||||||
|
const isDictionariesAdmin = req.user.hasRole('dictionaries admin')
|
||||||
|
if (isEditor || isPortalAdmin || isDictionariesAdmin) return next()
|
||||||
|
|
||||||
|
if (req.isAjax) return res.status(400).end()
|
||||||
|
res.redirect(req.baseUrl || '/')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
const db = require('./db')
|
const db = require('./db')
|
||||||
const debug = require('debug')('termPortal:models/comment')
|
const debug = require('debug')('termPortal:models/comment')
|
||||||
const User = require('../models/user')
|
const User = require('../models/user')
|
||||||
|
const {
|
||||||
|
portalAdminInitialEmail,
|
||||||
|
portalAdminInitialPassword
|
||||||
|
} = require('../config/keys')
|
||||||
|
|
||||||
class Comment {
|
class Comment {
|
||||||
// Deserialize flat data into an organized comment object.
|
// Deserialize flat data into an organized comment object.
|
||||||
@@ -311,24 +315,21 @@ async function seedMockUsersInDb() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function seedPortalAdmin() {
|
async function seedPortalAdmin() {
|
||||||
const MOCK_ADMIN_BASE = 'admin'
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
rows: [mockAdmin]
|
rows: [{ exists }]
|
||||||
} = await db.query('SELECT id FROM "user" WHERE username = $1', [
|
} = await db.query(
|
||||||
MOCK_ADMIN_BASE
|
"SELECT EXISTS (SELECT 1 FROM user_role WHERE role_name = 'portal admin')"
|
||||||
])
|
)
|
||||||
|
|
||||||
if (mockAdmin) {
|
if (exists) return 'Skipping creation of portal admin (already exists)'
|
||||||
return `Portal admin already exists (username: ${MOCK_ADMIN_BASE}, password: ${MOCK_ADMIN_BASE})`
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const MOCK_ADMIN_BASE = 'admin'
|
||||||
const adminUser = {
|
const adminUser = {
|
||||||
username: MOCK_ADMIN_BASE,
|
username: MOCK_ADMIN_BASE,
|
||||||
firstName: MOCK_ADMIN_BASE,
|
firstName: MOCK_ADMIN_BASE,
|
||||||
lastName: MOCK_ADMIN_BASE,
|
lastName: MOCK_ADMIN_BASE,
|
||||||
password: MOCK_ADMIN_BASE,
|
password: portalAdminInitialPassword,
|
||||||
email: `${MOCK_ADMIN_BASE}@rsdo.com`
|
email: portalAdminInitialEmail
|
||||||
}
|
}
|
||||||
const userId = await User.create(adminUser)
|
const userId = await User.create(adminUser)
|
||||||
const assignAdminRole = db.query(
|
const assignAdminRole = db.query(
|
||||||
@@ -345,7 +346,7 @@ async function seedPortalAdmin() {
|
|||||||
[adminUser.username]
|
[adminUser.username]
|
||||||
)
|
)
|
||||||
await Promise.all([assignAdminRole, activateAdminUser])
|
await Promise.all([assignAdminRole, activateAdminUser])
|
||||||
return `Successfully seeded portal admin (username: ${MOCK_ADMIN_BASE}, password: ${MOCK_ADMIN_BASE})`
|
return `Successfully created portal admin (username: ${adminUser.username}, password: ${adminUser.password})`
|
||||||
}
|
}
|
||||||
|
|
||||||
async function seedConsultants() {
|
async function seedConsultants() {
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ class ConsultancyEntry {
|
|||||||
|
|
||||||
// Fetch consultancy entry by ID
|
// Fetch consultancy entry by ID
|
||||||
// to_char(time_created,'HH24:MI:SS DD/MM/YYYY')
|
// to_char(time_created,'HH24:MI:SS DD/MM/YYYY')
|
||||||
|
// TODO i18n date format
|
||||||
static async fetchByIdWithFormattedTime(id) {
|
static async fetchByIdWithFormattedTime(id) {
|
||||||
const { rows: fetchedConsEntry } = await db.query(
|
const { rows: fetchedConsEntry } = await db.query(
|
||||||
`
|
`
|
||||||
@@ -679,6 +680,7 @@ class ConsultancyEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// (Re)index specific consultancy entry into consultancy search index.
|
// (Re)index specific consultancy entry into consultancy search index.
|
||||||
|
// TODO i18n name_sl
|
||||||
static async indexIntoSearchEngine(entryId, shouldWait) {
|
static async indexIntoSearchEngine(entryId, shouldWait) {
|
||||||
const values = [entryId]
|
const values = [entryId]
|
||||||
const text = `
|
const text = `
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
|
const { mkdir, open, unlink } = require('fs/promises')
|
||||||
const Cursor = require('pg-cursor')
|
const Cursor = require('pg-cursor')
|
||||||
const db = require('./db')
|
const db = require('./db')
|
||||||
const { intoDbArray, getInstanceSetting } = require('./helpers')
|
const { intoDbArray, getInstanceSetting } = require('./helpers')
|
||||||
const { deserialize, bulkIndex } = require('./helpers/dictionary')
|
const {
|
||||||
|
deserialize,
|
||||||
|
bulkIndex,
|
||||||
|
getExportFilesPath
|
||||||
|
} = require('./helpers/dictionary')
|
||||||
const { readFileIntoDb } = require('./helpers/dictionary/import-file')
|
const { readFileIntoDb } = require('./helpers/dictionary/import-file')
|
||||||
|
const { transformAndAppend } = require('./helpers/dictionary/export-file')
|
||||||
|
const { origin } = require('../config/keys')
|
||||||
// const debug = require('debug')('termPortal:models/dictionary')
|
// const debug = require('debug')('termPortal:models/dictionary')
|
||||||
|
|
||||||
class Dictionary {
|
class Dictionary {
|
||||||
@@ -25,6 +32,7 @@ class Dictionary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fetch all dictionaries from DB.
|
// Fetch all dictionaries from DB.
|
||||||
|
// TODO i18n name_sl
|
||||||
static async fetchAll() {
|
static async fetchAll() {
|
||||||
// TODO Implement SQL stored procedures or functions.
|
// TODO Implement SQL stored procedures or functions.
|
||||||
const { rows: fetchedDictionaries } = await db.query(`
|
const { rows: fetchedDictionaries } = await db.query(`
|
||||||
@@ -65,6 +73,7 @@ class Dictionary {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// TODO i18n name_sl
|
||||||
// Fetch all dictionaries from DB for which the user has at least one dictionary role.
|
// Fetch all dictionaries from DB for which the user has at least one dictionary role.
|
||||||
static async fetchAllByUser(userId) {
|
static async fetchAllByUser(userId) {
|
||||||
// TODO Implement SQL stored procedures or functions.
|
// TODO Implement SQL stored procedures or functions.
|
||||||
@@ -75,7 +84,12 @@ class Dictionary {
|
|||||||
time_modified,
|
time_modified,
|
||||||
status,
|
status,
|
||||||
count_entries,
|
count_entries,
|
||||||
count_comments
|
count_comments,
|
||||||
|
(
|
||||||
|
SELECT administration
|
||||||
|
FROM user_role
|
||||||
|
WHERE user_id = $1 and dictionary_id = id
|
||||||
|
) as is_admin
|
||||||
FROM dictionary
|
FROM dictionary
|
||||||
WHERE id IN (
|
WHERE id IN (
|
||||||
SELECT dictionary_id
|
SELECT dictionary_id
|
||||||
@@ -86,8 +100,8 @@ class Dictionary {
|
|||||||
`
|
`
|
||||||
const values = [userId]
|
const values = [userId]
|
||||||
const { rows: fetchedDictionaries } = await db.query(text, values)
|
const { rows: fetchedDictionaries } = await db.query(text, values)
|
||||||
const deserializedDictionaries = fetchedDictionaries.map(
|
const deserializedDictionaries = fetchedDictionaries.map(dictionary =>
|
||||||
dictionary => new this(dictionary)
|
deserialize.dictionary(dictionary)
|
||||||
)
|
)
|
||||||
return deserializedDictionaries
|
return deserializedDictionaries
|
||||||
}
|
}
|
||||||
@@ -138,13 +152,13 @@ class Dictionary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fetch all languages from DB.
|
// Fetch all languages from DB.
|
||||||
static async fetchAllLanguages(lang) {
|
static async fetchAllLanguages(lang, excludeSlovene) {
|
||||||
// TODO Implement SQL stored procedures or functions.
|
// TODO Implement SQL stored procedures or functions.
|
||||||
const { rows: fetchedLanguages } = await db.query(`
|
const { rows: fetchedLanguages } = await db.query(`
|
||||||
SELECT
|
SELECT
|
||||||
id,
|
id,
|
||||||
${lang}
|
${lang}
|
||||||
FROM language
|
FROM language${excludeSlovene ? "\nWHERE code <> 'sl'" : ''}
|
||||||
ORDER BY ${lang}`)
|
ORDER BY ${lang}`)
|
||||||
const deserializedLanguages = fetchedLanguages.map(language =>
|
const deserializedLanguages = fetchedLanguages.map(language =>
|
||||||
deserialize.language(language)
|
deserialize.language(language)
|
||||||
@@ -413,15 +427,15 @@ class Dictionary {
|
|||||||
description,
|
description,
|
||||||
l.name_sl languageSl,
|
l.name_sl languageSl,
|
||||||
ds.name_sl domainSecondarySl,
|
ds.name_sl domainSecondarySl,
|
||||||
lp.name portalname,
|
lp.name portalnamesl,
|
||||||
lp.code portalcode
|
lp.code portalcode
|
||||||
FROM
|
FROM
|
||||||
dictionary d
|
dictionary d
|
||||||
INNER JOIN dictionary_language dl ON dl.dictionary_id = d.id
|
LEFT JOIN dictionary_language dl ON dl.dictionary_id = d.id
|
||||||
INNER JOIN language l ON dl.language_id = l.id
|
LEFT JOIN language l ON dl.language_id = l.id
|
||||||
LEFT JOIN dictionary_domain_secondary dds on dds.dictionary_id = d.id
|
LEFT JOIN dictionary_domain_secondary dds on dds.dictionary_id = d.id
|
||||||
LEFT JOIN domain_secondary ds ON dds.domain_secondary_id = ds.id
|
LEFT JOIN domain_secondary ds ON dds.domain_secondary_id = ds.id
|
||||||
INNER JOIN domain_primary dp ON d.domain_primary_id = dp.id
|
LEFT JOIN domain_primary dp ON d.domain_primary_id = dp.id
|
||||||
LEFT JOIN linked_dictionary ld ON ld.target_dictionary_id = d.id
|
LEFT JOIN linked_dictionary ld ON ld.target_dictionary_id = d.id
|
||||||
LEFT JOIN linked_portal lp ON ld.linked_portal_id = lp.id
|
LEFT JOIN linked_portal lp ON ld.linked_portal_id = lp.id
|
||||||
WHERE d.id = $1`
|
WHERE d.id = $1`
|
||||||
@@ -447,7 +461,7 @@ class Dictionary {
|
|||||||
d.author,
|
d.author,
|
||||||
dp.name_sl domain_primary,
|
dp.name_sl domain_primary,
|
||||||
description,
|
description,
|
||||||
lp.name portalname,
|
lp.name portalnamesl,
|
||||||
lp.code portalcode
|
lp.code portalcode
|
||||||
FROM
|
FROM
|
||||||
dictionary d
|
dictionary d
|
||||||
@@ -509,7 +523,7 @@ class Dictionary {
|
|||||||
d.author,
|
d.author,
|
||||||
dp.name_sl domain_primary,
|
dp.name_sl domain_primary,
|
||||||
description,
|
description,
|
||||||
lp.name portalname,
|
lp.name portalnamesl,
|
||||||
lp.code portalcode
|
lp.code portalcode
|
||||||
FROM
|
FROM
|
||||||
dictionary d
|
dictionary d
|
||||||
@@ -577,12 +591,13 @@ class Dictionary {
|
|||||||
distinct (d.id),
|
distinct (d.id),
|
||||||
d.${lang} dictionarysl,
|
d.${lang} dictionarysl,
|
||||||
d.count_entries,
|
d.count_entries,
|
||||||
|
d.count_comments,
|
||||||
to_char(d.time_modified,'YYYY-MM-DD') time_modified,
|
to_char(d.time_modified,'YYYY-MM-DD') time_modified,
|
||||||
d.issn,
|
d.issn,
|
||||||
d.author,
|
d.author,
|
||||||
dp.name_sl domain_primary,
|
dp.name_sl domain_primary,
|
||||||
description,
|
description,
|
||||||
lp.name portalname,
|
lp.name portalnamesl,
|
||||||
lp.code portalcode
|
lp.code portalcode
|
||||||
FROM
|
FROM
|
||||||
dictionary d
|
dictionary d
|
||||||
@@ -590,7 +605,7 @@ class Dictionary {
|
|||||||
INNER JOIN domain_primary dp ON d.domain_primary_id = dp.id
|
INNER JOIN domain_primary dp ON d.domain_primary_id = dp.id
|
||||||
LEFT JOIN linked_dictionary ld ON ld.target_dictionary_id = d.id
|
LEFT JOIN linked_dictionary ld ON ld.target_dictionary_id = d.id
|
||||||
LEFT JOIN linked_portal lp ON ld.linked_portal_id = lp.id
|
LEFT JOIN linked_portal lp ON ld.linked_portal_id = lp.id
|
||||||
WHERE d.name_sl LIKE '%' || $1 || '%' ${queryAppend}
|
WHERE d.status = 'published' AND LOWER(d.name_sl) LIKE '%' || LOWER($1) || '%' ${queryAppend}
|
||||||
ORDER BY ${orderBy}
|
ORDER BY ${orderBy}
|
||||||
LIMIT $2
|
LIMIT $2
|
||||||
OFFSET $3`
|
OFFSET $3`
|
||||||
@@ -643,7 +658,7 @@ class Dictionary {
|
|||||||
INNER JOIN domain_primary dp ON d.domain_primary_id = dp.id
|
INNER JOIN domain_primary dp ON d.domain_primary_id = dp.id
|
||||||
LEFT JOIN linked_dictionary ld ON ld.target_dictionary_id = d.id
|
LEFT JOIN linked_dictionary ld ON ld.target_dictionary_id = d.id
|
||||||
LEFT JOIN linked_portal lp ON ld.linked_portal_id = lp.id
|
LEFT JOIN linked_portal lp ON ld.linked_portal_id = lp.id
|
||||||
WHERE d.name_sl LIKE '%' || $1 || '%' ${queryAppend}`
|
WHERE d.status = 'published' AND d.name_sl LIKE '%' || $1 || '%' ${queryAppend}`
|
||||||
|
|
||||||
const { rows } = await db.query(text, [searchQuery])
|
const { rows } = await db.query(text, [searchQuery])
|
||||||
|
|
||||||
@@ -656,8 +671,8 @@ class Dictionary {
|
|||||||
count(d.id)
|
count(d.id)
|
||||||
FROM
|
FROM
|
||||||
dictionary d
|
dictionary d
|
||||||
INNER JOIN dictionary_language dl ON dl.dictionary_id = d.id
|
LEFT JOIN dictionary_language dl ON dl.dictionary_id = d.id
|
||||||
INNER JOIN domain_primary dp ON d.domain_primary_id = dp.id
|
LEFT JOIN domain_primary dp ON d.domain_primary_id = dp.id
|
||||||
LEFT JOIN linked_dictionary ld ON ld.target_dictionary_id = d.id
|
LEFT JOIN linked_dictionary ld ON ld.target_dictionary_id = d.id
|
||||||
LEFT JOIN linked_portal lp ON ld.linked_portal_id = lp.id`
|
LEFT JOIN linked_portal lp ON ld.linked_portal_id = lp.id`
|
||||||
|
|
||||||
@@ -666,6 +681,23 @@ class Dictionary {
|
|||||||
return rows[0]
|
return rows[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async fetchAllDictionariesPublishedCount() {
|
||||||
|
const text = `
|
||||||
|
SELECT
|
||||||
|
count(d.id)
|
||||||
|
FROM
|
||||||
|
dictionary d
|
||||||
|
LEFT JOIN dictionary_language dl ON dl.dictionary_id = d.id
|
||||||
|
LEFT JOIN domain_primary dp ON d.domain_primary_id = dp.id
|
||||||
|
LEFT JOIN linked_dictionary ld ON ld.target_dictionary_id = d.id
|
||||||
|
LEFT JOIN linked_portal lp ON ld.linked_portal_id = lp.id
|
||||||
|
WHERE d.status = 'published'`
|
||||||
|
|
||||||
|
const { rows } = await db.query(text)
|
||||||
|
|
||||||
|
return rows[0]
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch single dictionary data for editing structure from DB.
|
// Fetch single dictionary data for editing structure from DB.
|
||||||
static async fetchDictionaryWithEditStructure(dictionaryId) {
|
static async fetchDictionaryWithEditStructure(dictionaryId) {
|
||||||
const text = `
|
const text = `
|
||||||
@@ -734,10 +766,11 @@ class Dictionary {
|
|||||||
SELECT
|
SELECT
|
||||||
d.id,
|
d.id,
|
||||||
${lang} dictionarysl,
|
${lang} dictionarysl,
|
||||||
dp.name_sl domain_primary
|
dp.name_sl domain_primary,
|
||||||
|
d.count_comments
|
||||||
FROM dictionary d
|
FROM dictionary d
|
||||||
INNER JOIN domain_primary dp ON d.domain_primary_id = dp.id
|
INNER JOIN domain_primary dp ON d.domain_primary_id = dp.id
|
||||||
where d.time_published is not NULL
|
where d.time_published is not NULL and d.status = 'published'
|
||||||
ORDER BY d.time_published desc
|
ORDER BY d.time_published desc
|
||||||
limit 3`
|
limit 3`
|
||||||
|
|
||||||
@@ -984,15 +1017,62 @@ class Dictionary {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Import entries from extraction into DB.
|
||||||
|
static async importFromExtraction(dictionaryId, userId, termCandidates) {
|
||||||
|
await db.transaction(async dbClient => {
|
||||||
|
// TODO Depending on performance, consider batching requests.
|
||||||
|
for (const {
|
||||||
|
kanonicnaoblika: term,
|
||||||
|
definicija: other
|
||||||
|
} of termCandidates) {
|
||||||
|
const values = [
|
||||||
|
dictionaryId,
|
||||||
|
false,
|
||||||
|
'suggestion',
|
||||||
|
term || null,
|
||||||
|
userId,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
[],
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
[],
|
||||||
|
other || null,
|
||||||
|
[],
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
]
|
||||||
|
|
||||||
|
const text = `SELECT entry_new (${db.genParamStr(values)})`
|
||||||
|
|
||||||
|
await dbClient.query(text, values)
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.updateMetadataAfterModifyingEntries(dictionaryId, dbClient)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Index entries of specific dictionary from DB into search engine.
|
// Index entries of specific dictionary from DB into search engine.
|
||||||
static async indexIntoSearchEngine(dictionaryId) {
|
static async indexIntoSearchEngine(dictionaryId) {
|
||||||
// TODO Luka: I expect "source" needing a rework once linked portals and dictionaries start working.
|
|
||||||
const source = {
|
|
||||||
code: await getInstanceSetting('portal_code'),
|
|
||||||
name: await getInstanceSetting('portal_name')
|
|
||||||
}
|
|
||||||
const dbClient = await db.getClient()
|
const dbClient = await db.getClient()
|
||||||
try {
|
try {
|
||||||
|
const {
|
||||||
|
rows: [{ code: linkedPortalCode, name: linkedPortalName } = {}]
|
||||||
|
} = await dbClient.query(
|
||||||
|
'SELECT p.code, p.name FROM linked_dictionary d LEFT JOIN linked_portal p ON p.id = d.linked_portal_id WHERE d.id = $1',
|
||||||
|
[dictionaryId]
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO i18n Luka: index portal name for both languages?
|
||||||
|
const source = {
|
||||||
|
code: linkedPortalCode ?? (await getInstanceSetting('portal_code')),
|
||||||
|
name: linkedPortalName ?? (await getInstanceSetting('portal_name_sl'))
|
||||||
|
}
|
||||||
|
|
||||||
const queryValues = [dictionaryId]
|
const queryValues = [dictionaryId]
|
||||||
|
|
||||||
const dictionaryQueryText = `
|
const dictionaryQueryText = `
|
||||||
@@ -1060,7 +1140,7 @@ class Dictionary {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
FROM entry_foreign ef
|
FROM entry_foreign ef
|
||||||
LEFT JOIN LANGUAGE l ON l.id = ef.language_id
|
LEFT JOIN language l ON l.id = ef.language_id
|
||||||
WHERE entry_id = e.id
|
WHERE entry_id = e.id
|
||||||
) foreign_entries
|
) foreign_entries
|
||||||
FROM entry e
|
FROM entry e
|
||||||
@@ -1154,6 +1234,41 @@ class Dictionary {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async fetchFilteredPaginationDomainLabels(
|
||||||
|
dictionaryId,
|
||||||
|
query,
|
||||||
|
resultsPerPage,
|
||||||
|
page
|
||||||
|
) {
|
||||||
|
const {
|
||||||
|
rows: [{ result }]
|
||||||
|
} = await db.query(
|
||||||
|
`
|
||||||
|
SELECT jsonb_build_object(
|
||||||
|
'pages_total', (
|
||||||
|
SELECT CEIL(COUNT(*) / $3::float)
|
||||||
|
FROM domain_label
|
||||||
|
WHERE dictionary_id = $1 and name LIKE '%' || $2 || '%'
|
||||||
|
),
|
||||||
|
'results', ARRAY(
|
||||||
|
SELECT jsonb_build_object(
|
||||||
|
'id', id,
|
||||||
|
'name', name,
|
||||||
|
'isVisible', is_visible
|
||||||
|
)
|
||||||
|
FROM domain_label
|
||||||
|
WHERE dictionary_id = $1 and LOWER(name) LIKE '%' || LOWER($2) || '%'
|
||||||
|
ORDER BY name
|
||||||
|
LIMIT $3
|
||||||
|
OFFSET $4
|
||||||
|
)
|
||||||
|
) result`,
|
||||||
|
[dictionaryId, query, resultsPerPage, resultsPerPage * (page - 1)]
|
||||||
|
)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch all secondary domains from DB.
|
// Fetch all secondary domains from DB.
|
||||||
static async fetchAllSecondaryDomains(resultsPerPage, page) {
|
static async fetchAllSecondaryDomains(resultsPerPage, page) {
|
||||||
const {
|
const {
|
||||||
@@ -1184,6 +1299,38 @@ class Dictionary {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetch filtered secondary domains from DB.
|
||||||
|
static async fetchFilteredSecondaryDomains(query, resultsPerPage, page) {
|
||||||
|
const {
|
||||||
|
rows: [{ result }]
|
||||||
|
} = await db.query(
|
||||||
|
`
|
||||||
|
SELECT jsonb_build_object(
|
||||||
|
'pages_total', (
|
||||||
|
SELECT CEIL(COUNT(*) / $2::float)
|
||||||
|
FROM domain_secondary
|
||||||
|
WHERE name_sl LIKE '%' || $1 || '%'
|
||||||
|
),
|
||||||
|
'results', ARRAY(
|
||||||
|
SELECT jsonb_build_object(
|
||||||
|
'id', id,
|
||||||
|
'isApproved', approved,
|
||||||
|
'nameSl', name_sl,
|
||||||
|
'nameEn', name_en
|
||||||
|
)
|
||||||
|
FROM domain_secondary
|
||||||
|
WHERE LOWER(name_sl) LIKE '%' || LOWER($1) || '%'
|
||||||
|
ORDER BY name_sl
|
||||||
|
LIMIT $2
|
||||||
|
OFFSET $3
|
||||||
|
)
|
||||||
|
) result`,
|
||||||
|
[query, resultsPerPage, resultsPerPage * (page - 1)]
|
||||||
|
)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
static async updateDomainLabel(dictionaryId, data) {
|
static async updateDomainLabel(dictionaryId, data) {
|
||||||
const dbClient = await db.getClient()
|
const dbClient = await db.getClient()
|
||||||
try {
|
try {
|
||||||
@@ -1343,24 +1490,54 @@ class Dictionary {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async fetchAllImports(dictionaryId) {
|
// static async fetchAllImports(dictionaryId) {
|
||||||
const text = `
|
// const text = `
|
||||||
SELECT
|
// SELECT
|
||||||
time_started,
|
// time_started,
|
||||||
status,
|
// status,
|
||||||
delete_existing_entries,
|
// delete_existing_entries,
|
||||||
file_format,
|
// file_format,
|
||||||
count_valid_entries
|
// count_valid_entries
|
||||||
|
// FROM import_file_job
|
||||||
|
// WHERE dictionary_id = $1`
|
||||||
|
|
||||||
|
// const value = [dictionaryId]
|
||||||
|
// const { rows: fetchedImports } = await db.query(text, value)
|
||||||
|
|
||||||
|
// const deserializedImports = fetchedImports.map(oneImport =>
|
||||||
|
// deserialize.imports(oneImport)
|
||||||
|
// )
|
||||||
|
// return deserializedImports
|
||||||
|
// }
|
||||||
|
|
||||||
|
static async fetchAllImports(dictionaryId, resultsPerPage, page) {
|
||||||
|
const {
|
||||||
|
rows: [{ result }]
|
||||||
|
} = await db.query(
|
||||||
|
`
|
||||||
|
SELECT jsonb_build_object(
|
||||||
|
'pages_total', (
|
||||||
|
SELECT CEIL(COUNT(*) / $2::float)
|
||||||
FROM import_file_job
|
FROM import_file_job
|
||||||
WHERE dictionary_id = $1`
|
WHERE dictionary_id = $1
|
||||||
|
),
|
||||||
const value = [dictionaryId]
|
'results', ARRAY(
|
||||||
const { rows: fetchedImports } = await db.query(text, value)
|
SELECT jsonb_build_object(
|
||||||
|
'time_started', time_started,
|
||||||
const deserializedImports = fetchedImports.map(oneImport =>
|
'status', status,
|
||||||
deserialize.imports(oneImport)
|
'delete_existing_entries', delete_existing_entries,
|
||||||
|
'file_format', file_format,
|
||||||
|
'count_valid_entries', count_valid_entries
|
||||||
)
|
)
|
||||||
return deserializedImports
|
FROM import_file_job
|
||||||
|
WHERE dictionary_id = $1
|
||||||
|
LIMIT $2
|
||||||
|
OFFSET $3
|
||||||
|
)
|
||||||
|
) result`,
|
||||||
|
[dictionaryId, resultsPerPage, resultsPerPage * (page - 1)]
|
||||||
|
)
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
static async delete(dictionaryId) {
|
static async delete(dictionaryId) {
|
||||||
@@ -1368,6 +1545,464 @@ class Dictionary {
|
|||||||
const value = [dictionaryId]
|
const value = [dictionaryId]
|
||||||
await db.query(text, value)
|
await db.query(text, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// static async fetchExports(dictionaryId) {
|
||||||
|
// const text = `
|
||||||
|
// SELECT
|
||||||
|
// id,
|
||||||
|
// status,
|
||||||
|
// to_char(time_created, 'FMDD. FMMM. YYYY') date_created,
|
||||||
|
// entry_count,
|
||||||
|
// is_valid_filter,
|
||||||
|
// is_published_filter,
|
||||||
|
// is_terminology_reviewed_filter,
|
||||||
|
// is_language_reviewed_filter,
|
||||||
|
// status_filter,
|
||||||
|
// export_file_format
|
||||||
|
// FROM dictionary_export
|
||||||
|
// WHERE dictionary_id = $1
|
||||||
|
// ORDER BY id DESC`
|
||||||
|
// const value = [dictionaryId]
|
||||||
|
|
||||||
|
// const { rows: fetchedExports } = await db.query(text, value)
|
||||||
|
|
||||||
|
// const deserializedExports = fetchedExports.map(eachExport =>
|
||||||
|
// deserialize.exports(eachExport)
|
||||||
|
// )
|
||||||
|
// return deserializedExports
|
||||||
|
// }
|
||||||
|
|
||||||
|
static async fetchExports(dictionaryId, resultsPerPage, page) {
|
||||||
|
const {
|
||||||
|
rows: [{ result }]
|
||||||
|
} = await db.query(
|
||||||
|
`
|
||||||
|
SELECT jsonb_build_object(
|
||||||
|
'pages_total', (
|
||||||
|
SELECT CEIL(COUNT(*) / $2::float)
|
||||||
|
FROM dictionary_export
|
||||||
|
WHERE dictionary_id = $1
|
||||||
|
),
|
||||||
|
'results', ARRAY(
|
||||||
|
SELECT jsonb_build_object(
|
||||||
|
'id', id,
|
||||||
|
'status', status,
|
||||||
|
'time_created', time_created,
|
||||||
|
'entry_count', entry_count,
|
||||||
|
'is_valid_filter', is_valid_filter,
|
||||||
|
'is_published_filter', is_published_filter,
|
||||||
|
'is_terminology_reviewed_filter', is_terminology_reviewed_filter,
|
||||||
|
'is_language_reviewed_filter', is_language_reviewed_filter,
|
||||||
|
'status_filter', status_filter,
|
||||||
|
'export_file_format', export_file_format
|
||||||
|
)
|
||||||
|
FROM dictionary_export
|
||||||
|
WHERE dictionary_id = $1
|
||||||
|
LIMIT $2
|
||||||
|
OFFSET $3
|
||||||
|
)
|
||||||
|
) result`,
|
||||||
|
[dictionaryId, resultsPerPage, resultsPerPage * (page - 1)]
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
static async beginExport(dictionaryId, exportParams) {
|
||||||
|
const text = `
|
||||||
|
INSERT INTO dictionary_export (
|
||||||
|
dictionary_id,
|
||||||
|
is_valid_filter,
|
||||||
|
is_published_filter,
|
||||||
|
is_terminology_reviewed_filter,
|
||||||
|
is_language_reviewed_filter,
|
||||||
|
status_filter,
|
||||||
|
export_file_format
|
||||||
|
)
|
||||||
|
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||||
|
RETURNING id
|
||||||
|
`
|
||||||
|
|
||||||
|
const values = [
|
||||||
|
dictionaryId,
|
||||||
|
exportParams.isValidFilter,
|
||||||
|
exportParams.isPublishedFilter,
|
||||||
|
exportParams.isTerminologyReviewedFilter,
|
||||||
|
exportParams.isLanguageReviewedFilter,
|
||||||
|
exportParams.statusFilter,
|
||||||
|
exportParams.exportFileFormat
|
||||||
|
]
|
||||||
|
|
||||||
|
const {
|
||||||
|
rows: [{ id }]
|
||||||
|
} = await db.query(text, values)
|
||||||
|
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
static async processExport(exportId) {
|
||||||
|
const dbClient = await db.getClient()
|
||||||
|
let exportFilePath
|
||||||
|
let exportFile
|
||||||
|
let cursor
|
||||||
|
|
||||||
|
try {
|
||||||
|
const exportQueryText = `
|
||||||
|
UPDATE dictionary_export
|
||||||
|
SET status = 'in progress', time_started = NOW()
|
||||||
|
WHERE id = $1
|
||||||
|
RETURNING
|
||||||
|
dictionary_id,
|
||||||
|
is_valid_filter,
|
||||||
|
is_published_filter,
|
||||||
|
is_terminology_reviewed_filter,
|
||||||
|
is_language_reviewed_filter,
|
||||||
|
status_filter,
|
||||||
|
export_file_format
|
||||||
|
`
|
||||||
|
|
||||||
|
const exportQueryParams = [exportId]
|
||||||
|
|
||||||
|
const {
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
dictionary_id: dictionaryId,
|
||||||
|
is_valid_filter: isValidFilter,
|
||||||
|
is_published_filter: isPublishedFilter,
|
||||||
|
is_terminology_reviewed_filter: isTerminologyReviewedFilter,
|
||||||
|
is_language_reviewed_filter: isLanguageReviewedFilter,
|
||||||
|
status_filter: statusFilter,
|
||||||
|
export_file_format: exportFileFormat
|
||||||
|
}
|
||||||
|
]
|
||||||
|
} = await dbClient.query(exportQueryText, exportQueryParams)
|
||||||
|
|
||||||
|
const dictionaryQueryText = `
|
||||||
|
SELECT
|
||||||
|
entries_have_domain_labels,
|
||||||
|
entries_have_label,
|
||||||
|
entries_have_definition,
|
||||||
|
entries_have_synonyms,
|
||||||
|
entries_have_links,
|
||||||
|
entries_have_other,
|
||||||
|
entries_have_foreign_languages,
|
||||||
|
entries_have_foreign_definitions,
|
||||||
|
entries_have_foreign_synonyms,
|
||||||
|
entries_have_images,
|
||||||
|
entries_have_audio,
|
||||||
|
entries_have_videos
|
||||||
|
FROM dictionary
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
const dictionaryQueryParams = [dictionaryId]
|
||||||
|
|
||||||
|
const {
|
||||||
|
rows: [dictionaryStructure]
|
||||||
|
} = await dbClient.query(dictionaryQueryText, dictionaryQueryParams)
|
||||||
|
|
||||||
|
const exportFields = {
|
||||||
|
domainLabels: dictionaryStructure.entries_have_domain_labels,
|
||||||
|
label: dictionaryStructure.entries_have_label,
|
||||||
|
definition: dictionaryStructure.entries_have_definition,
|
||||||
|
synonyms: dictionaryStructure.entries_have_synonyms,
|
||||||
|
links: dictionaryStructure.entries_have_links,
|
||||||
|
other: dictionaryStructure.entries_have_other,
|
||||||
|
foreignTerms: dictionaryStructure.entries_have_foreign_languages,
|
||||||
|
foreignDefinitions:
|
||||||
|
dictionaryStructure.entries_have_foreign_definitions,
|
||||||
|
foreignSynonyms: dictionaryStructure.entries_have_foreign_synonyms,
|
||||||
|
images: dictionaryStructure.entries_have_images,
|
||||||
|
audio: dictionaryStructure.entries_have_audio,
|
||||||
|
videos: dictionaryStructure.entries_have_videos
|
||||||
|
}
|
||||||
|
|
||||||
|
let entryQueryText = `
|
||||||
|
SELECT
|
||||||
|
term${
|
||||||
|
exportFields.label
|
||||||
|
? `,
|
||||||
|
label`
|
||||||
|
: ''
|
||||||
|
}${
|
||||||
|
exportFields.definition
|
||||||
|
? `,
|
||||||
|
definition`
|
||||||
|
: ''
|
||||||
|
}${
|
||||||
|
exportFields.synonyms
|
||||||
|
? `,
|
||||||
|
synonym synonyms`
|
||||||
|
: ''
|
||||||
|
}${
|
||||||
|
exportFields.other
|
||||||
|
? `,
|
||||||
|
other`
|
||||||
|
: ''
|
||||||
|
}${
|
||||||
|
exportFields.images
|
||||||
|
? `,
|
||||||
|
image`
|
||||||
|
: ''
|
||||||
|
}${
|
||||||
|
exportFields.audio
|
||||||
|
? `,
|
||||||
|
audio`
|
||||||
|
: ''
|
||||||
|
}${
|
||||||
|
exportFields.videos
|
||||||
|
? `,
|
||||||
|
video`
|
||||||
|
: ''
|
||||||
|
}${
|
||||||
|
exportFields.domainLabels
|
||||||
|
? `,
|
||||||
|
ARRAY(
|
||||||
|
SELECT name
|
||||||
|
FROM entry_domain_label edl
|
||||||
|
LEFT JOIN domain_label dl ON dl.id = edl.domain_label_id
|
||||||
|
WHERE entry_id = e.id
|
||||||
|
) domain_labels`
|
||||||
|
: ''
|
||||||
|
}${
|
||||||
|
exportFields.links
|
||||||
|
? `,
|
||||||
|
ARRAY(
|
||||||
|
SELECT jsonb_build_object(
|
||||||
|
'link', link,
|
||||||
|
'type', type)
|
||||||
|
FROM entry_link
|
||||||
|
WHERE entry_id = e.id
|
||||||
|
) links`
|
||||||
|
: ''
|
||||||
|
}${
|
||||||
|
exportFields.foreignTerms
|
||||||
|
? `,
|
||||||
|
ARRAY(
|
||||||
|
SELECT jsonb_build_object(
|
||||||
|
'lang_code', l.code,
|
||||||
|
'terms', ef.term${
|
||||||
|
exportFields.foreignDefinitions
|
||||||
|
? `,
|
||||||
|
'definition', ef.definition`
|
||||||
|
: ''
|
||||||
|
}${
|
||||||
|
exportFields.foreignSynonyms
|
||||||
|
? `,
|
||||||
|
'synonyms', ef.synonym`
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
)
|
||||||
|
FROM dictionary_language dl
|
||||||
|
LEFT JOIN language l ON l.id = dl.language_id
|
||||||
|
LEFT JOIN entry_foreign ef ON ef.language_id = l.id
|
||||||
|
WHERE dl.dictionary_id = $1 AND ef.entry_id = e.id
|
||||||
|
ORDER BY dl.selection_order
|
||||||
|
) foreign_entries`
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
FROM entry e
|
||||||
|
WHERE
|
||||||
|
e.dictionary_id = $1`
|
||||||
|
|
||||||
|
const entryQueryParams = [dictionaryId]
|
||||||
|
|
||||||
|
if (isValidFilter !== null) {
|
||||||
|
entryQueryParams.push(isValidFilter)
|
||||||
|
entryQueryText += ` AND is_valid = $${entryQueryParams.length}`
|
||||||
|
}
|
||||||
|
if (isPublishedFilter !== null) {
|
||||||
|
entryQueryParams.push(isPublishedFilter)
|
||||||
|
entryQueryText += ` AND is_published = $${entryQueryParams.length}`
|
||||||
|
}
|
||||||
|
if (isTerminologyReviewedFilter !== null) {
|
||||||
|
entryQueryParams.push(isTerminologyReviewedFilter)
|
||||||
|
entryQueryText += ` AND is_terminology_reviewed = $${entryQueryParams.length}`
|
||||||
|
}
|
||||||
|
if (isLanguageReviewedFilter !== null) {
|
||||||
|
entryQueryParams.push(isLanguageReviewedFilter)
|
||||||
|
entryQueryText += ` AND is_language_reviewed = $${entryQueryParams.length}`
|
||||||
|
}
|
||||||
|
if (statusFilter !== null) {
|
||||||
|
entryQueryParams.push(statusFilter)
|
||||||
|
entryQueryText += ` AND status = $${entryQueryParams.length}`
|
||||||
|
}
|
||||||
|
entryQueryText += '\nORDER BY term'
|
||||||
|
|
||||||
|
const exportFilesPath = getExportFilesPath(dictionaryId)
|
||||||
|
await mkdir(exportFilesPath, { recursive: true })
|
||||||
|
exportFilePath = `${exportFilesPath}/${exportId}`
|
||||||
|
exportFile = await open(exportFilePath, 'ax')
|
||||||
|
|
||||||
|
const isXmlFileFormat = exportFileFormat === 'xml'
|
||||||
|
const isDsvFileFormat = ['csv', 'tsv'].includes(exportFileFormat)
|
||||||
|
const isTbxFileFormat = exportFileFormat === 'tbx'
|
||||||
|
let dsvConfig
|
||||||
|
if (isXmlFileFormat) {
|
||||||
|
const openingMarkup =
|
||||||
|
'<?xml version="1.0" encoding="utf-8"?>\n<dictionary>\n'
|
||||||
|
await exportFile.write(openingMarkup)
|
||||||
|
} else if (isDsvFileFormat) {
|
||||||
|
if (exportFileFormat === 'csv') dsvConfig = { delimiter: ';' }
|
||||||
|
else if (exportFileFormat === 'tsv') dsvConfig = { delimiter: '\t' }
|
||||||
|
|
||||||
|
if (exportFields.foreignTerms) {
|
||||||
|
const { rows: languages } = await dbClient.query(
|
||||||
|
`
|
||||||
|
SELECT l.code
|
||||||
|
FROM dictionary d
|
||||||
|
LEFT JOIN dictionary_language dl ON dl.dictionary_id = d.id
|
||||||
|
LEFT JOIN language l ON l.id = dl.language_id
|
||||||
|
WHERE d.id = $1
|
||||||
|
ORDER BY dl.selection_order
|
||||||
|
`,
|
||||||
|
[dictionaryId]
|
||||||
|
)
|
||||||
|
|
||||||
|
dsvConfig.languageCodes = languages.map(language => language.code)
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldNamesArr = ['term']
|
||||||
|
if (exportFields.domainLabels) fieldNamesArr.push('domainLabels')
|
||||||
|
if (exportFields.label) fieldNamesArr.push('label')
|
||||||
|
if (exportFields.definition) fieldNamesArr.push('def')
|
||||||
|
if (exportFields.synonyms) fieldNamesArr.push('syns')
|
||||||
|
if (exportFields.links) fieldNamesArr.push('links')
|
||||||
|
if (exportFields.other) fieldNamesArr.push('other')
|
||||||
|
dsvConfig.languageCodes?.forEach(languageCode => {
|
||||||
|
fieldNamesArr.push(`[${languageCode}]fTerms`)
|
||||||
|
if (exportFields.foreignDefinitions) {
|
||||||
|
fieldNamesArr.push(`[${languageCode}]fDef`)
|
||||||
|
}
|
||||||
|
if (exportFields.foreignSynonyms) {
|
||||||
|
fieldNamesArr.push(`[${languageCode}]fSyns`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (exportFields.images) fieldNamesArr.push('images')
|
||||||
|
if (exportFields.audio) fieldNamesArr.push('audios')
|
||||||
|
if (exportFields.videos) fieldNamesArr.push('videos')
|
||||||
|
const headerLine = `${fieldNamesArr.join(dsvConfig.delimiter)}\n`
|
||||||
|
await exportFile.write(headerLine)
|
||||||
|
} else if (isTbxFileFormat) {
|
||||||
|
const {
|
||||||
|
rows: [tbxMetadata]
|
||||||
|
} = await dbClient.query(
|
||||||
|
`
|
||||||
|
SELECT
|
||||||
|
name_sl,
|
||||||
|
name_en,
|
||||||
|
author,
|
||||||
|
to_char(time_modified, 'YYYY-MM-DD') modified_date_string,
|
||||||
|
status,
|
||||||
|
(
|
||||||
|
SELECT to_char(time_created, 'YYYY-MM-DD')
|
||||||
|
FROM dictionary_export de
|
||||||
|
WHERE de.id = $1
|
||||||
|
) export_date_string
|
||||||
|
FROM dictionary
|
||||||
|
WHERE id = $2
|
||||||
|
`,
|
||||||
|
[exportId, dictionaryId]
|
||||||
|
)
|
||||||
|
const portalName = await getInstanceSetting('portal_name_sl')
|
||||||
|
const authorsString = tbxMetadata.author?.join(', ')
|
||||||
|
const urlPublished =
|
||||||
|
tbxMetadata.status === 'published'
|
||||||
|
? new URL(`/slovarji/${dictionaryId}/o-slovarju`, origin).href
|
||||||
|
: null
|
||||||
|
|
||||||
|
let openingMarkup = '<?xml version="1.0" encoding="utf-8"?>\n'
|
||||||
|
openingMarkup += '<!DOCTYPE martif SYSTEM "TBXcoreStructV02.dtd">\n'
|
||||||
|
openingMarkup += '<martif type="TBX" xml:lang="sl">\n<martifHeader>\n'
|
||||||
|
openingMarkup += '<fileDesc>\n<titleStmt>\n'
|
||||||
|
openingMarkup += `<title>${tbxMetadata.name_sl}</title>\n`
|
||||||
|
openingMarkup += `<note xml:lang="en">${tbxMetadata.name_en}</note>\n`
|
||||||
|
openingMarkup += '</titleStmt>\n<publicationStmt>\n'
|
||||||
|
openingMarkup += `<p>Datum objave: ${tbxMetadata.export_date_string}</p>\n`
|
||||||
|
openingMarkup +=
|
||||||
|
'<p>Avtorske pravice: Delo je dostopno pod pogoji licence CC BY 4.0.</p>\n'
|
||||||
|
openingMarkup += '</publicationStmt>\n<sourceDesc>\n'
|
||||||
|
openingMarkup += `<p>Vir: ${portalName}</p>\n`
|
||||||
|
if (authorsString) openingMarkup += `<p>Avtorji: ${authorsString}</p>\n`
|
||||||
|
openingMarkup += `<p>Datum objave: ${tbxMetadata.modified_date_string}</p>\n`
|
||||||
|
if (urlPublished) {
|
||||||
|
openingMarkup += `<p>Mesto objave: ${urlPublished}</p>\n`
|
||||||
|
}
|
||||||
|
openingMarkup += '</sourceDesc>\n</fileDesc>\n'
|
||||||
|
openingMarkup += '</martifHeader>\n<text>\n<body>\n'
|
||||||
|
|
||||||
|
await exportFile.write(openingMarkup)
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor = dbClient.query(new Cursor(entryQueryText, entryQueryParams))
|
||||||
|
|
||||||
|
let entries = []
|
||||||
|
let batchEntriesCount
|
||||||
|
let entriesWritten = 0
|
||||||
|
do {
|
||||||
|
// Keep getting and writing entries to file in batches of 100.
|
||||||
|
entries = await cursor.read(100)
|
||||||
|
batchEntriesCount = entries.length
|
||||||
|
|
||||||
|
if (batchEntriesCount) {
|
||||||
|
await transformAndAppend(
|
||||||
|
entries,
|
||||||
|
exportFields,
|
||||||
|
exportFile,
|
||||||
|
exportFileFormat,
|
||||||
|
dsvConfig
|
||||||
|
)
|
||||||
|
entriesWritten += batchEntriesCount
|
||||||
|
}
|
||||||
|
} while (batchEntriesCount === 100)
|
||||||
|
|
||||||
|
if (isXmlFileFormat) {
|
||||||
|
const closingMarkup = '</dictionary>\n'
|
||||||
|
await exportFile.write(closingMarkup)
|
||||||
|
} else if (isTbxFileFormat) {
|
||||||
|
const closingMarkup = '</body>\n</text>\n</martif>\n'
|
||||||
|
await exportFile.write(closingMarkup)
|
||||||
|
}
|
||||||
|
|
||||||
|
await dbClient.query(
|
||||||
|
"UPDATE dictionary_export SET status = 'finished', time_finished = NOW(), entry_count = $1 WHERE id = $2",
|
||||||
|
[entriesWritten, exportId]
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
await cursor?.close()
|
||||||
|
await dbClient.query(
|
||||||
|
"UPDATE dictionary_export SET status = 'failed', time_finished = NOW() WHERE id = $1",
|
||||||
|
[exportId]
|
||||||
|
)
|
||||||
|
await unlink(exportFilePath)
|
||||||
|
throw error
|
||||||
|
} finally {
|
||||||
|
dbClient.release()
|
||||||
|
await exportFile?.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async fetchExportDownloadMetadata(exportId) {
|
||||||
|
const {
|
||||||
|
rows: [fetchedMetadata]
|
||||||
|
} = await db.query(
|
||||||
|
`
|
||||||
|
SELECT
|
||||||
|
e.status,
|
||||||
|
e.dictionary_id,
|
||||||
|
d.name_sl_short name_string,
|
||||||
|
to_char(e.time_created, 'YYYYMMDDHH24MISS') time_string,
|
||||||
|
e.export_file_format
|
||||||
|
FROM dictionary_export e
|
||||||
|
LEFT JOIN dictionary d ON d.id = e.dictionary_id
|
||||||
|
WHERE e.id = $1`,
|
||||||
|
[exportId]
|
||||||
|
)
|
||||||
|
|
||||||
|
const deserializedMetadata =
|
||||||
|
deserialize.exportDownloadMetadata(fetchedMetadata)
|
||||||
|
|
||||||
|
return deserializedMetadata
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Dictionary
|
module.exports = Dictionary
|
||||||
|
|||||||
@@ -3,15 +3,28 @@ const htmlToText = require('nodemailer-html-to-text').htmlToText()
|
|||||||
const {
|
const {
|
||||||
smtpHost,
|
smtpHost,
|
||||||
smtpPort,
|
smtpPort,
|
||||||
smtpTlsRejectUnauthorized,
|
smtpUser,
|
||||||
|
smtpPassword,
|
||||||
|
smtpSecure,
|
||||||
|
smtpRequireTls,
|
||||||
|
smtpAllowInvalidCerts,
|
||||||
smtpFrom
|
smtpFrom
|
||||||
} = require('../config/keys')
|
} = require('../config/keys')
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
host: smtpHost,
|
host: smtpHost,
|
||||||
port: smtpPort,
|
port: smtpPort,
|
||||||
tls: { rejectUnauthorized: smtpTlsRejectUnauthorized }
|
secure: smtpSecure,
|
||||||
|
requireTLS: smtpRequireTls,
|
||||||
|
tls: { rejectUnauthorized: !smtpAllowInvalidCerts }
|
||||||
}
|
}
|
||||||
|
if (smtpUser || smtpPassword) {
|
||||||
|
options.auth = {
|
||||||
|
user: smtpUser,
|
||||||
|
pass: smtpPassword
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const defaults = { from: smtpFrom }
|
const defaults = { from: smtpFrom }
|
||||||
|
|
||||||
const transporter = nodemailer.createTransport(options, defaults)
|
const transporter = nodemailer.createTransport(options, defaults)
|
||||||
|
|||||||
@@ -184,7 +184,12 @@ Entry.fetchFull = async entryId => {
|
|||||||
SELECT jsonb_strip_nulls(
|
SELECT jsonb_strip_nulls(
|
||||||
jsonb_build_object(
|
jsonb_build_object(
|
||||||
'version', version,
|
'version', version,
|
||||||
'version_time', version_time
|
'version_time', version_time,
|
||||||
|
'version_author', (
|
||||||
|
SELECT username
|
||||||
|
FROM "user"
|
||||||
|
WHERE id = (version_snapshot['version_author'])::int
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
FROM entry_version_history
|
FROM entry_version_history
|
||||||
@@ -232,6 +237,7 @@ Entry.fetchFullWithOrderedForeignLanguages = async entryId => {
|
|||||||
'audio', e.audio,
|
'audio', e.audio,
|
||||||
'video', e.video,
|
'video', e.video,
|
||||||
'time_modified', e.time_modified,
|
'time_modified', e.time_modified,
|
||||||
|
'external_url', e.external_url,
|
||||||
'domain_labels', ARRAY(
|
'domain_labels', ARRAY(
|
||||||
SELECT name
|
SELECT name
|
||||||
FROM entry_domain_label edl
|
FROM entry_domain_label edl
|
||||||
@@ -421,6 +427,8 @@ Entry.deleteAllLinks = async dictionaryId => {
|
|||||||
|
|
||||||
// (Re)index specific entry into entry search index.
|
// (Re)index specific entry into entry search index.
|
||||||
Entry.indexIntoSearchEngine = async (entryId, shouldWait) => {
|
Entry.indexIntoSearchEngine = async (entryId, shouldWait) => {
|
||||||
|
// TODO If this method is ever used for linked portals/dictionaries,
|
||||||
|
// TODO rework the source object below (already done in Dictionary.indexIntoSearchEngine).
|
||||||
const values = [entryId]
|
const values = [entryId]
|
||||||
const text = `
|
const text = `
|
||||||
SELECT
|
SELECT
|
||||||
@@ -465,7 +473,7 @@ Entry.indexIntoSearchEngine = async (entryId, shouldWait) => {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
FROM entry_foreign ef
|
FROM entry_foreign ef
|
||||||
LEFT JOIN LANGUAGE l ON l.id = ef.language_id
|
LEFT JOIN language l ON l.id = ef.language_id
|
||||||
WHERE entry_id = e.id
|
WHERE entry_id = e.id
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -495,10 +503,11 @@ Entry.indexIntoSearchEngine = async (entryId, shouldWait) => {
|
|||||||
|
|
||||||
const { dictionary, primary_domain: primaryDomain } = dataToIndex
|
const { dictionary, primary_domain: primaryDomain } = dataToIndex
|
||||||
let { entry } = dataToIndex
|
let { entry } = dataToIndex
|
||||||
// TODO Luka: I expect "source" needing a rework once linked portals and dictionaries start working.
|
|
||||||
|
// TODO i18n Luka: index portal name for both languages?
|
||||||
const source = {
|
const source = {
|
||||||
code: await getInstanceSetting('portal_code'),
|
code: await getInstanceSetting('portal_code'),
|
||||||
name: await getInstanceSetting('portal_name')
|
name: await getInstanceSetting('portal_name_sl')
|
||||||
}
|
}
|
||||||
|
|
||||||
entry = prepareEntryForIndexing(entry)
|
entry = prepareEntryForIndexing(entry)
|
||||||
@@ -618,13 +627,17 @@ Entry.update = async (userId, entry) => {
|
|||||||
// Fetch a single version snapshot of a single entry from DB.
|
// Fetch a single version snapshot of a single entry from DB.
|
||||||
Entry.fetchVersionSnapshot = async (entryId, version) => {
|
Entry.fetchVersionSnapshot = async (entryId, version) => {
|
||||||
const {
|
const {
|
||||||
rows: [{ version_snapshot: historySnapshot }]
|
rows: [{ version_snapshot: historySnapshot, author }]
|
||||||
} = await db.query(
|
} = await db.query(
|
||||||
'SELECT version_snapshot FROM entry_version_history WHERE entry_id = $1 and version = $2',
|
`SELECT v.version_snapshot, (
|
||||||
|
SELECT u.username
|
||||||
|
FROM "user" u
|
||||||
|
WHERE u.id = (v.version_snapshot['version_author'])::int) as author
|
||||||
|
FROM entry_version_history v WHERE v.entry_id = $1 and v.version = $2`,
|
||||||
[entryId, version]
|
[entryId, version]
|
||||||
)
|
)
|
||||||
|
|
||||||
return historySnapshot
|
return { data: historySnapshot, author }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Fetch by language and entry Id. Note that this version includes the language name */
|
/* Fetch by language and entry Id. Note that this version includes the language name */
|
||||||
|
|||||||
@@ -12,13 +12,14 @@ const {
|
|||||||
getFileNamesInFolder,
|
getFileNamesInFolder,
|
||||||
getFileStatsInFolder
|
getFileStatsInFolder
|
||||||
} = require('./helpers/extraction')
|
} = require('./helpers/extraction')
|
||||||
|
const { extractionApiOrigin } = require('../config/keys')
|
||||||
|
|
||||||
const Extraction = {}
|
const Extraction = {}
|
||||||
|
|
||||||
// Fetch all extractions for a specific user.
|
// Fetch all extractions for a specific user.
|
||||||
Extraction.fetchAllForUser = async userId => {
|
Extraction.fetchAllForUser = async userId => {
|
||||||
const { rows: fetchedExtractions } = await db.query(
|
const { rows: fetchedExtractions } = await db.query(
|
||||||
'SELECT id, name, status, corpus_id, oss_params, time_started, time_finished FROM extraction WHERE user_id = $1 ORDER BY id',
|
'SELECT id, name, status, corpus_id, oss_params, time_started, time_finished FROM extraction WHERE user_id = $1 ORDER BY status ASC, id DESC, time_finished DESC',
|
||||||
[userId]
|
[userId]
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -74,19 +75,19 @@ Extraction.fetch = async id => {
|
|||||||
return deserialize.extraction(fetchedExtraction)
|
return deserialize.extraction(fetchedExtraction)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch author email of a specific extraction entry from DB.
|
// Fetch data of the author of a specific extraction entry from DB.
|
||||||
Extraction.fetchAuthorEmail = async id => {
|
Extraction.fetchAuthorData = async id => {
|
||||||
const {
|
const {
|
||||||
rows: [{ email }]
|
rows: [authorData]
|
||||||
} = await db.query(
|
} = await db.query(
|
||||||
`SELECT u.email
|
`SELECT u.email, u.language
|
||||||
FROM extraction e
|
FROM extraction e
|
||||||
LEFT JOIN "user" u ON u.id = e.user_id
|
LEFT JOIN "user" u ON u.id = e.user_id
|
||||||
WHERE e.id = $1`,
|
WHERE e.id = $1`,
|
||||||
[id]
|
[id]
|
||||||
)
|
)
|
||||||
|
|
||||||
return email
|
return authorData
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update extraction entry in DB.
|
// Update extraction entry in DB.
|
||||||
@@ -160,6 +161,18 @@ Extraction.fetchTermCandidatesCount = async function (extractionId) {
|
|||||||
return termCandidates.length
|
return termCandidates.length
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetch term candidates slice for a specific extraction.
|
||||||
|
Extraction.fetchTermCandidatesSlice = async function (
|
||||||
|
extractionId,
|
||||||
|
fromIndex,
|
||||||
|
toIndex
|
||||||
|
) {
|
||||||
|
const termCandidatesJson = await this.fetchTermCandidatesJson(extractionId)
|
||||||
|
const termCandidates = JSON.parse(termCandidatesJson).terminoloski_kandidati
|
||||||
|
const termCandidatesSlice = termCandidates.slice(fromIndex, toIndex)
|
||||||
|
return termCandidatesSlice
|
||||||
|
}
|
||||||
|
|
||||||
// Mark extraction from own documents as began.
|
// Mark extraction from own documents as began.
|
||||||
Extraction.beginOwn = async (extractionId, documentsNames) => {
|
Extraction.beginOwn = async (extractionId, documentsNames) => {
|
||||||
let timeStarted
|
let timeStarted
|
||||||
@@ -195,20 +208,20 @@ Extraction.beginOwn = async (extractionId, documentsNames) => {
|
|||||||
Extraction.beginOss = async extractionId => {
|
Extraction.beginOss = async extractionId => {
|
||||||
let timeStarted
|
let timeStarted
|
||||||
await db.transaction(async dbClient => {
|
await db.transaction(async dbClient => {
|
||||||
const insertExtractionJob = dbClient.query(
|
|
||||||
'INSERT INTO extraction_job (extraction_id, job_type, filename) VALUES ($1, $2, $3)',
|
|
||||||
[extractionId, 'oss term candidates', '']
|
|
||||||
)
|
|
||||||
const updateExtraction = dbClient.query(
|
|
||||||
"UPDATE extraction SET status = 'in progress', time_started = NOW() WHERE id = $1 RETURNING time_started",
|
|
||||||
[extractionId]
|
|
||||||
)
|
|
||||||
|
|
||||||
;[
|
;[
|
||||||
{
|
{
|
||||||
rows: [{ time_started: timeStarted }]
|
rows: [{ time_started: timeStarted }]
|
||||||
}
|
}
|
||||||
] = await Promise.all([updateExtraction, insertExtractionJob])
|
] = await Promise.all([
|
||||||
|
dbClient.query(
|
||||||
|
"UPDATE extraction SET status = 'in progress', time_started = NOW() WHERE id = $1 RETURNING time_started",
|
||||||
|
[extractionId]
|
||||||
|
),
|
||||||
|
dbClient.query(
|
||||||
|
'INSERT INTO extraction_job (extraction_id, job_type, filename) VALUES ($1, $2, $3)',
|
||||||
|
[extractionId, 'oss term candidates', '']
|
||||||
|
)
|
||||||
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
return timeStarted
|
return timeStarted
|
||||||
@@ -235,6 +248,7 @@ Extraction.processOwn = async function (extractionId, extractionName) {
|
|||||||
const documentNames = await this.fetchAllDocumentsNames(extractionId)
|
const documentNames = await this.fetchAllDocumentsNames(extractionId)
|
||||||
const conllusPath = getConllusPath(extractionId)
|
const conllusPath = getConllusPath(extractionId)
|
||||||
const conllusPaths = []
|
const conllusPaths = []
|
||||||
|
const MAX_BODY_LENGTH = 10 ** 9 // 1 GB
|
||||||
// Using remote API, transform each document into conllu format.
|
// Using remote API, transform each document into conllu format.
|
||||||
for (const documentName of documentNames) {
|
for (const documentName of documentNames) {
|
||||||
const filePath = `${documentsPath}/${documentName}`
|
const filePath = `${documentsPath}/${documentName}`
|
||||||
@@ -242,17 +256,18 @@ Extraction.processOwn = async function (extractionId, extractionName) {
|
|||||||
form.append('file', createReadStream(filePath), documentName)
|
form.append('file', createReadStream(filePath), documentName)
|
||||||
try {
|
try {
|
||||||
const { data: data1 } = await axios.post(
|
const { data: data1 } = await axios.post(
|
||||||
'http://rsdo.lhrs.feri.um.si:8080/datotekaVConlluAsync',
|
`${extractionApiOrigin}/datotekaVConlluAsync`,
|
||||||
form,
|
form,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
...form.getHeaders()
|
...form.getHeaders()
|
||||||
}
|
},
|
||||||
|
maxBodyLength: MAX_BODY_LENGTH
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
const remotejobId = +data1.check_job_url.split('/').at(-1)
|
const remotejobId = +data1.check_job_url.split('/').at(-1)
|
||||||
await db.query(
|
await db.query(
|
||||||
"UPDATE extraction_job SET status = 'in progress', remote_job_id = $1 WHERE extraction_id = $2 AND job_type = $3 AND filename = $4",
|
"UPDATE extraction_job SET status = 'in progress', remote_job_id = $1, time_started = NOW() WHERE extraction_id = $2 AND job_type = $3 AND filename = $4",
|
||||||
[remotejobId, extractionId, 'doc to conllu', documentName]
|
[remotejobId, extractionId, 'doc to conllu', documentName]
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -262,10 +277,15 @@ Extraction.processOwn = async function (extractionId, extractionName) {
|
|||||||
while (true) {
|
while (true) {
|
||||||
await sleep(5)
|
await sleep(5)
|
||||||
const { data: data2 } = await axios.get(
|
const { data: data2 } = await axios.get(
|
||||||
`http://rsdo.lhrs.feri.um.si:8080/job/${remotejobId}`
|
`${extractionApiOrigin}/job/${remotejobId}`
|
||||||
)
|
)
|
||||||
if (data2.finished_on) {
|
if (data2.finished_on) {
|
||||||
if (data2.job_status !== 'finished processing (OK)') throw Error()
|
if (data2.job_status !== 'finished processing (OK)') {
|
||||||
|
throw Error(
|
||||||
|
`Remote job with id ${remotejobId} failed with result:\n${data2.job_result}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO Read the response as a stream and try to parse it's contents into a file (write stream)('stream-json' package?).
|
// TODO Read the response as a stream and try to parse it's contents into a file (write stream)('stream-json' package?).
|
||||||
const fileSavePath = `${conllusPath}/${documentName}.conllu`
|
const fileSavePath = `${conllusPath}/${documentName}.conllu`
|
||||||
await writeFile(fileSavePath, data2.job_result)
|
await writeFile(fileSavePath, data2.job_result)
|
||||||
@@ -277,7 +297,8 @@ Extraction.processOwn = async function (extractionId, extractionName) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch (error) {
|
||||||
|
logExtractionError(error, extractionId, 'doc to conllu', documentName)
|
||||||
await failTheJob(extractionId, 'doc to conllu', documentName)
|
await failTheJob(extractionId, 'doc to conllu', documentName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -309,15 +330,18 @@ Extraction.processOwn = async function (extractionId, extractionName) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const { data: data3 } = await axios.post(
|
const { data: data3 } = await axios.post(
|
||||||
'http://rsdo.lhrs.feri.um.si:8080/izlusciAsync',
|
`${extractionApiOrigin}/izlusciAsync`,
|
||||||
{
|
{
|
||||||
conllus: conllusArr,
|
conllus: conllusArr,
|
||||||
prepovedaneBesede: Array.from(stopTermsSet)
|
prepovedaneBesede: Array.from(stopTermsSet),
|
||||||
}
|
// TODO Enabled for all cases. Add a switch for users later.
|
||||||
|
definicije: true
|
||||||
|
},
|
||||||
|
{ maxBodyLength: MAX_BODY_LENGTH }
|
||||||
)
|
)
|
||||||
const remotejobId = +data3.check_job_url.split('/').at(-1)
|
const remotejobId = +data3.check_job_url.split('/').at(-1)
|
||||||
await db.query(
|
await db.query(
|
||||||
"UPDATE extraction_job SET status = 'in progress', remote_job_id = $1 WHERE extraction_id = $2 AND job_type = $3 AND filename = $4",
|
"UPDATE extraction_job SET status = 'in progress', remote_job_id = $1, time_started = NOW() WHERE extraction_id = $2 AND job_type = $3 AND filename = $4",
|
||||||
[remotejobId, extractionId, 'conllus to term candidates', '']
|
[remotejobId, extractionId, 'conllus to term candidates', '']
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -325,14 +349,21 @@ Extraction.processOwn = async function (extractionId, extractionName) {
|
|||||||
while (true) {
|
while (true) {
|
||||||
await sleep(5)
|
await sleep(5)
|
||||||
const { data: data4 } = await axios.get(
|
const { data: data4 } = await axios.get(
|
||||||
`http://rsdo.lhrs.feri.um.si:8080/job/${remotejobId}`
|
`${extractionApiOrigin}/job/${remotejobId}`
|
||||||
)
|
)
|
||||||
if (data4.finished_on) {
|
if (data4.finished_on) {
|
||||||
if (data4.job_status !== 'finished processing (OK)') throw Error()
|
const { job_result: jobResult } = data4
|
||||||
|
if (
|
||||||
|
data4.job_status !== 'finished processing (OK)' ||
|
||||||
|
!jobResult.terminoloski_kandidati
|
||||||
|
) {
|
||||||
|
throw Error(
|
||||||
|
`Remote job with id ${remotejobId} failed with result:\n${jobResult}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO Read the response as a stream and try to parse it's contents into a file (write stream)('stream-json' package?).
|
// TODO Read the response as a stream and try to parse it's contents into a file (write stream)('stream-json' package?).
|
||||||
await writeFile(termCandidatesPath, JSON.stringify(data4.job_result))
|
await writeFile(termCandidatesPath, JSON.stringify(jobResult))
|
||||||
// TODO Once returned JSON is properly formed, use the bottom line instead.
|
|
||||||
// await writeFile(termCandidatesPath, data4.job_result.terminoloski_kandidati)
|
|
||||||
await db.query(
|
await db.query(
|
||||||
"UPDATE extraction_job SET status = 'finished', time_finished = NOW() WHERE extraction_id = $1 AND job_type = $2 AND filename = $3",
|
"UPDATE extraction_job SET status = 'finished', time_finished = NOW() WHERE extraction_id = $1 AND job_type = $2 AND filename = $3",
|
||||||
[extractionId, 'conllus to term candidates', '']
|
[extractionId, 'conllus to term candidates', '']
|
||||||
@@ -340,8 +371,10 @@ Extraction.processOwn = async function (extractionId, extractionName) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch (error) {
|
||||||
|
logExtractionError(error, extractionId, 'conllus to term candidates')
|
||||||
await failTheJob(extractionId, 'conllus to term candidates', '')
|
await failTheJob(extractionId, 'conllus to term candidates', '')
|
||||||
|
await skipConcordancerJob(extractionId)
|
||||||
await failExtraction(extractionId)
|
await failExtraction(extractionId)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -350,7 +383,7 @@ Extraction.processOwn = async function (extractionId, extractionName) {
|
|||||||
// Start concondancer corpus processing.
|
// Start concondancer corpus processing.
|
||||||
try {
|
try {
|
||||||
await db.query(
|
await db.query(
|
||||||
"UPDATE extraction_job SET status = 'in progress' WHERE extraction_id = $1 AND job_type = $2 AND filename = $3",
|
"UPDATE extraction_job SET status = 'in progress', time_started = NOW() WHERE extraction_id = $1 AND job_type = $2 AND filename = $3",
|
||||||
[extractionId, 'concordancer', '']
|
[extractionId, 'concordancer', '']
|
||||||
)
|
)
|
||||||
console.log('CREATING CORPUS')
|
console.log('CREATING CORPUS')
|
||||||
@@ -361,32 +394,90 @@ Extraction.processOwn = async function (extractionId, extractionName) {
|
|||||||
} = await axios.post('http://concordancer:5000/dashboard/corpus', {
|
} = await axios.post('http://concordancer:5000/dashboard/corpus', {
|
||||||
title: extractionName
|
title: extractionName
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Wait for creation of corpus.
|
||||||
|
while (true) {
|
||||||
|
console.log('SLEEP FOR 5 SECS')
|
||||||
|
await sleep(5)
|
||||||
|
const {
|
||||||
|
data: { status }
|
||||||
|
} = await axios.get(
|
||||||
|
`http://concordancer:5000/dashboard/corpus/${corpusId}`
|
||||||
|
)
|
||||||
|
|
||||||
|
if (status === 'Creating') continue
|
||||||
|
if (status === 'Active') break
|
||||||
|
throw Error('Error creating concorcander corpus')
|
||||||
|
}
|
||||||
console.log('CORPUS CREATED')
|
console.log('CORPUS CREATED')
|
||||||
console.log('SLEEP FOR 10 SECS')
|
|
||||||
await sleep(10)
|
const inProgressStatusList = [
|
||||||
|
'Waiting',
|
||||||
|
'Importing',
|
||||||
|
'ImportingCompleted',
|
||||||
|
'Indexing',
|
||||||
|
'IndexingCompleted'
|
||||||
|
]
|
||||||
for (const conlluPath of conllusPaths) {
|
for (const conlluPath of conllusPaths) {
|
||||||
const textPathParts = conlluPath.split('/')
|
const textPathParts = conlluPath.split('/')
|
||||||
textPathParts[0] = '/data'
|
textPathParts[0] = '/data'
|
||||||
const textPath = textPathParts.join('/')
|
const textPath = textPathParts.join('/')
|
||||||
console.log('ADDING TEXT')
|
console.log('ADDING TEXT')
|
||||||
await axios.post(
|
const {
|
||||||
|
data: {
|
||||||
|
entityInfo: { id: textId }
|
||||||
|
}
|
||||||
|
} = await axios.post(
|
||||||
`http://concordancer:5000/dashboard/corpus/${corpusId}/text`,
|
`http://concordancer:5000/dashboard/corpus/${corpusId}/text`,
|
||||||
{ sourceFile: textPath }
|
{ sourceFile: textPath }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Wait for text ingestion.
|
||||||
|
while (true) {
|
||||||
|
console.log('SLEEP FOR 5 SECS')
|
||||||
|
await sleep(5)
|
||||||
|
const {
|
||||||
|
data: { status }
|
||||||
|
} = await axios.get(
|
||||||
|
`http://concordancer:5000/dashboard/corpus/${corpusId}/text/${textId}`
|
||||||
|
)
|
||||||
|
|
||||||
|
if (inProgressStatusList.includes(status)) continue
|
||||||
|
if (status === 'Active') break
|
||||||
|
throw Error('Error importing concorcander text')
|
||||||
|
}
|
||||||
console.log('TEXT ADDED')
|
console.log('TEXT ADDED')
|
||||||
console.log('SLEEP FOR 10 SECS')
|
|
||||||
await sleep(10)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const termListPathParts = termCandidatesPath.split('/')
|
const termListPathParts = termCandidatesPath.split('/')
|
||||||
termListPathParts[0] = '/data'
|
termListPathParts[0] = '/data'
|
||||||
const termListPath = termListPathParts.join('/')
|
const termListPath = termListPathParts.join('/')
|
||||||
console.log('ADDING TERMS')
|
console.log('ADDING TERMS')
|
||||||
await axios.post(
|
const {
|
||||||
|
data: {
|
||||||
|
entityInfo: { id: termListId }
|
||||||
|
}
|
||||||
|
} = await axios.post(
|
||||||
`http://concordancer:5000/dashboard/corpus/${corpusId}/termList`,
|
`http://concordancer:5000/dashboard/corpus/${corpusId}/termList`,
|
||||||
{ sourceFile: termListPath }
|
{ sourceFile: termListPath }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Wait for term list ingestion.
|
||||||
|
while (true) {
|
||||||
|
console.log('SLEEP FOR 5 SECS')
|
||||||
|
await sleep(5)
|
||||||
|
const {
|
||||||
|
data: { status }
|
||||||
|
} = await axios.get(
|
||||||
|
`http://concordancer:5000/dashboard/corpus/${corpusId}/termList/${termListId}`
|
||||||
|
)
|
||||||
|
|
||||||
|
if (inProgressStatusList.includes(status)) continue
|
||||||
|
if (status === 'Active') break
|
||||||
|
throw Error('Error importing concorcander text')
|
||||||
|
}
|
||||||
console.log('TERMS ADDED')
|
console.log('TERMS ADDED')
|
||||||
|
|
||||||
await db.query(
|
await db.query(
|
||||||
"UPDATE extraction_job SET status = 'finished', time_finished = NOW() WHERE extraction_id = $1 AND job_type = $2 AND filename = $3",
|
"UPDATE extraction_job SET status = 'finished', time_finished = NOW() WHERE extraction_id = $1 AND job_type = $2 AND filename = $3",
|
||||||
[extractionId, 'concordancer', '']
|
[extractionId, 'concordancer', '']
|
||||||
@@ -397,9 +488,8 @@ Extraction.processOwn = async function (extractionId, extractionName) {
|
|||||||
[corpusId, extractionId]
|
[corpusId, extractionId]
|
||||||
)
|
)
|
||||||
console.log('EXTRACTION SUCCESSFUL')
|
console.log('EXTRACTION SUCCESSFUL')
|
||||||
} catch (e) {
|
} catch (error) {
|
||||||
console.log('EXTRACTION ERROR')
|
logExtractionError(error, extractionId, 'concordancer')
|
||||||
console.log(e)
|
|
||||||
await failTheJob(extractionId, 'concordancer', '')
|
await failTheJob(extractionId, 'concordancer', '')
|
||||||
await failExtraction(extractionId)
|
await failExtraction(extractionId)
|
||||||
}
|
}
|
||||||
@@ -435,15 +525,17 @@ Extraction.processOss = async function (extractionId, ossParams) {
|
|||||||
...(ossParams.documentType && { vrste: ossParams.documentType }),
|
...(ossParams.documentType && { vrste: ossParams.documentType }),
|
||||||
...(ossParams.keywords && { kljucneBesede: ossParams.keywords }),
|
...(ossParams.keywords && { kljucneBesede: ossParams.keywords }),
|
||||||
...(ossParams.domainUdk && { udk: ossParams.domainUdk }),
|
...(ossParams.domainUdk && { udk: ossParams.domainUdk }),
|
||||||
...(stopTerms.length && { prepovedaneBesede: stopTerms })
|
...(stopTerms.length && { prepovedaneBesede: stopTerms }),
|
||||||
|
// TODO Enabled for all cases. Add a switch for users later.
|
||||||
|
definicije: true
|
||||||
})
|
})
|
||||||
|
|
||||||
const extractApiUrl = `http://rsdo.lhrs.feri.um.si:8080/oss/izlusciPoIskanjuAsync?${searchParams}`
|
const extractApiUrl = `${extractionApiOrigin}/oss/izlusciPoIskanjuAsync?${searchParams}`
|
||||||
try {
|
try {
|
||||||
const { data: data1 } = await axios.get(extractApiUrl)
|
const { data: data1 } = await axios.get(extractApiUrl)
|
||||||
const remotejobId = +data1.check_job_url.split('/').at(-1)
|
const remotejobId = +data1.check_job_url.split('/').at(-1)
|
||||||
await db.query(
|
await db.query(
|
||||||
"UPDATE extraction_job SET status = 'in progress', remote_job_id = $1 WHERE extraction_id = $2 AND job_type = $3 AND filename = $4",
|
"UPDATE extraction_job SET status = 'in progress', remote_job_id = $1, time_started = NOW() WHERE extraction_id = $2 AND job_type = $3 AND filename = $4",
|
||||||
[remotejobId, extractionId, 'oss term candidates', '']
|
[remotejobId, extractionId, 'oss term candidates', '']
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -451,15 +543,21 @@ Extraction.processOss = async function (extractionId, ossParams) {
|
|||||||
while (true) {
|
while (true) {
|
||||||
await sleep(5)
|
await sleep(5)
|
||||||
const { data: data2 } = await axios.get(
|
const { data: data2 } = await axios.get(
|
||||||
`http://rsdo.lhrs.feri.um.si:8080/job/${remotejobId}`
|
`${extractionApiOrigin}/job/${remotejobId}`
|
||||||
)
|
)
|
||||||
if (data2.finished_on) {
|
if (data2.finished_on) {
|
||||||
if (data2.job_status !== 'finished processing (OK)') throw Error()
|
if (
|
||||||
|
data2.job_status !== 'finished processing (OK)' ||
|
||||||
|
!Array.isArray(data2.job_result?.terminoloski_kandidati)
|
||||||
|
) {
|
||||||
|
throw Error(
|
||||||
|
`Remote job with id ${remotejobId} failed with result:\n${data2.job_result}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO Read the response as a stream and try to parse it's contents into a file (write stream)('stream-json' package?).
|
// TODO Read the response as a stream and try to parse it's contents into a file (write stream)('stream-json' package?).
|
||||||
const termCandidatesPath = getTermCandidatesPath(extractionId)
|
const termCandidatesPath = getTermCandidatesPath(extractionId)
|
||||||
await writeFile(termCandidatesPath, JSON.stringify(data2.job_result))
|
await writeFile(termCandidatesPath, JSON.stringify(data2.job_result))
|
||||||
// TODO Once returned JSON is properly formed, use the bottom line instead.
|
|
||||||
// await writeFile(termCandidatesPath, data4.job_result.terminoloski_kandidati)
|
|
||||||
await db.query(
|
await db.query(
|
||||||
"UPDATE extraction_job SET status = 'finished', time_finished = NOW() WHERE extraction_id = $1 AND job_type = $2 AND filename = $3",
|
"UPDATE extraction_job SET status = 'finished', time_finished = NOW() WHERE extraction_id = $1 AND job_type = $2 AND filename = $3",
|
||||||
[extractionId, 'oss term candidates', '']
|
[extractionId, 'oss term candidates', '']
|
||||||
@@ -474,7 +572,8 @@ Extraction.processOss = async function (extractionId, ossParams) {
|
|||||||
"UPDATE extraction SET status = 'finished', time_finished = NOW() WHERE id = $1",
|
"UPDATE extraction SET status = 'finished', time_finished = NOW() WHERE id = $1",
|
||||||
[extractionId]
|
[extractionId]
|
||||||
)
|
)
|
||||||
} catch {
|
} catch (error) {
|
||||||
|
logExtractionError(error, extractionId, 'oss term candidates')
|
||||||
await failTheJob(extractionId, 'oss term candidates', '')
|
await failTheJob(extractionId, 'oss term candidates', '')
|
||||||
await failExtraction(extractionId)
|
await failExtraction(extractionId)
|
||||||
}
|
}
|
||||||
@@ -487,6 +586,13 @@ async function failTheJob(extractionId, jobType, documentName) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function skipConcordancerJob(extractionId) {
|
||||||
|
await db.query(
|
||||||
|
"UPDATE extraction_job SET status = 'skipped' WHERE extraction_id = $1 AND job_type = $2",
|
||||||
|
[extractionId, 'concordancer']
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
async function failExtraction(extractionId) {
|
async function failExtraction(extractionId) {
|
||||||
await db.query(
|
await db.query(
|
||||||
"UPDATE extraction SET status = 'failed', time_finished = NOW() WHERE id = $1",
|
"UPDATE extraction SET status = 'failed', time_finished = NOW() WHERE id = $1",
|
||||||
@@ -498,4 +604,18 @@ function sleep(seconds) {
|
|||||||
return new Promise(resolve => setTimeout(resolve, seconds * 1000))
|
return new Promise(resolve => setTimeout(resolve, seconds * 1000))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function logExtractionError(error, extractionId, jobType, filename) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(
|
||||||
|
Error(`Failed extraction job:
|
||||||
|
extractionId: ${extractionId},
|
||||||
|
jobType: ${jobType},
|
||||||
|
filename: ${filename}`)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (error.isAxiosError) error = Error(`Axios message: ${error.message}`)
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = Extraction
|
module.exports = Extraction
|
||||||
|
|||||||
@@ -0,0 +1,486 @@
|
|||||||
|
const xmlFlow = require('xml-flow')
|
||||||
|
const xss = require('xss')
|
||||||
|
|
||||||
|
exports.transformAndAppend = async (
|
||||||
|
entries,
|
||||||
|
exportFields,
|
||||||
|
exportFile,
|
||||||
|
exportFileFormat,
|
||||||
|
dsvConfig
|
||||||
|
) => {
|
||||||
|
let transformEntry
|
||||||
|
if (exportFileFormat === 'xml') transformEntry = intoXml
|
||||||
|
else if (dsvConfig) transformEntry = intoDsv(dsvConfig)
|
||||||
|
else if (exportFileFormat === 'tbx') transformEntry = intoTbx
|
||||||
|
else throw Error('Specified export format not supported yet')
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
const transformedEntry = transformEntry(entry, exportFields)
|
||||||
|
await exportFile.write(`${transformedEntry}\n`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function intoXml(entry, exportFields) {
|
||||||
|
const entryObj = { $name: 'entry', $markup: [] }
|
||||||
|
|
||||||
|
if (entry.term) {
|
||||||
|
entryObj.$markup.push({ term: entry.term })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exportFields.domainLabels && entry.domain_labels.length) {
|
||||||
|
const domainLabelsObj = {
|
||||||
|
$name: 'domainLabels',
|
||||||
|
$markup: entry.domain_labels.map(domainLabel => {
|
||||||
|
return { domainLabel }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
entryObj.$markup.push(domainLabelsObj)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exportFields.label && entry.label) {
|
||||||
|
entryObj.$markup.push({ label: entry.label })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exportFields.definition && entry.definition) {
|
||||||
|
entryObj.$markup.push({ def: entry.definition })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exportFields.synonyms && entry.synonyms?.length) {
|
||||||
|
const SynonymsObj = {
|
||||||
|
$name: 'syns',
|
||||||
|
$markup: entry.synonyms.map(synonym => {
|
||||||
|
return { syn: synonym }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
entryObj.$markup.push(SynonymsObj)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exportFields.links && entry.links.length) {
|
||||||
|
const LinksObj = {
|
||||||
|
$name: 'links',
|
||||||
|
$markup: entry.links.map(linkObj => {
|
||||||
|
return {
|
||||||
|
$name: 'link',
|
||||||
|
$attrs: { type: linkObj.type },
|
||||||
|
$text: linkObj.link
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
entryObj.$markup.push(LinksObj)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exportFields.other && entry.other) {
|
||||||
|
entryObj.$markup.push({ other: entry.other })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exportFields.foreignTerms && entry.foreign_entries.length) {
|
||||||
|
const fLangsObj = {
|
||||||
|
$name: 'fLangs',
|
||||||
|
$markup: entry.foreign_entries.map(fEntryObj => {
|
||||||
|
const fLangObj = {
|
||||||
|
$name: 'fLang',
|
||||||
|
$attrs: { lang: fEntryObj.lang_code },
|
||||||
|
$markup: []
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fEntryObj.terms) {
|
||||||
|
const fTermsObj = {
|
||||||
|
$name: 'fTerms',
|
||||||
|
$markup: fEntryObj.terms.map(term => {
|
||||||
|
return { fTerm: term }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fLangObj.$markup.push(fTermsObj)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exportFields.foreignDefinitions && fEntryObj.definition) {
|
||||||
|
fLangObj.$markup.push({ fDef: fEntryObj.definition })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exportFields.foreignSynonyms && fEntryObj.synonyms?.length) {
|
||||||
|
const fSynsObj = {
|
||||||
|
$name: 'fSyns',
|
||||||
|
$markup: fEntryObj.synonyms.map(synonym => {
|
||||||
|
return { fSyn: synonym }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fLangObj.$markup.push(fSynsObj)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fLangObj
|
||||||
|
})
|
||||||
|
}
|
||||||
|
entryObj.$markup.push(fLangsObj)
|
||||||
|
}
|
||||||
|
|
||||||
|
const shouldExportImages = exportFields.images && entry.image?.length
|
||||||
|
const shouldExportAudio = exportFields.audio && entry.audio?.length
|
||||||
|
const shouldExportVideos = exportFields.videos && entry.video?.length
|
||||||
|
const shouldCreateMm =
|
||||||
|
shouldExportImages || shouldExportAudio || shouldExportVideos
|
||||||
|
|
||||||
|
if (shouldCreateMm) {
|
||||||
|
const MmObj = {
|
||||||
|
$name: 'mm',
|
||||||
|
$markup: []
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldExportImages) {
|
||||||
|
entry.image.forEach(el => {
|
||||||
|
MmObj.$markup.push({ image: el })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldExportAudio) {
|
||||||
|
entry.audio.forEach(el => {
|
||||||
|
MmObj.$markup.push({ audio: el })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldExportVideos) {
|
||||||
|
entry.video.forEach(el => {
|
||||||
|
MmObj.$markup.push({ video: el })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
entryObj.$markup.push(MmObj)
|
||||||
|
}
|
||||||
|
|
||||||
|
return xmlFlow.toXml(entryObj, { escape: str => str })
|
||||||
|
}
|
||||||
|
|
||||||
|
function intoDsv({ delimiter, languageCodes }) {
|
||||||
|
return function (entry, exportFields) {
|
||||||
|
const fieldsArr = [intoDsvField(entry.term)]
|
||||||
|
|
||||||
|
if (exportFields.domainLabels) {
|
||||||
|
fieldsArr.push(
|
||||||
|
intoDsvField(
|
||||||
|
entry.domain_labels.reduce((agg, domainLabel, index) => {
|
||||||
|
agg += `${index ? '\n' : ''}${domainLabel}`
|
||||||
|
return agg
|
||||||
|
}, '')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exportFields.label) fieldsArr.push(intoDsvField(entry.label))
|
||||||
|
|
||||||
|
if (exportFields.definition) fieldsArr.push(intoDsvField(entry.definition))
|
||||||
|
|
||||||
|
if (exportFields.synonyms) {
|
||||||
|
fieldsArr.push(
|
||||||
|
intoDsvField(
|
||||||
|
entry.synonyms?.reduce((agg, synonym, index) => {
|
||||||
|
agg += `${index ? '\n' : ''}${synonym}`
|
||||||
|
return agg
|
||||||
|
}, '')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exportFields.links) {
|
||||||
|
fieldsArr.push(
|
||||||
|
intoDsvField(
|
||||||
|
entry.links.reduce((agg, linkObj, index) => {
|
||||||
|
agg += `${index ? '\n' : ''}[${linkObj.type[0]}t]${linkObj.link}`
|
||||||
|
return agg
|
||||||
|
}, '')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exportFields.other) fieldsArr.push(intoDsvField(entry.other))
|
||||||
|
|
||||||
|
languageCodes?.forEach(languageCode => {
|
||||||
|
const fEntryObj = entry.foreign_entries.find(
|
||||||
|
entryObj => entryObj.lang_code === languageCode
|
||||||
|
)
|
||||||
|
|
||||||
|
fieldsArr.push(
|
||||||
|
intoDsvField(
|
||||||
|
fEntryObj?.terms?.reduce((agg, term, index) => {
|
||||||
|
agg += `${index ? '\n' : ''}${term}`
|
||||||
|
return agg
|
||||||
|
}, '')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if (exportFields.foreignDefinitions) {
|
||||||
|
fieldsArr.push(intoDsvField(fEntryObj?.definition))
|
||||||
|
}
|
||||||
|
if (exportFields.foreignSynonyms) {
|
||||||
|
fieldsArr.push(
|
||||||
|
intoDsvField(
|
||||||
|
fEntryObj?.synonyms?.reduce((agg, synonym, index) => {
|
||||||
|
agg += `${index ? '\n' : ''}${synonym}`
|
||||||
|
return agg
|
||||||
|
}, '')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (exportFields.images) {
|
||||||
|
fieldsArr.push(
|
||||||
|
intoDsvField(
|
||||||
|
entry.image?.reduce((agg, el, index) => {
|
||||||
|
agg += `${index ? '\n' : ''}${el}`
|
||||||
|
return agg
|
||||||
|
}, '')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exportFields.audio) {
|
||||||
|
fieldsArr.push(
|
||||||
|
intoDsvField(
|
||||||
|
entry.audio?.reduce((agg, el, index) => {
|
||||||
|
agg += `${index ? '\n' : ''}${el}`
|
||||||
|
return agg
|
||||||
|
}, '')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exportFields.videos) {
|
||||||
|
fieldsArr.push(
|
||||||
|
intoDsvField(
|
||||||
|
entry.video?.reduce((agg, el, index) => {
|
||||||
|
agg += `${index ? '\n' : ''}${el}`
|
||||||
|
return agg
|
||||||
|
}, '')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fieldsArr.join(delimiter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function intoDsvField(fieldString) {
|
||||||
|
if (!fieldString) return ''
|
||||||
|
return `"${fieldString.replaceAll('"', '""')}"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const tbxLinkTypeMap = {
|
||||||
|
related: 'relatedConcept',
|
||||||
|
broader: 'relatedConceptBroader',
|
||||||
|
narrow: 'relatedConceptNarrower'
|
||||||
|
}
|
||||||
|
const brTagPattern = /<br[^>]*>/
|
||||||
|
function intoTbx(entry, exportFields) {
|
||||||
|
const entryObj = { $name: 'termEntry', $markup: [] }
|
||||||
|
|
||||||
|
if (exportFields.images) {
|
||||||
|
entry.image?.forEach(el => {
|
||||||
|
entryObj.$markup.push({
|
||||||
|
$name: 'xref',
|
||||||
|
$attrs: { type: 'xGraphic' },
|
||||||
|
$text: el
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exportFields.audio) {
|
||||||
|
entry.audio?.forEach(el => {
|
||||||
|
entryObj.$markup.push({
|
||||||
|
$name: 'xref',
|
||||||
|
$attrs: { type: 'xAudio' },
|
||||||
|
$text: el
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exportFields.videos) {
|
||||||
|
entry.video?.forEach(el => {
|
||||||
|
entryObj.$markup.push({
|
||||||
|
$name: 'xref',
|
||||||
|
$attrs: { type: 'xVideo' },
|
||||||
|
$text: el
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const slLangObj = {
|
||||||
|
$name: 'langSet',
|
||||||
|
$attrs: { 'xml:lang': 'sl' },
|
||||||
|
$markup: []
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exportFields.label && entry.label) {
|
||||||
|
slLangObj.$markup.push({
|
||||||
|
$name: 'descrip',
|
||||||
|
$attrs: { type: 'explanation' },
|
||||||
|
$text: intoTbxMixed(entry.label)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exportFields.definition && entry.definition) {
|
||||||
|
slLangObj.$markup.push({
|
||||||
|
$name: 'descrip',
|
||||||
|
$attrs: { type: 'definition' },
|
||||||
|
$text: intoTbxMixed(entry.definition)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exportFields.links) {
|
||||||
|
entry.links.forEach(linkObj => {
|
||||||
|
slLangObj.$markup.push({
|
||||||
|
$name: 'descrip',
|
||||||
|
$attrs: { type: tbxLinkTypeMap[linkObj.type] },
|
||||||
|
$text: intoTbxMixed(linkObj.link)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exportFields.other && entry.other) {
|
||||||
|
const otherLines = entry.other.split(brTagPattern)
|
||||||
|
otherLines.forEach(line => {
|
||||||
|
slLangObj.$markup.push({
|
||||||
|
$name: 'descrip',
|
||||||
|
$attrs: { type: 'other' },
|
||||||
|
$text: intoTbxMixed(line)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const shouldExportTerm = entry.term
|
||||||
|
const shouldExportDomainLabels =
|
||||||
|
exportFields.domainLabels && entry.domain_labels.length
|
||||||
|
const shouldCreateTermNtig = shouldExportTerm || shouldExportDomainLabels
|
||||||
|
|
||||||
|
if (shouldCreateTermNtig) {
|
||||||
|
const termNtigObj = {
|
||||||
|
$name: 'ntig',
|
||||||
|
$markup: [{ $name: 'termGrp', $markup: [] }]
|
||||||
|
}
|
||||||
|
const termGrpMarkup = termNtigObj.$markup[0].$markup
|
||||||
|
|
||||||
|
if (shouldExportTerm) {
|
||||||
|
termGrpMarkup.push({ term: intoTbxMixed(entry.term) })
|
||||||
|
termGrpMarkup.push({
|
||||||
|
$name: 'termNote',
|
||||||
|
$attrs: { type: 'termType' },
|
||||||
|
$text: 'entryTerm'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldExportDomainLabels) {
|
||||||
|
entry.domain_labels.forEach(domainLabel => {
|
||||||
|
termGrpMarkup.push({
|
||||||
|
$name: 'termNote',
|
||||||
|
$attrs: { type: 'domain' },
|
||||||
|
$text: domainLabel
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
slLangObj.$markup.push(termNtigObj)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exportFields.synonyms) {
|
||||||
|
entry.synonyms?.forEach(synonym => {
|
||||||
|
slLangObj.$markup.push({
|
||||||
|
$name: 'ntig',
|
||||||
|
$markup: [
|
||||||
|
{
|
||||||
|
$name: 'termGrp',
|
||||||
|
$markup: [
|
||||||
|
{ term: intoTbxMixed(synonym) },
|
||||||
|
{
|
||||||
|
$name: 'termNote',
|
||||||
|
$attrs: { type: 'termType' },
|
||||||
|
$text: 'synonym'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
entryObj.$markup.push(slLangObj)
|
||||||
|
|
||||||
|
if (exportFields.foreignTerms) {
|
||||||
|
entry.foreign_entries.forEach(fEntryObj => {
|
||||||
|
const langSetObj = {
|
||||||
|
$name: 'langSet',
|
||||||
|
$attrs: { 'xml:lang': fEntryObj.lang_code },
|
||||||
|
$markup: []
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exportFields.foreignDefinitions && fEntryObj.definition) {
|
||||||
|
langSetObj.$markup.push({
|
||||||
|
$name: 'descrip',
|
||||||
|
$attrs: { type: 'definition' },
|
||||||
|
$text: intoTbxMixed(fEntryObj.definition)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fEntryObj.terms?.forEach(term => {
|
||||||
|
langSetObj.$markup.push({
|
||||||
|
$name: 'ntig',
|
||||||
|
$markup: [
|
||||||
|
{
|
||||||
|
$name: 'termGrp',
|
||||||
|
$markup: [{ term: intoTbxMixed(term) }]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
if (exportFields.foreignSynonyms) {
|
||||||
|
fEntryObj.synonyms?.forEach(synonym => {
|
||||||
|
langSetObj.$markup.push({
|
||||||
|
$name: 'ntig',
|
||||||
|
$markup: [
|
||||||
|
{
|
||||||
|
$name: 'termGrp',
|
||||||
|
$markup: [
|
||||||
|
{ term: intoTbxMixed(synonym) },
|
||||||
|
{
|
||||||
|
$name: 'termNote',
|
||||||
|
$attrs: { type: 'termType' },
|
||||||
|
$text: 'synonym'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
entryObj.$markup.push(langSetObj)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return xmlFlow.toXml(entryObj, { escape: str => str })
|
||||||
|
}
|
||||||
|
|
||||||
|
const tbxRichFilter = new xss.FilterXSS({
|
||||||
|
whiteList: {
|
||||||
|
sup: [],
|
||||||
|
sub: [],
|
||||||
|
b: [],
|
||||||
|
i: []
|
||||||
|
},
|
||||||
|
stripIgnoreTag: true,
|
||||||
|
stripIgnoreTagBody: ['script', 'style']
|
||||||
|
})
|
||||||
|
const tagPattern = /<\s*(\/)?\s*([^\s/>]+)\s*>/g
|
||||||
|
function intoTbxMixed(mixedContentStr) {
|
||||||
|
return tbxRichFilter
|
||||||
|
.process(mixedContentStr)
|
||||||
|
.replace(tagPattern, tbxMixedReplacer)
|
||||||
|
}
|
||||||
|
|
||||||
|
const tbxMixedTypeMap = {
|
||||||
|
sup: 'superscript',
|
||||||
|
sub: 'subscript',
|
||||||
|
b: 'bold',
|
||||||
|
i: 'italics'
|
||||||
|
}
|
||||||
|
function tbxMixedReplacer(match, closingSlash, tagName) {
|
||||||
|
if (closingSlash) return '</hi>'
|
||||||
|
return `<hi type="${tbxMixedTypeMap[tagName]}">`
|
||||||
|
}
|
||||||
@@ -250,7 +250,7 @@ function customTagHandler(tag, html, { isWhite, isClosing }) {
|
|||||||
const matchUrl = html.match(/href="?(?<url>https?:\/\/.*?)"?[\s>]/)
|
const matchUrl = html.match(/href="?(?<url>https?:\/\/.*?)"?[\s>]/)
|
||||||
const url = matchUrl ? xss.escapeAttrValue(matchUrl.groups.url) : undefined
|
const url = matchUrl ? xss.escapeAttrValue(matchUrl.groups.url) : undefined
|
||||||
|
|
||||||
return `<a href${url ? `="${url}" target="_blank"` : ''}>`
|
return `<a href="${url || ''}" target="_blank">`
|
||||||
}
|
}
|
||||||
|
|
||||||
function toText(markupObj) {
|
function toText(markupObj) {
|
||||||
|
|||||||
@@ -1,7 +1,22 @@
|
|||||||
const { removeHtmlTags } = require('../../helpers')
|
const { removeHtmlTags } = require('../../helpers')
|
||||||
const { searchEngineClient, ENTRY_INDEX } = require('../../search-engine')
|
const { searchEngineClient, ENTRY_INDEX } = require('../../search-engine')
|
||||||
|
const { DATA_FILES_PATH } = require('../../../config/settings')
|
||||||
|
|
||||||
exports.deserialize = {
|
exports.deserialize = {
|
||||||
|
dictionary(dictionary) {
|
||||||
|
const deserializedDictionary = {
|
||||||
|
id: dictionary.id,
|
||||||
|
nameSl: dictionary.name_sl,
|
||||||
|
timeModified: dictionary.time_modified,
|
||||||
|
status: dictionary.status,
|
||||||
|
countEntries: dictionary.count_entries,
|
||||||
|
countComments: dictionary.count_comments,
|
||||||
|
isAdmin: dictionary.is_admin
|
||||||
|
}
|
||||||
|
|
||||||
|
return deserializedDictionary
|
||||||
|
},
|
||||||
|
|
||||||
primaryDomain(domain) {
|
primaryDomain(domain) {
|
||||||
const deserializedDomain = {
|
const deserializedDomain = {
|
||||||
id: domain.id,
|
id: domain.id,
|
||||||
@@ -102,16 +117,78 @@ exports.deserialize = {
|
|||||||
return deserializedDomainLabel
|
return deserializedDomainLabel
|
||||||
},
|
},
|
||||||
|
|
||||||
imports(oneImport) {
|
// imports(oneImport) {
|
||||||
const deserializedImports = {
|
// const deserializedImports = {
|
||||||
timeStarted: oneImport.time_started,
|
// timeStarted: oneImport.time_started,
|
||||||
status: oneImport.status,
|
// status: oneImport.status,
|
||||||
deleteExisting: oneImport.delete_existing_entries,
|
// deleteExisting: oneImport.delete_existing_entries,
|
||||||
fileFormat: oneImport.file_format,
|
// fileFormat: oneImport.file_format,
|
||||||
countValidEntries: oneImport.count_valid_entries
|
// countValidEntries: oneImport.count_valid_entries
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return deserializedImports
|
||||||
|
// },
|
||||||
|
|
||||||
|
exports(oneExport) {
|
||||||
|
const deserializedExports = {
|
||||||
|
id: oneExport.id,
|
||||||
|
status: oneExport.status,
|
||||||
|
dateCreated: oneExport.date_created,
|
||||||
|
entryCount: oneExport.entry_count ?? '',
|
||||||
|
typeString: `${
|
||||||
|
oneExport.is_valid_filter === true
|
||||||
|
? 2
|
||||||
|
: oneExport.is_valid_filter === false
|
||||||
|
? 3
|
||||||
|
: 1
|
||||||
|
}${
|
||||||
|
oneExport.is_published_filter === true
|
||||||
|
? 2
|
||||||
|
: oneExport.is_published_filter === false
|
||||||
|
? 3
|
||||||
|
: 1
|
||||||
|
}${
|
||||||
|
oneExport.status_filter === 'complete'
|
||||||
|
? 2
|
||||||
|
: oneExport.status_filter === 'in_edit'
|
||||||
|
? 3
|
||||||
|
: 1
|
||||||
|
}${
|
||||||
|
oneExport.is_terminology_reviewed_filter === true
|
||||||
|
? 2
|
||||||
|
: oneExport.is_terminology_reviewed_filter === false
|
||||||
|
? 3
|
||||||
|
: 1
|
||||||
|
}${
|
||||||
|
oneExport.is_language_reviewed_filter === true
|
||||||
|
? 2
|
||||||
|
: oneExport.is_language_reviewed_filter === false
|
||||||
|
? 3
|
||||||
|
: 1
|
||||||
|
}-${
|
||||||
|
oneExport.export_file_format === 'xml'
|
||||||
|
? 1
|
||||||
|
: oneExport.export_file_format === 'csv'
|
||||||
|
? 2
|
||||||
|
: oneExport.export_file_format === 'tsv'
|
||||||
|
? 3
|
||||||
|
: 0
|
||||||
|
}`
|
||||||
}
|
}
|
||||||
|
|
||||||
return deserializedImports
|
return deserializedExports
|
||||||
|
},
|
||||||
|
|
||||||
|
exportDownloadMetadata(metadata) {
|
||||||
|
const deserializedMetadata = {
|
||||||
|
exportStatus: metadata.status,
|
||||||
|
dictionaryId: metadata.dictionary_id,
|
||||||
|
nameString: metadata.name_string,
|
||||||
|
timeString: metadata.time_string,
|
||||||
|
fileFormat: metadata.export_file_format
|
||||||
|
}
|
||||||
|
|
||||||
|
return deserializedMetadata
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,3 +271,7 @@ function prepareEntryForIndexing(entry) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
exports.prepareEntryForIndexing = prepareEntryForIndexing
|
exports.prepareEntryForIndexing = prepareEntryForIndexing
|
||||||
|
|
||||||
|
exports.getExportFilesPath = dictId => {
|
||||||
|
return `${DATA_FILES_PATH}/dict_export/${dictId}`
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
const { readdir, stat } = require('fs/promises')
|
const { readdir, stat } = require('fs/promises')
|
||||||
|
const path = require('path')
|
||||||
const { partial } = require('filesize')
|
const { partial } = require('filesize')
|
||||||
const { DATA_FILES_PATH } = require('../../config/settings')
|
const { DATA_FILES_PATH } = require('../../config/settings')
|
||||||
|
|
||||||
@@ -43,18 +44,22 @@ exports.getFileNamesInFolder = async folderPath => {
|
|||||||
return filenames
|
return filenames
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.getFileStats = getFileStats
|
||||||
|
|
||||||
exports.getFileStatsInFolder = async folderPath => {
|
exports.getFileStatsInFolder = async folderPath => {
|
||||||
const filenames = await readdir(folderPath)
|
const filenames = await readdir(folderPath)
|
||||||
const fileStats = await Promise.all(
|
const filePaths = filenames.map(filename => `${folderPath}/${filename}`)
|
||||||
filenames.map(async filename => {
|
const fileStats = await Promise.all(filePaths.map(getFileStats))
|
||||||
const filePath = `${folderPath}/${filename}`
|
fileStats.sort((a, b) => a.timeModified - b.timeModified)
|
||||||
|
return fileStats
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getFileStats(filePath) {
|
||||||
|
const filename = path.basename(filePath)
|
||||||
const { mtimeMs: timeModified, size } = await stat(filePath)
|
const { mtimeMs: timeModified, size } = await stat(filePath)
|
||||||
const sizeHumanReadable = formatFileSize(size)
|
const sizeHumanReadable = formatFileSize(size)
|
||||||
const fileStats = { filename, size: sizeHumanReadable, timeModified }
|
const fileStats = { filename, size: sizeHumanReadable, timeModified }
|
||||||
return fileStats
|
return fileStats
|
||||||
})
|
|
||||||
)
|
|
||||||
return fileStats
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getExtractionFilesPath(extractionId) {
|
function getExtractionFilesPath(extractionId) {
|
||||||
|
|||||||
@@ -9,8 +9,10 @@ exports.aggregateSettings = settings => {
|
|||||||
exports.deserialize = {
|
exports.deserialize = {
|
||||||
settings(settings) {
|
settings(settings) {
|
||||||
const deserializedSettings = {
|
const deserializedSettings = {
|
||||||
name: settings.portal_name,
|
nameSl: settings.portal_name_sl,
|
||||||
description: settings.portal_description,
|
nameEn: settings.portal_name_en,
|
||||||
|
descriptionSl: settings.portal_description_sl,
|
||||||
|
descriptionEn: settings.portal_description_en,
|
||||||
code: settings.portal_code,
|
code: settings.portal_code,
|
||||||
isExtractionEnabled: settings.is_extraction_enabled,
|
isExtractionEnabled: settings.is_extraction_enabled,
|
||||||
isDictionariesEnabled: settings.is_dictionaries_enabled,
|
isDictionariesEnabled: settings.is_dictionaries_enabled,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ module.exports = function (filters, hitsPerPage, page) {
|
|||||||
filter: []
|
filter: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sort: ['_score', 'timeCreated']
|
sort: ['_score', { timeCreated: 'desc' }]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filters.status) {
|
if (filters.status) {
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ module.exports = function (searchString, filters, hitsPerPage, page) {
|
|||||||
filter: []
|
filter: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sort: ['_score', 'timeCreated']
|
sort: ['_score', { timeCreated: 'desc' }]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filters.status) {
|
if (filters.status) {
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ module.exports = function (searchString, filters, hitsPerPage, page) {
|
|||||||
filter: []
|
filter: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sort: ['_score', 'timeCreated']
|
sort: ['_score', { timeCreated: 'desc' }]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filters.status) {
|
if (filters.status) {
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ module.exports = function (searchString, filters, hitsPerPage, page) {
|
|||||||
filter: []
|
filter: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sort: ['_score', 'timeCreated']
|
sort: ['_score', { timeCreated: 'desc' }]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filters.status) {
|
if (filters.status) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ const { EDITOR_MAX_HITS } = require('../../../../../config/settings')
|
|||||||
|
|
||||||
module.exports = function (dictionaryId, filters, searchFieldFilters) {
|
module.exports = function (dictionaryId, filters, searchFieldFilters) {
|
||||||
const queryDsl = {
|
const queryDsl = {
|
||||||
_source: ['id', 'isValid', 'isPublished', 'term'],
|
_source: ['id', 'isValid', 'isPublished', 'term', 'homonymSort'],
|
||||||
fields: ['foreignEntries.terms'],
|
fields: ['foreignEntries.terms'],
|
||||||
script_fields: {
|
script_fields: {
|
||||||
commentActivityIndicator: {
|
commentActivityIndicator: {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ exports.deserialize = {
|
|||||||
lastName: user.last_name,
|
lastName: user.last_name,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
hitsPerPage: user.hits_per_page,
|
hitsPerPage: user.hits_per_page,
|
||||||
|
language: user.language,
|
||||||
userRoles: user.user_roles,
|
userRoles: user.user_roles,
|
||||||
assignedConsultancyEntries: user.assigned_consultancy_entries
|
assignedConsultancyEntries: user.assigned_consultancy_entries
|
||||||
}
|
}
|
||||||
@@ -21,7 +22,8 @@ exports.deserialize = {
|
|||||||
firstName: userData.first_name,
|
firstName: userData.first_name,
|
||||||
lastName: userData.last_name,
|
lastName: userData.last_name,
|
||||||
email: userData.email,
|
email: userData.email,
|
||||||
password: userData.password
|
status: userData.status,
|
||||||
|
language: userData.language
|
||||||
}
|
}
|
||||||
|
|
||||||
return deserializedData
|
return deserializedData
|
||||||
|
|||||||
@@ -73,6 +73,9 @@ class InterInstanceSync {
|
|||||||
'SELECT l.code AS language_code, ef.entry_id, ef.term, ef.definition, ef.synonym FROM entry_foreign AS ef' +
|
'SELECT l.code AS language_code, ef.entry_id, ef.term, ef.definition, ef.synonym FROM entry_foreign AS ef' +
|
||||||
' INNER JOIN language AS l ON l.id = ef.language_id' +
|
' INNER JOIN language AS l ON l.id = ef.language_id' +
|
||||||
' WHERE entry_id = ANY($1::int[]) ORDER BY ef.entry_id, l.code'
|
' WHERE entry_id = ANY($1::int[]) ORDER BY ef.entry_id, l.code'
|
||||||
|
const sqlLinks =
|
||||||
|
'SELECT entry_id, link, type FROM entry_link WHERE entry_id = ANY($1::int[]) ORDER BY entry_id, type, link'
|
||||||
|
|
||||||
const { rows: entryList } = await db.query(sqlEntries, [
|
const { rows: entryList } = await db.query(sqlEntries, [
|
||||||
dictionaryId,
|
dictionaryId,
|
||||||
since
|
since
|
||||||
@@ -83,6 +86,7 @@ class InterInstanceSync {
|
|||||||
const { rows: translationList } = await db.query(sqlTranslations, [
|
const { rows: translationList } = await db.query(sqlTranslations, [
|
||||||
entryIds
|
entryIds
|
||||||
])
|
])
|
||||||
|
const { rows: linkList } = await db.query(sqlLinks, [entryIds])
|
||||||
let currentEntryId = 0
|
let currentEntryId = 0
|
||||||
let lastEntryId = 0
|
let lastEntryId = 0
|
||||||
translationList.forEach(t => {
|
translationList.forEach(t => {
|
||||||
@@ -102,6 +106,23 @@ class InterInstanceSync {
|
|||||||
}
|
}
|
||||||
entry.translations.push(t)
|
entry.translations.push(t)
|
||||||
})
|
})
|
||||||
|
linkList.forEach(l => {
|
||||||
|
currentEntryId = l.entry_id
|
||||||
|
const entry = entryList.find(e => {
|
||||||
|
return e.id === currentEntryId
|
||||||
|
})
|
||||||
|
if (!entry) return
|
||||||
|
if (lastEntryId === 0) {
|
||||||
|
entry.links = []
|
||||||
|
lastEntryId = l.entry_id
|
||||||
|
} else if (currentEntryId !== lastEntryId) {
|
||||||
|
// new entry translations : save previous
|
||||||
|
entry.links = []
|
||||||
|
lastEntryId = currentEntryId
|
||||||
|
currentEntryId = l.entry_id
|
||||||
|
}
|
||||||
|
entry.links.push({ type: l.type, link: l.link })
|
||||||
|
})
|
||||||
return entryList
|
return entryList
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,11 @@ Portal.fetchInstanceSettings = async () => {
|
|||||||
WHERE
|
WHERE
|
||||||
name
|
name
|
||||||
IN (
|
IN (
|
||||||
'portal_name',
|
'portal_name_sl',
|
||||||
|
'portal_name_en',
|
||||||
'portal_code',
|
'portal_code',
|
||||||
'portal_description',
|
'portal_description_sl',
|
||||||
|
'portal_description_en',
|
||||||
'is_consultancy_enabled',
|
'is_consultancy_enabled',
|
||||||
'is_dictionaries_enabled',
|
'is_dictionaries_enabled',
|
||||||
'is_extraction_enabled')`
|
'is_extraction_enabled')`
|
||||||
@@ -32,9 +34,11 @@ Portal.updateInstaceSettings = async payload => {
|
|||||||
const isConsultancyEnabled = payload.isConsultancyEnabled ? 'T' : 'F'
|
const isConsultancyEnabled = payload.isConsultancyEnabled ? 'T' : 'F'
|
||||||
|
|
||||||
const values = [
|
const values = [
|
||||||
payload.portalName,
|
payload.portalNameSl,
|
||||||
|
payload.portalNameEn,
|
||||||
payload.portalCode,
|
payload.portalCode,
|
||||||
payload.portalDescription,
|
payload.portalDescriptionSl,
|
||||||
|
payload.portalDescriptionEn,
|
||||||
isExtractionEnabled,
|
isExtractionEnabled,
|
||||||
isDictionariesEnabled,
|
isDictionariesEnabled,
|
||||||
isConsultancyEnabled
|
isConsultancyEnabled
|
||||||
@@ -47,17 +51,21 @@ Portal.updateInstaceSettings = async payload => {
|
|||||||
value
|
value
|
||||||
= CASE name
|
= CASE name
|
||||||
WHEN
|
WHEN
|
||||||
'portal_name' THEN $1
|
'portal_name_sl' THEN $1
|
||||||
WHEN
|
WHEN
|
||||||
'portal_code' THEN $2
|
'portal_name_en' THEN $2
|
||||||
WHEN
|
WHEN
|
||||||
'portal_description' THEN $3
|
'portal_code' THEN $3
|
||||||
WHEN
|
WHEN
|
||||||
'is_extraction_enabled' THEN $4
|
'portal_description_sl' THEN $4
|
||||||
WHEN
|
WHEN
|
||||||
'is_dictionaries_enabled' THEN $5
|
'portal_description_en' THEN $5
|
||||||
WHEN
|
WHEN
|
||||||
'is_consultancy_enabled' THEN $6
|
'is_extraction_enabled' THEN $6
|
||||||
|
WHEN
|
||||||
|
'is_dictionaries_enabled' THEN $7
|
||||||
|
WHEN
|
||||||
|
'is_consultancy_enabled' THEN $8
|
||||||
ELSE value
|
ELSE value
|
||||||
END`
|
END`
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ Eurotermbank.push = async () => {
|
|||||||
'definition', ef.definition,
|
'definition', ef.definition,
|
||||||
'synonyms', ef.synonym)
|
'synonyms', ef.synonym)
|
||||||
FROM entry_foreign ef
|
FROM entry_foreign ef
|
||||||
LEFT JOIN LANGUAGE l ON l.id = ef.language_id
|
LEFT JOIN language l ON l.id = ef.language_id
|
||||||
WHERE entry_id = e.id
|
WHERE entry_id = e.id
|
||||||
) foreign_entries
|
) foreign_entries
|
||||||
FROM entry e
|
FROM entry e
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ User.create = async user => {
|
|||||||
user.email || null,
|
user.email || null,
|
||||||
bcryptHash || null
|
bcryptHash || null
|
||||||
]
|
]
|
||||||
|
if (user.language) values.push(user.language)
|
||||||
|
|
||||||
const text = `INSERT INTO "user" (
|
const text = `INSERT INTO "user" (
|
||||||
username,
|
username,
|
||||||
@@ -24,6 +25,7 @@ User.create = async user => {
|
|||||||
last_name,
|
last_name,
|
||||||
email,
|
email,
|
||||||
bcrypt_hash
|
bcrypt_hash
|
||||||
|
${user.language ? ', language' : ''}
|
||||||
)
|
)
|
||||||
VALUES (${db.genParamStr(values)})
|
VALUES (${db.genParamStr(values)})
|
||||||
RETURNING id`
|
RETURNING id`
|
||||||
@@ -67,7 +69,10 @@ User.fetchByActivationToken = async activationToken => {
|
|||||||
|
|
||||||
// Activate user account.
|
// Activate user account.
|
||||||
User.activateAccount = async user => {
|
User.activateAccount = async user => {
|
||||||
await db.query(`UPDATE "user" SET status = 'active' WHERE id = $1`, [user.id])
|
await db.query(
|
||||||
|
`UPDATE "user" SET status = 'active', time_activated = NOW() WHERE id = $1`,
|
||||||
|
[user.id]
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate a user remember me token.
|
// Generate a user remember me token.
|
||||||
@@ -101,6 +106,7 @@ User.fetchDeserializedDataById = async userId => {
|
|||||||
u.last_name,
|
u.last_name,
|
||||||
u.email,
|
u.email,
|
||||||
u.hits_per_page,
|
u.hits_per_page,
|
||||||
|
u.language,
|
||||||
ARRAY(
|
ARRAY(
|
||||||
SELECT jsonb_build_object(
|
SELECT jsonb_build_object(
|
||||||
'roleName', r.role_name,
|
'roleName', r.role_name,
|
||||||
@@ -258,7 +264,7 @@ User.updatePortalRoles = async rolesPerUser => {
|
|||||||
|
|
||||||
User.fetchUser = async userId => {
|
User.fetchUser = async userId => {
|
||||||
const text = `
|
const text = `
|
||||||
SELECT id, username, first_name, last_name, email
|
SELECT id, username, first_name, last_name, email, status, language
|
||||||
FROM "user"
|
FROM "user"
|
||||||
WHERE id = $1`
|
WHERE id = $1`
|
||||||
const value = [userId]
|
const value = [userId]
|
||||||
@@ -271,17 +277,33 @@ User.fetchUser = async userId => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
User.updateUser = async (userId, payload) => {
|
User.updateUser = async (userId, payload) => {
|
||||||
const text = `
|
const previousStatusText = 'SELECT status FROM "user" WHERE id = $1'
|
||||||
|
const { rows } = await db.query(previousStatusText, [userId])
|
||||||
|
const previousStatus = rows[0].status
|
||||||
|
|
||||||
|
let statusValue
|
||||||
|
if (previousStatus === 'registered') {
|
||||||
|
statusValue = !payload.status ? 'registered' : 'active'
|
||||||
|
} else statusValue = !payload.status ? 'inactive' : 'active'
|
||||||
|
|
||||||
|
const updateText = `
|
||||||
UPDATE "user"
|
UPDATE "user"
|
||||||
SET
|
SET
|
||||||
username = $2,
|
username = $2,
|
||||||
first_name = $3,
|
first_name = $3,
|
||||||
last_name = $4
|
last_name = $4,
|
||||||
|
status = $5
|
||||||
WHERE id = $1`
|
WHERE id = $1`
|
||||||
|
|
||||||
const values = [userId, payload.username, payload.firstName, payload.lastName]
|
const values = [
|
||||||
|
userId,
|
||||||
|
payload.username,
|
||||||
|
payload.firstName,
|
||||||
|
payload.lastName,
|
||||||
|
statusValue
|
||||||
|
]
|
||||||
|
|
||||||
await db.query(text, values)
|
await db.query(updateText, values)
|
||||||
}
|
}
|
||||||
|
|
||||||
User.fetchUserRoles = async userId => {
|
User.fetchUserRoles = async userId => {
|
||||||
@@ -467,4 +489,12 @@ User.updateHitsPerPage = async (username, hitsPerPageAmount) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update user's language.
|
||||||
|
User.updateLanguage = async (userId, languageCode) => {
|
||||||
|
await db.query('UPDATE "user" SET language = $1 WHERE id = $2', [
|
||||||
|
languageCode,
|
||||||
|
userId
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = User
|
module.exports = User
|
||||||
|
|||||||
@@ -22,6 +22,9 @@
|
|||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
"helmet": "^5.0.2",
|
"helmet": "^5.0.2",
|
||||||
"http-errors": "^2.0.0",
|
"http-errors": "^2.0.0",
|
||||||
|
"i18next": "^22.4.10",
|
||||||
|
"i18next-fs-backend": "^2.1.1",
|
||||||
|
"i18next-http-middleware": "^3.2.2",
|
||||||
"ioredis": "^4.27.8",
|
"ioredis": "^4.27.8",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"multer": "^1.4.3",
|
"multer": "^1.4.3",
|
||||||
@@ -64,6 +67,17 @@
|
|||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/runtime": {
|
||||||
|
"version": "7.21.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz",
|
||||||
|
"integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==",
|
||||||
|
"dependencies": {
|
||||||
|
"regenerator-runtime": "^0.13.11"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/types": {
|
"node_modules/@babel/types": {
|
||||||
"version": "7.17.0",
|
"version": "7.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz",
|
||||||
@@ -1678,6 +1692,38 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/i18next": {
|
||||||
|
"version": "22.4.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/i18next/-/i18next-22.4.10.tgz",
|
||||||
|
"integrity": "sha512-3EqgGK6fAJRjnGgfkNSStl4mYLCjUoJID338yVyLMj5APT67HUtWoqSayZewiiC5elzMUB1VEUwcmSCoeQcNEA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://locize.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://locize.com/i18next.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.20.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/i18next-fs-backend": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/i18next-fs-backend/-/i18next-fs-backend-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-FTnj+UmNgT3YRml5ruRv0jMZDG7odOL/OP5PF5mOqvXud2vHrPOOs68Zdk6iqzL47cnnM0ZVkK2BAvpFeDJToA=="
|
||||||
|
},
|
||||||
|
"node_modules/i18next-http-middleware": {
|
||||||
|
"version": "3.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/i18next-http-middleware/-/i18next-http-middleware-3.2.2.tgz",
|
||||||
|
"integrity": "sha512-OW2sWnbns+PuLi77T+/ni4Mi+TNJ6Q6XNGdZicMv9FD+QfZrFrynBVqryHB/bfflszx2Zswg/kxtCildVLdhSA=="
|
||||||
|
},
|
||||||
"node_modules/iconv-lite": {
|
"node_modules/iconv-lite": {
|
||||||
"version": "0.4.24",
|
"version": "0.4.24",
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||||
@@ -3039,6 +3085,11 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/regenerator-runtime": {
|
||||||
|
"version": "0.13.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||||
|
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
|
||||||
|
},
|
||||||
"node_modules/registry-auth-token": {
|
"node_modules/registry-auth-token": {
|
||||||
"version": "4.2.1",
|
"version": "4.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz",
|
||||||
@@ -3789,6 +3840,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.8.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.8.tgz",
|
||||||
"integrity": "sha512-BoHhDJrJXqcg+ZL16Xv39H9n+AqJ4pcDrQBGZN+wHxIysrLZ3/ECwCBUch/1zUNhnsXULcONU3Ei5Hmkfk6kiQ=="
|
"integrity": "sha512-BoHhDJrJXqcg+ZL16Xv39H9n+AqJ4pcDrQBGZN+wHxIysrLZ3/ECwCBUch/1zUNhnsXULcONU3Ei5Hmkfk6kiQ=="
|
||||||
},
|
},
|
||||||
|
"@babel/runtime": {
|
||||||
|
"version": "7.21.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz",
|
||||||
|
"integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==",
|
||||||
|
"requires": {
|
||||||
|
"regenerator-runtime": "^0.13.11"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@babel/types": {
|
"@babel/types": {
|
||||||
"version": "7.17.0",
|
"version": "7.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz",
|
||||||
@@ -5032,6 +5091,24 @@
|
|||||||
"debug": "4"
|
"debug": "4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"i18next": {
|
||||||
|
"version": "22.4.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/i18next/-/i18next-22.4.10.tgz",
|
||||||
|
"integrity": "sha512-3EqgGK6fAJRjnGgfkNSStl4mYLCjUoJID338yVyLMj5APT67HUtWoqSayZewiiC5elzMUB1VEUwcmSCoeQcNEA==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.20.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"i18next-fs-backend": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/i18next-fs-backend/-/i18next-fs-backend-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-FTnj+UmNgT3YRml5ruRv0jMZDG7odOL/OP5PF5mOqvXud2vHrPOOs68Zdk6iqzL47cnnM0ZVkK2BAvpFeDJToA=="
|
||||||
|
},
|
||||||
|
"i18next-http-middleware": {
|
||||||
|
"version": "3.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/i18next-http-middleware/-/i18next-http-middleware-3.2.2.tgz",
|
||||||
|
"integrity": "sha512-OW2sWnbns+PuLi77T+/ni4Mi+TNJ6Q6XNGdZicMv9FD+QfZrFrynBVqryHB/bfflszx2Zswg/kxtCildVLdhSA=="
|
||||||
|
},
|
||||||
"iconv-lite": {
|
"iconv-lite": {
|
||||||
"version": "0.4.24",
|
"version": "0.4.24",
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||||
@@ -6100,6 +6177,11 @@
|
|||||||
"redis-errors": "^1.0.0"
|
"redis-errors": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"regenerator-runtime": {
|
||||||
|
"version": "0.13.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||||
|
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
|
||||||
|
},
|
||||||
"registry-auth-token": {
|
"registry-auth-token": {
|
||||||
"version": "4.2.1",
|
"version": "4.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz",
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node ./bin/www",
|
"start": "node ./bin/www",
|
||||||
"devstart": "nodemon --ignore data_files/ --inspect=0.0.0.0:9229 ./bin/www",
|
"devstart": "nodemon --ignore data_files/ --ignore public/locales/dev --inspect=0.0.0.0:9229 ./bin/www",
|
||||||
"devstart-wait": "nodemon --ignore data_files/ --inspect-brk=0.0.0.0:9229 ./bin/www"
|
"devstart-wait": "nodemon --ignore data_files/ --ignore public/locales/dev --inspect-brk=0.0.0.0:9229 ./bin/www"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opensearch-project/opensearch": "^2.1.0",
|
"@opensearch-project/opensearch": "^2.1.0",
|
||||||
@@ -22,6 +22,9 @@
|
|||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
"helmet": "^5.0.2",
|
"helmet": "^5.0.2",
|
||||||
"http-errors": "^2.0.0",
|
"http-errors": "^2.0.0",
|
||||||
|
"i18next": "^22.4.10",
|
||||||
|
"i18next-fs-backend": "^2.1.1",
|
||||||
|
"i18next-http-middleware": "^3.2.2",
|
||||||
"ioredis": "^4.27.8",
|
"ioredis": "^4.27.8",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"multer": "^1.4.3",
|
"multer": "^1.4.3",
|
||||||
|
|||||||
@@ -0,0 +1,285 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||||
|
<xs:element name="dictionary">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Dictionary (root element)</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="entry" maxOccurs="unbounded">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Entry</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="term" type="mixedBasic">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Slovenian Term</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="hwGrp" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Headword Group</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:attribute name="wfs" type="headwordStatus" default="no content">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Wordforms</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="acc" type="headwordStatus" default="no content">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Accent</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="pron" type="headwordStatus" default="no content">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Pronunciation</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="domainLabels" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Domain Labels</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="domainLabel" type="xs:token" maxOccurs="unbounded">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Domain Label</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="label" type="mixedExtended" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Label</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="def" type="mixedExtended" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Definition</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="syns" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Synonyms</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="syn" type="mixedBasic" maxOccurs="unbounded">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Synonym</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="links" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Links</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="link" maxOccurs="unbounded">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Link</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:complexType mixed="true">
|
||||||
|
<xs:complexContent mixed="true">
|
||||||
|
<xs:extension base="mixedBasic">
|
||||||
|
<xs:attribute name="type" type="linkType" use="required"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:complexContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="other" type="mixedOther" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Other</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="fLangs" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Foreign Languages</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="fLang" maxOccurs="unbounded">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Foreign Language</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="fTerms">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Foreign Terms</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="fTerm" type="mixedBasic" maxOccurs="unbounded">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Foreign Term</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="fDef" type="mixedExtended" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Foreign Definition</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="fSyns" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Foreign Synonims</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="fSyn" type="mixedBasic" maxOccurs="unbounded">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Foreign Synonim</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
<xs:attribute name="lang" type="xs:language" use="required"/>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="mm" minOccurs="0">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Multimedia</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:choice maxOccurs="unbounded">
|
||||||
|
<xs:element name="image" type="xs:anyURI">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Image</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="audio" type="xs:anyURI">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Audio</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="video" type="xs:anyURI">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Video</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
</xs:choice>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="sup" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Superscript</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="sub" type="xs:string">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Subscript</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="b">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Bold</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:complexType mixed="true">
|
||||||
|
<xs:choice minOccurs="0" maxOccurs="unbounded">
|
||||||
|
<xs:element ref="sup"/>
|
||||||
|
<xs:element ref="sub"/>
|
||||||
|
<xs:element ref="i"/>
|
||||||
|
<xs:element ref="a"/>
|
||||||
|
</xs:choice>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="i">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Italic</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:complexType mixed="true">
|
||||||
|
<xs:choice minOccurs="0" maxOccurs="unbounded">
|
||||||
|
<xs:element ref="sup"/>
|
||||||
|
<xs:element ref="sub"/>
|
||||||
|
<xs:element ref="b"/>
|
||||||
|
<xs:element ref="a"/>
|
||||||
|
</xs:choice>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="a">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Link</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:complexType mixed="true">
|
||||||
|
<xs:choice minOccurs="0" maxOccurs="unbounded">
|
||||||
|
<xs:element ref="sup"/>
|
||||||
|
<xs:element ref="sub"/>
|
||||||
|
<xs:element ref="b"/>
|
||||||
|
<xs:element ref="i"/>
|
||||||
|
</xs:choice>
|
||||||
|
<xs:attribute name="href" type="xs:anyURI" use="required"/>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="br">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>New Line</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:complexType/>
|
||||||
|
</xs:element>
|
||||||
|
<xs:simpleType name="headwordStatus">
|
||||||
|
<xs:restriction base="xs:string">
|
||||||
|
<xs:enumeration value="no content"/>
|
||||||
|
<xs:enumeration value="unverified"/>
|
||||||
|
<xs:enumeration value="unconfirmed"/>
|
||||||
|
<xs:enumeration value="confirmed"/>
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
<xs:simpleType name="linkType">
|
||||||
|
<xs:restriction base="xs:string">
|
||||||
|
<xs:enumeration value="related"/>
|
||||||
|
<xs:enumeration value="broader"/>
|
||||||
|
<xs:enumeration value="narrow"/>
|
||||||
|
</xs:restriction>
|
||||||
|
</xs:simpleType>
|
||||||
|
<xs:complexType name="mixedBasic" mixed="true">
|
||||||
|
<xs:choice minOccurs="0" maxOccurs="unbounded">
|
||||||
|
<xs:element ref="sup"/>
|
||||||
|
<xs:element ref="sub"/>
|
||||||
|
</xs:choice>
|
||||||
|
</xs:complexType>
|
||||||
|
<xs:complexType name="mixedExtended" mixed="true">
|
||||||
|
<xs:complexContent mixed="true">
|
||||||
|
<xs:extension base="mixedBasic">
|
||||||
|
<xs:choice minOccurs="0" maxOccurs="unbounded">
|
||||||
|
<xs:element ref="b"/>
|
||||||
|
<xs:element ref="i"/>
|
||||||
|
<xs:element ref="a"/>
|
||||||
|
</xs:choice>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:complexContent>
|
||||||
|
</xs:complexType>
|
||||||
|
<xs:complexType name="mixedOther" mixed="true">
|
||||||
|
<xs:complexContent mixed="true">
|
||||||
|
<xs:extension base="mixedExtended">
|
||||||
|
<xs:choice minOccurs="0" maxOccurs="unbounded">
|
||||||
|
<xs:element ref="br"/>
|
||||||
|
</xs:choice>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:complexContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:schema>
|
||||||
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,5 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M3.49999 22C2.99999 22 2.49999 21.9 1.99999 21.6C0.599987 20.8 0.0999871 18.9 0.899987 17.5L9.39999 3.30002C9.69999 2.90002 9.99999 2.50002 10.4 2.30002C11.1 1.90002 11.9 1.80002 12.7 2.00002C13.5 2.20002 14.1 2.70002 14.6 3.40002L23 17.5C23.3 18 23.4 18.5 23.4 19C23.4 19.8 23.1 20.6 22.5 21.1C22 21.7 21.3 22 20.5 22H3.49999ZM11.1 4.40002L2.69999 18.5C2.39999 19 2.59999 19.6 3.09999 19.9C3.19999 20 3.39999 20 3.49999 20H20.4C20.7 20 20.9 19.9 21.1 19.7C21.3 19.5 21.4 19.3 21.4 19C21.4 18.8 21.4 18.7 21.3 18.5L12.9 4.40002C12.6 3.90002 12 3.80002 11.5 4.00002C11.3 4.10002 11.2 4.20002 11.1 4.40002Z" fill="white"/>
|
||||||
|
<path d="M12 14C11.4 14 11 13.6 11 13V9.00002C11 8.40002 11.4 8.00002 12 8.00002C12.6 8.00002 13 8.40002 13 9.00002V13C13 13.6 12.6 14 12 14Z" fill="white"/>
|
||||||
|
<path d="M12 18C11.7 18 11.5 17.9 11.3 17.7C11.1 17.5 11 17.3 11 17C11 16.9 11 16.7 11.1 16.6C11.2 16.5 11.2 16.4 11.3 16.3C11.4 16.2 11.5 16.1 11.6 16.1C11.8 16 12 16 12.2 16C12.3 16 12.3 16 12.4 16.1C12.5 16.1 12.5 16.1 12.6 16.2C12.6 16.2 12.7 16.3 12.8 16.3C12.9 16.4 13 16.5 13 16.6C13 16.7 13.1 16.9 13.1 17C13.1 17.3 13 17.5 12.8 17.7C12.5 17.9 12.3 18 12 18Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,6 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.46447 15.4645C2.40215 14.5268 3.67392 14 5 14H12C13.3261 14 14.5979 14.5268 15.5355 15.4645C16.4732 16.4021 17 17.6739 17 19V21C17 21.5523 16.5523 22 16 22C15.4477 22 15 21.5523 15 21V19C15 18.2044 14.6839 17.4413 14.1213 16.8787C13.5587 16.3161 12.7956 16 12 16H5C4.20435 16 3.44129 16.3161 2.87868 16.8787C2.31607 17.4413 2 18.2044 2 19V21C2 21.5523 1.55228 22 1 22C0.447715 22 0 21.5523 0 21V19C0 17.6739 0.526784 16.4021 1.46447 15.4645Z" fill="#46535B"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.5 4C6.84315 4 5.5 5.34315 5.5 7C5.5 8.65685 6.84315 10 8.5 10C10.1569 10 11.5 8.65685 11.5 7C11.5 5.34315 10.1569 4 8.5 4ZM3.5 7C3.5 4.23858 5.73858 2 8.5 2C11.2614 2 13.5 4.23858 13.5 7C13.5 9.76142 11.2614 12 8.5 12C5.73858 12 3.5 9.76142 3.5 7Z" fill="#46535B"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.2929 7.29289C17.6834 6.90237 18.3166 6.90237 18.7071 7.29289L23.7071 12.2929C24.0976 12.6834 24.0976 13.3166 23.7071 13.7071C23.3166 14.0976 22.6834 14.0976 22.2929 13.7071L17.2929 8.70711C16.9024 8.31658 16.9024 7.68342 17.2929 7.29289Z" fill="#46535B"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.7071 7.29289C24.0976 7.68342 24.0976 8.31658 23.7071 8.70711L18.7071 13.7071C18.3166 14.0976 17.6834 14.0976 17.2929 13.7071C16.9024 13.3166 16.9024 12.6834 17.2929 12.2929L22.2929 7.29289C22.6834 6.90237 23.3166 6.90237 23.7071 7.29289Z" fill="#46535B"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -1,7 +1,9 @@
|
|||||||
/* global $, axios, bootstrap, currentPagePath, initPagination, removeAllChildNodes, unsavedData */
|
/* global $, axios, bootstrap, currentPagePath, initPagination, removeAllChildNodes, unsavedData, replaceContainer, i18next */
|
||||||
|
|
||||||
// const currentPagePath = location.pathname
|
// const currentPagePath = location.pathname
|
||||||
|
|
||||||
|
let queryBattery = ''
|
||||||
|
|
||||||
window.addEventListener('load', () => {
|
window.addEventListener('load', () => {
|
||||||
initAdmin()
|
initAdmin()
|
||||||
})
|
})
|
||||||
@@ -62,11 +64,11 @@ function initAdmin() {
|
|||||||
renderResults(results)
|
renderResults(results)
|
||||||
updatePager(page, numberOfAllPages)
|
updatePager(page, numberOfAllPages)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
let message = 'Prišlo je do napake.'
|
let message = i18next.t('Prišlo je do napake.')
|
||||||
if (error.response?.data) {
|
if (error.response?.data) {
|
||||||
message = error.response.data
|
message = error.response.data
|
||||||
} else if (error.request) {
|
} else if (error.request) {
|
||||||
message = 'Strežnik ni dosegljiv. Poskusite kasneje.'
|
message = i18next.t('Strežnik ni dosegljiv. Poskusite kasneje.')
|
||||||
}
|
}
|
||||||
alert(message)
|
alert(message)
|
||||||
updatePager()
|
updatePager()
|
||||||
@@ -130,11 +132,11 @@ function initAdmin() {
|
|||||||
ifUserExists(data, type)
|
ifUserExists(data, type)
|
||||||
event.target.reset()
|
event.target.reset()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
let message = 'Prišlo je do napake.'
|
let message = i18next.t('Prišlo je do napake.')
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
message = error.response.data
|
message = error.response.data
|
||||||
} else if (error.request) {
|
} else if (error.request) {
|
||||||
message = 'Strežnik ni dosegljiv. Poskusite kasneje.'
|
message = i18next.t('Strežnik ni dosegljiv. Poskusite kasneje.')
|
||||||
}
|
}
|
||||||
console.log(message)
|
console.log(message)
|
||||||
}
|
}
|
||||||
@@ -155,11 +157,11 @@ function initAdmin() {
|
|||||||
renderResults(results)
|
renderResults(results)
|
||||||
updatePager(page, numberOfAllPages)
|
updatePager(page, numberOfAllPages)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
let message = 'Prišlo je do napake.'
|
let message = i18next.t('Prišlo je do napake.')
|
||||||
if (error.response?.data) {
|
if (error.response?.data) {
|
||||||
message = error.response.data
|
message = error.response.data
|
||||||
} else if (error.request) {
|
} else if (error.request) {
|
||||||
message = 'Strežnik ni dosegljiv. Poskusite kasneje.'
|
message = i18next.t('Strežnik ni dosegljiv. Poskusite kasneje.')
|
||||||
}
|
}
|
||||||
alert(message)
|
alert(message)
|
||||||
updatePager()
|
updatePager()
|
||||||
@@ -189,9 +191,9 @@ function initAdmin() {
|
|||||||
aEl.type = 'link'
|
aEl.type = 'link'
|
||||||
aEl.href = `/admin/uporabniki/${result.id}/urejanje`
|
aEl.href = `/admin/uporabniki/${result.id}/urejanje`
|
||||||
imgEl.src = '/images/u_edit-alt.svg'
|
imgEl.src = '/images/u_edit-alt.svg'
|
||||||
imgEl.alt = 'Uredi'
|
imgEl.alt = i18next.t('Uredi')
|
||||||
spanEl.className = 'normal-gray ms-1'
|
spanEl.className = 'normal-gray ms-1'
|
||||||
spanEl.textContent = 'Uredi'
|
spanEl.textContent = i18next.t('Uredi')
|
||||||
td4.append(aEl)
|
td4.append(aEl)
|
||||||
aEl.append(imgEl, spanEl)
|
aEl.append(imgEl, spanEl)
|
||||||
rowEl.append(td1, td2, td3, td4)
|
rowEl.append(td1, td2, td3, td4)
|
||||||
@@ -214,11 +216,11 @@ function initAdmin() {
|
|||||||
renderResults(results)
|
renderResults(results)
|
||||||
updatePager(page, numberOfAllPages)
|
updatePager(page, numberOfAllPages)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
let message = 'Prišlo je do napake.'
|
let message = i18next.t('Prišlo je do napake.')
|
||||||
if (error.response?.data) {
|
if (error.response?.data) {
|
||||||
message = error.response.data
|
message = error.response.data
|
||||||
} else if (error.request) {
|
} else if (error.request) {
|
||||||
message = 'Strežnik ni dosegljiv. Poskusite kasneje.'
|
message = i18next.t('Strežnik ni dosegljiv. Poskusite kasneje.')
|
||||||
}
|
}
|
||||||
alert(message)
|
alert(message)
|
||||||
updatePager()
|
updatePager()
|
||||||
@@ -267,9 +269,9 @@ function initAdmin() {
|
|||||||
aEl.type = 'link'
|
aEl.type = 'link'
|
||||||
aEl.href = `/admin/slovarji/${result.id}/podatki`
|
aEl.href = `/admin/slovarji/${result.id}/podatki`
|
||||||
imgEl.src = '/images/u_edit-alt.svg'
|
imgEl.src = '/images/u_edit-alt.svg'
|
||||||
imgEl.alt = 'Uredi'
|
imgEl.alt = i18next.t('Uredi')
|
||||||
spanEl.className = 'normal-gray ms-1'
|
spanEl.className = 'normal-gray ms-1'
|
||||||
spanEl.textContent = 'Uredi'
|
spanEl.textContent = i18next.t('Uredi')
|
||||||
td6.append(aEl)
|
td6.append(aEl)
|
||||||
aEl.append(imgEl, spanEl)
|
aEl.append(imgEl, spanEl)
|
||||||
rowEl.append(td1, td2, td3, td4, td5, td6)
|
rowEl.append(td1, td2, td3, td4, td5, td6)
|
||||||
@@ -343,18 +345,20 @@ function initAdmin() {
|
|||||||
|
|
||||||
async function onPageChange(newPage) {
|
async function onPageChange(newPage) {
|
||||||
try {
|
try {
|
||||||
const { page, numberOfAllPages, results } = await getDataForPage(
|
const results = await getDataForPage(newPage)
|
||||||
newPage
|
|
||||||
)
|
const numberOfAllPages = +results.headers['number-of-all-pages']
|
||||||
|
const page = +results.headers.page
|
||||||
|
|
||||||
removeAllChildNodes(resultsListEl)
|
removeAllChildNodes(resultsListEl)
|
||||||
renderResults(results)
|
renderResults(results.data)
|
||||||
updatePager(page, numberOfAllPages)
|
updatePager(page, numberOfAllPages)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
let message = 'Prišlo je do napake.'
|
let message = i18next.t('Prišlo je do napake.')
|
||||||
if (error.response?.data) {
|
if (error.response?.data) {
|
||||||
message = error.response.data
|
message = error.response.data
|
||||||
} else if (error.request) {
|
} else if (error.request) {
|
||||||
message = 'Strežnik ni dosegljiv. Poskusite kasneje.'
|
message = i18next.t('Strežnik ni dosegljiv. Poskusite kasneje.')
|
||||||
}
|
}
|
||||||
alert(message)
|
alert(message)
|
||||||
updatePager()
|
updatePager()
|
||||||
@@ -362,56 +366,46 @@ function initAdmin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getDataForPage(page) {
|
async function getDataForPage(page) {
|
||||||
const url = `/api/v1/dictionaries/listSecondaryDomains?p=${page}`
|
const url = `/api/v1/dictionaries/secondaryDomains?q=${queryBattery}&p=${page}`
|
||||||
const { data } = await axios.get(url)
|
return await axios.get(url)
|
||||||
return data
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderResults(results) {
|
function renderResults(results) {
|
||||||
results.forEach(result => {
|
replaceContainer('page-results', results)
|
||||||
const rowEl = document.createElement('tr')
|
}
|
||||||
const input = document.createElement('input')
|
|
||||||
const th = document.createElement('th')
|
|
||||||
const input2 = document.createElement('input')
|
|
||||||
const td1 = document.createElement('td')
|
|
||||||
const tdTrans = document.createElement('td')
|
|
||||||
const td2 = document.createElement('td')
|
|
||||||
const div = document.createElement('div')
|
|
||||||
const editBtn = document.createElement('button')
|
|
||||||
const deleteBtn = document.createElement('button')
|
|
||||||
const imgEditEl = document.createElement('img')
|
|
||||||
const imgDelEl = document.createElement('img')
|
|
||||||
input.value = result.id
|
|
||||||
input.type = 'hidden'
|
|
||||||
input.name = 'domainLabelId'
|
|
||||||
th.scope = 'row'
|
|
||||||
input2.className = 'form-check checkbox-table'
|
|
||||||
input2.type = 'checkbox'
|
|
||||||
input2.name = 'isVisible'
|
|
||||||
input2.disabled = true
|
|
||||||
input2.checked = !!result.isApproved
|
|
||||||
th.append(input2)
|
|
||||||
td1.className = 'tdata-area'
|
|
||||||
td1.textContent = result.nameSl
|
|
||||||
tdTrans.className = 'tdata-translation'
|
|
||||||
tdTrans.textContent = result.nameEn
|
|
||||||
td2.classList.add('buttons-group')
|
|
||||||
div.classList.add('table-buttons')
|
|
||||||
editBtn.className = 'p-0 table-button-grp me-3 edit-row-btn'
|
|
||||||
editBtn.type = 'button'
|
|
||||||
imgEditEl.src = '/images/u_edit-alt.svg'
|
|
||||||
deleteBtn.className = 'p-0 table-button-grp delete-row-btn'
|
|
||||||
deleteBtn.dataset.bsTarget = '#alert-modal'
|
|
||||||
deleteBtn.dataset.bsToggle = 'modal'
|
|
||||||
deleteBtn.type = 'button'
|
|
||||||
imgDelEl.src = '/images/red-trash-icon.svg'
|
|
||||||
td2.append(div)
|
|
||||||
div.append(editBtn, deleteBtn)
|
|
||||||
editBtn.append(imgEditEl)
|
|
||||||
deleteBtn.append(imgDelEl)
|
|
||||||
|
|
||||||
rowEl.append(input, th, td1, tdTrans, td2)
|
const updatePaginationOnFilter = axiosResult => {
|
||||||
resultsListEl.appendChild(rowEl)
|
const numberOfAllPages = +axiosResult.headers['number-of-all-pages']
|
||||||
|
const page = +axiosResult.headers.page
|
||||||
|
// removeAllChildNodes(resultsListEl)
|
||||||
|
// renderResults(results.data)
|
||||||
|
// console.log(numberOfAllPages)
|
||||||
|
// console.log(page)
|
||||||
|
// console.log(updatePager)
|
||||||
|
updatePager(page, numberOfAllPages)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// / Due to unsual design, the function was moved inside
|
||||||
|
function searchController() {
|
||||||
|
// if (window.location.pathname.includes('podrocne-oznake')) { // No if required since already checkek above
|
||||||
|
queryBattery = document.getElementById('input-search').value
|
||||||
|
// console.log(queryBattery)
|
||||||
|
axios
|
||||||
|
.get(`/api/v1/dictionaries/secondaryDomains?q=${queryBattery}`)
|
||||||
|
.then(result => {
|
||||||
|
updatePaginationOnFilter(result)
|
||||||
|
replaceContainer('page-results', result.data)
|
||||||
|
})
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
const inlineSearchButton = document.getElementById('inline-search-btn')
|
||||||
|
|
||||||
|
if (inlineSearchButton) {
|
||||||
|
inlineSearchButton.addEventListener('click', searchController)
|
||||||
|
$('#input-search').on('keyup', function (e) {
|
||||||
|
if (e.code === 'Enter' || e.code === 'NumpadEnter') {
|
||||||
|
searchController()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -450,7 +444,7 @@ function initAdmin() {
|
|||||||
ifUserExists(data, type)
|
ifUserExists(data, type)
|
||||||
event.target.reset()
|
event.target.reset()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = 'Prišlo je do napake.'
|
const message = i18next.t('Prišlo je do napake.')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -460,17 +454,6 @@ function initAdmin() {
|
|||||||
formEditUser.addEventListener('input', enableButton)
|
formEditUser.addEventListener('input', enableButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (/\/slovarji\/\d+\/struktura/.test(currentPagePath)) {
|
|
||||||
changePreview()
|
|
||||||
const switchForm = document.querySelector('.switch-forms')
|
|
||||||
switchForm.addEventListener('click', () => changePreview())
|
|
||||||
const wholeForm = document.getElementById('form-dictionary-structure')
|
|
||||||
wholeForm.addEventListener('input', enableButton)
|
|
||||||
const dictSideMenu = document.querySelector('.admin-nav-content')
|
|
||||||
unsavedData(wholeForm, dictSideMenu)
|
|
||||||
$('#languages-input').on('change', enableButton)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentPagePath === '/admin/nastavitve/portal') {
|
if (currentPagePath === '/admin/nastavitve/portal') {
|
||||||
const formAdminPortal = document.getElementById('admin-portal-settings')
|
const formAdminPortal = document.getElementById('admin-portal-settings')
|
||||||
formAdminPortal.addEventListener('input', enableButton)
|
formAdminPortal.addEventListener('input', enableButton)
|
||||||
@@ -537,7 +520,7 @@ function createNewAuthorInput(pageForm) {
|
|||||||
divAuthor.className = 'author mt-sm-4 added-field'
|
divAuthor.className = 'author mt-sm-4 added-field'
|
||||||
divSubjectName.className = 'subject-name'
|
divSubjectName.className = 'subject-name'
|
||||||
spanName.className = 'smaller-gray-uppercase'
|
spanName.className = 'smaller-gray-uppercase'
|
||||||
spanName.textContent = 'AVTOR'
|
spanName.textContent = i18next.t('AVTOR')
|
||||||
divRow.className = 'row align-items-center'
|
divRow.className = 'row align-items-center'
|
||||||
divColSm5.className = 'col-sm-6'
|
divColSm5.className = 'col-sm-6'
|
||||||
inputGroup.className = 'input-group'
|
inputGroup.className = 'input-group'
|
||||||
@@ -553,7 +536,7 @@ function createNewAuthorInput(pageForm) {
|
|||||||
divColSm.className = 'col-sm'
|
divColSm.className = 'col-sm'
|
||||||
spanNameInfoTxt.className =
|
spanNameInfoTxt.className =
|
||||||
'd-md-inline d-block name-info-txt ms-xxl-3 ms-md-3 mt-3 mt-ms-0'
|
'd-md-inline d-block name-info-txt ms-xxl-3 ms-md-3 mt-3 mt-ms-0'
|
||||||
spanNameInfoTxt.textContent = 'Dodaten avtor.'
|
spanNameInfoTxt.textContent = i18next.t('Dodaten avtor.')
|
||||||
|
|
||||||
divAuthor.appendChild(divSubjectName)
|
divAuthor.appendChild(divSubjectName)
|
||||||
divSubjectName.appendChild(spanName)
|
divSubjectName.appendChild(spanName)
|
||||||
@@ -594,9 +577,9 @@ function createNewAreaInput(pageForm) {
|
|||||||
divSmallNameArea.className = 'author mt-4 added-field'
|
divSmallNameArea.className = 'author mt-4 added-field'
|
||||||
divSubjectName.className = 'subject-name'
|
divSubjectName.className = 'subject-name'
|
||||||
spanInputNameTxtSlo.className = 'smaller-gray-uppercase'
|
spanInputNameTxtSlo.className = 'smaller-gray-uppercase'
|
||||||
spanInputNameTxtSlo.textContent = 'NOVO PODPODROČJE (slovensko)'
|
spanInputNameTxtSlo.textContent = i18next.t('NOVO PODPODROČJE (slovensko)')
|
||||||
spanInputNameTxtEng.className = 'smaller-gray-uppercase mt-4'
|
spanInputNameTxtEng.className = 'smaller-gray-uppercase mt-4'
|
||||||
spanInputNameTxtEng.textContent = 'NOVO PODPODROČJE (angleško)'
|
spanInputNameTxtEng.textContent = i18next.t('NOVO PODPODROČJE (angleško)')
|
||||||
divRow.className = 'row align-items-center'
|
divRow.className = 'row align-items-center'
|
||||||
divRow2.className = 'row align-items-center'
|
divRow2.className = 'row align-items-center'
|
||||||
divEnglishInput.className = 'mt-4'
|
divEnglishInput.className = 'mt-4'
|
||||||
@@ -617,11 +600,12 @@ function createNewAreaInput(pageForm) {
|
|||||||
divColSm.className = 'col-sm mt-3 mt-sm-0'
|
divColSm.className = 'col-sm mt-3 mt-sm-0'
|
||||||
divColSm2.className = 'col-sm mt-3 mt-sm-0'
|
divColSm2.className = 'col-sm mt-3 mt-sm-0'
|
||||||
spanNameInfoTxt.className = 'd-sm-inline name-info-txt ms-xxl-3 ms-md-3'
|
spanNameInfoTxt.className = 'd-sm-inline name-info-txt ms-xxl-3 ms-md-3'
|
||||||
spanNameInfoTxt.textContent =
|
spanNameInfoTxt.textContent = i18next.t(
|
||||||
'Vpišite novo podpodročje. Na seznamu podpodročij bo vidno takoj po potrditvi administratorja portala.'
|
'Vpišite novo podpodročje. Na seznamu podpodročij bo vidno takoj po potrditvi administratorja portala.'
|
||||||
|
)
|
||||||
spanNameInfoTxtEng.className =
|
spanNameInfoTxtEng.className =
|
||||||
'd-sm-inline name-info-txt ms-xxl-3 ms-md-3 mt-4'
|
'd-sm-inline name-info-txt ms-xxl-3 ms-md-3 mt-4'
|
||||||
spanNameInfoTxtEng.textContent = 'Novo podpodročje (angleško).'
|
spanNameInfoTxtEng.textContent = i18next.t('Novo podpodročje (angleško).')
|
||||||
|
|
||||||
divSmallNameArea.appendChild(divSubjectName)
|
divSmallNameArea.appendChild(divSubjectName)
|
||||||
divSubjectName.appendChild(spanInputNameTxtSlo)
|
divSubjectName.appendChild(spanInputNameTxtSlo)
|
||||||
@@ -782,7 +766,7 @@ function handleAreasClick({ target }) {
|
|||||||
const newButtonGroup = document.createElement('div')
|
const newButtonGroup = document.createElement('div')
|
||||||
const cancelButton = document.createElement('button')
|
const cancelButton = document.createElement('button')
|
||||||
const saveButton = document.createElement('button')
|
const saveButton = document.createElement('button')
|
||||||
cancelButton.textContent = 'Prekliči'
|
cancelButton.textContent = i18next.t('Prekliči')
|
||||||
cancelButton.type = 'button'
|
cancelButton.type = 'button'
|
||||||
cancelButton.className = 'btn btn-secondary me-2'
|
cancelButton.className = 'btn btn-secondary me-2'
|
||||||
cancelButton.style.height = '33px'
|
cancelButton.style.height = '33px'
|
||||||
@@ -790,7 +774,7 @@ function handleAreasClick({ target }) {
|
|||||||
cancelButton.addEventListener('click', () =>
|
cancelButton.addEventListener('click', () =>
|
||||||
abortEditing(newButtonGroup, tableButtons, tDataArea, tDataTranslation)
|
abortEditing(newButtonGroup, tableButtons, tDataArea, tDataTranslation)
|
||||||
)
|
)
|
||||||
saveButton.textContent = 'POTRDI'
|
saveButton.textContent = i18next.t('POTRDI')
|
||||||
saveButton.type = 'button'
|
saveButton.type = 'button'
|
||||||
saveButton.className = 'btn btn-primary'
|
saveButton.className = 'btn btn-primary'
|
||||||
saveButton.style.height = '33px'
|
saveButton.style.height = '33px'
|
||||||
@@ -986,76 +970,12 @@ function mobileMoveContent() {
|
|||||||
currentPagePath.includes('slovarji') &&
|
currentPagePath.includes('slovarji') &&
|
||||||
!currentPagePath.includes('admin')
|
!currentPagePath.includes('admin')
|
||||||
)
|
)
|
||||||
navTitle.textContent = 'Urejanje'
|
navTitle.textContent = i18next.t('Urejanje')
|
||||||
else navTitle.textContent = 'Administrator'
|
else navTitle.textContent = i18next.t('Administracija')
|
||||||
siteHeading.style.display = 'block'
|
siteHeading.style.display = 'block'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function changePreview() {
|
|
||||||
const switchDomainLabel = document.getElementById('domain-labels')
|
|
||||||
const switchLabel = document.getElementById('label-checkbox')
|
|
||||||
const switchDefinition = document.getElementById('definition-check-box')
|
|
||||||
const switchSynonym = document.getElementById('synonyms')
|
|
||||||
const switchLink = document.getElementById('links')
|
|
||||||
const switchForeignLang = document.getElementById('language-group')
|
|
||||||
const switchForeignTerm = document.getElementById('termin-language-subgroup')
|
|
||||||
const switchForeignDef = document.getElementById(
|
|
||||||
'definition-language-subgroup'
|
|
||||||
)
|
|
||||||
const switchForeignSyn = document.getElementById('synonym-language-subgroup')
|
|
||||||
const switchImage = document.getElementById('images')
|
|
||||||
const switchAudio = document.getElementById('audio')
|
|
||||||
const switchVideo = document.getElementById('video')
|
|
||||||
|
|
||||||
const previewDomainSecondary = document.querySelector(
|
|
||||||
'.preview-domain-secondary'
|
|
||||||
)
|
|
||||||
const selectedLabel = document.querySelector('.preview-label')
|
|
||||||
const selectedDef = document.querySelector('.preview-definition')
|
|
||||||
const selectedSynonyms = document.querySelector('.preview-synonym')
|
|
||||||
const linkedTerms = document.getElementById('linked-terms')
|
|
||||||
const foreignLanguages = document.querySelector(
|
|
||||||
'.preview-languages-container'
|
|
||||||
)
|
|
||||||
const listForeignTerm = document.querySelector('.preview-f-term')
|
|
||||||
const listForeignDefinitions = document.querySelector(
|
|
||||||
'.preview-foreign-definition'
|
|
||||||
)
|
|
||||||
const foreignSynonyms = document.querySelector('.preview-foreign-synonyms')
|
|
||||||
const selectedImages = document.querySelector('.preview-images')
|
|
||||||
const selectedAudio = document.querySelector('.preview-audio')
|
|
||||||
const selectedVideo = document.querySelector('.preview-video')
|
|
||||||
|
|
||||||
if (!switchDomainLabel.checked)
|
|
||||||
previewDomainSecondary.classList.add('hide-preview')
|
|
||||||
else previewDomainSecondary.classList.remove('hide-preview')
|
|
||||||
if (!switchLabel.checked) selectedLabel.classList.add('hide-preview')
|
|
||||||
else selectedLabel.classList.remove('hide-preview')
|
|
||||||
if (!switchDefinition.checked) selectedDef.classList.add('hide-preview')
|
|
||||||
else selectedDef.classList.remove('hide-preview')
|
|
||||||
if (!switchSynonym.checked) selectedSynonyms.classList.add('hide-preview')
|
|
||||||
else selectedSynonyms.classList.remove('hide-preview')
|
|
||||||
if (!switchLink.checked) linkedTerms.classList.add('hide-preview')
|
|
||||||
else linkedTerms.classList.remove('hide-preview')
|
|
||||||
if (!switchForeignLang.checked) foreignLanguages.classList.add('hide-preview')
|
|
||||||
else foreignLanguages.classList.remove('hide-preview')
|
|
||||||
if (!switchForeignSyn.checked) foreignSynonyms.classList.add('hide-preview')
|
|
||||||
else foreignSynonyms.classList.remove('hide-preview')
|
|
||||||
if (!switchForeignTerm.checked) listForeignTerm.classList.add('hide-preview')
|
|
||||||
else listForeignTerm.classList.remove('hide-preview')
|
|
||||||
if (!switchForeignDef.checked)
|
|
||||||
listForeignDefinitions.classList.add('hide-preview')
|
|
||||||
else listForeignDefinitions.classList.remove('hide-preview')
|
|
||||||
|
|
||||||
if (!switchImage.checked) selectedImages.classList.add('hide-preview')
|
|
||||||
else selectedImages.classList.remove('hide-preview')
|
|
||||||
if (!switchAudio.checked) selectedAudio.classList.add('hide-preview')
|
|
||||||
else selectedAudio.classList.remove('hide-preview')
|
|
||||||
if (!switchVideo.checked) selectedVideo.classList.add('hide-preview')
|
|
||||||
else selectedVideo.classList.remove('hide-preview')
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkUserRightsCb(el) {
|
function checkUserRightsCb(el) {
|
||||||
const { terminologyReviewCb, languageReviewCb } = window.adminElements
|
const { terminologyReviewCb, languageReviewCb } = window.adminElements
|
||||||
const termRevCbs = document.querySelectorAll('.terminology-review-cb')
|
const termRevCbs = document.querySelectorAll('.terminology-review-cb')
|
||||||
@@ -1109,12 +1029,18 @@ function ifUserExists(data, type) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (bool === true && data[0] !== undefined) {
|
if (bool === true && data[0] !== undefined) {
|
||||||
modalText.textContent = `Uporabnik ${data[0].username} je že v tabeli.`
|
// modalText.textContent = `Uporabnik ${data[0].username} je že v tabeli.`
|
||||||
|
modalText.textContent =
|
||||||
|
i18next.t('Uporabnik') +
|
||||||
|
`${data[0].username}` +
|
||||||
|
i18next.t('je že v tabeli.')
|
||||||
modalEl.toggle()
|
modalEl.toggle()
|
||||||
} else createNewUserArea(data, type)
|
} else createNewUserArea(data, type)
|
||||||
} else createNewUserArea(data, type)
|
} else createNewUserArea(data, type)
|
||||||
} else {
|
} else {
|
||||||
modalText.textContent = `Preverite, če ste pravilno vpisali uporabniško ime. Bodite pozorni na velike in male črke.`
|
modalText.textContent = i18next.t(
|
||||||
|
'Preverite, če ste pravilno vpisali uporabniško ime. Bodite pozorni na velike in male črke.'
|
||||||
|
)
|
||||||
modalEl.toggle()
|
modalEl.toggle()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1172,24 +1098,24 @@ function createNewUserArea(data, type) {
|
|||||||
inputConsultAdmin.type = 'checkbox'
|
inputConsultAdmin.type = 'checkbox'
|
||||||
|
|
||||||
if (type === 'portal') {
|
if (type === 'portal') {
|
||||||
tdPortAdminOrAdmin.dataset.label = 'Skrbnik portala'
|
tdPortAdminOrAdmin.dataset.label = i18next.t('Skrbnik portala')
|
||||||
inputPortAdminOrAdmin.name = `rolesPerUser['${data[0].id}'][isPortalAdmin]`
|
inputPortAdminOrAdmin.name = `rolesPerUser['${data[0].id}'][isPortalAdmin]`
|
||||||
tdDictAdmin.dataset.label = 'Skrbnik slovarjev'
|
tdDictAdmin.dataset.label = i18next.t('Skrbnik slovarjev')
|
||||||
inputDictAdmin.name = `rolesPerUser['${data[0].id}'][isDictionariesAdmin]`
|
inputDictAdmin.name = `rolesPerUser['${data[0].id}'][isDictionariesAdmin]`
|
||||||
tdConsultAdmin.dataset.label = 'Skrbnik svetovalnice'
|
tdConsultAdmin.dataset.label = i18next.t('Skrbnik svetovalnice')
|
||||||
inputConsultAdmin.name = `rolesPerUser['${data[0].id}'][isConsultancyAdmin]`
|
inputConsultAdmin.name = `rolesPerUser['${data[0].id}'][isConsultancyAdmin]`
|
||||||
} else {
|
} else {
|
||||||
tdPortAdminOrAdmin.dataset.label = 'Administrator'
|
tdPortAdminOrAdmin.dataset.label = i18next.t('Administrator')
|
||||||
inputPortAdminOrAdmin.classList.add('administration-cb')
|
inputPortAdminOrAdmin.classList.add('administration-cb')
|
||||||
inputPortAdminOrAdmin.name = `rightsPerUser['${data[0].id}'][isAdministration]`
|
inputPortAdminOrAdmin.name = `rightsPerUser['${data[0].id}'][isAdministration]`
|
||||||
tdDictAdmin.dataset.label = 'Urejanje'
|
tdDictAdmin.dataset.label = i18next.t('Urejanje')
|
||||||
inputDictAdmin.name = `rightsPerUser['${data[0].id}'][isEditing]`
|
inputDictAdmin.name = `rightsPerUser['${data[0].id}'][isEditing]`
|
||||||
inputDictAdmin.classList.add('edit-cb')
|
inputDictAdmin.classList.add('edit-cb')
|
||||||
tdConsultAdmin.dataset.label = 'Strokovni pregled'
|
tdConsultAdmin.dataset.label = i18next.t('Strokovni pregled')
|
||||||
inputConsultAdmin.classList.add('terminology-review-cb')
|
inputConsultAdmin.classList.add('terminology-review-cb')
|
||||||
inputConsultAdmin.name = `rightsPerUser['${data[0].id}'][isTerminologyReview]`
|
inputConsultAdmin.name = `rightsPerUser['${data[0].id}'][isTerminologyReview]`
|
||||||
tdLanguageRev.className = 'pt-1 pb-1'
|
tdLanguageRev.className = 'pt-1 pb-1'
|
||||||
tdLanguageRev.dataset.label = 'Jezikovni pregled'
|
tdLanguageRev.dataset.label = i18next.t('Jezikovni pregled')
|
||||||
divLanguageRev.className =
|
divLanguageRev.className =
|
||||||
'form-check d-flex justify-content-left justify-content-xl-center'
|
'form-check d-flex justify-content-left justify-content-xl-center'
|
||||||
inputLanguageRev.className = 'language-review-cb form-check-input'
|
inputLanguageRev.className = 'language-review-cb form-check-input'
|
||||||
@@ -1235,7 +1161,7 @@ function createNewUserArea(data, type) {
|
|||||||
// Summernote
|
// Summernote
|
||||||
|
|
||||||
$('.summernote').summernote({
|
$('.summernote').summernote({
|
||||||
placeholder: 'Na kratko opišite zasnovo in namen slovarja.',
|
placeholder: i18next.t('Na kratko opišite zasnovo in namen slovarja.'),
|
||||||
height: 300,
|
height: 300,
|
||||||
minheight: 150,
|
minheight: 150,
|
||||||
toolbar: [
|
toolbar: [
|
||||||
|
|||||||
@@ -4,21 +4,28 @@
|
|||||||
const commentsContainer = document.querySelector('#comments-container')
|
const commentsContainer = document.querySelector('#comments-container')
|
||||||
const dropImage = document.querySelector('#dropImage')
|
const dropImage = document.querySelector('#dropImage')
|
||||||
const commentCount = document.querySelector('#comments-count')
|
const commentCount = document.querySelector('#comments-count')
|
||||||
|
const collapsableHR = document.querySelector('.collapable-hr')
|
||||||
|
|
||||||
const hide = () => {
|
const hide = () => {
|
||||||
pager.className += ' d-none'
|
pager.className += ' invisible'
|
||||||
commentsContainer.className += ' d-none'
|
commentsContainer.className += ' d-none'
|
||||||
commentCount.className += ' d-none'
|
commentCount.className += ' invisible'
|
||||||
|
collapsableHR.className += ' d-none'
|
||||||
|
}
|
||||||
|
|
||||||
|
const show = () => {
|
||||||
|
pager.className = 'pager d-flex'
|
||||||
|
commentsContainer.className = 'comments-container pe-1'
|
||||||
|
dropImage.src = '/images/arrow_drop_up.svg'
|
||||||
|
commentCount.className = 'comment-count d-flex ms-auto me-4 text-p875rem'
|
||||||
|
collapsableHR.className += 'comments-top-hr mt-3'
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('collapseComments').addEventListener('click', e => {
|
document.getElementById('collapseComments').addEventListener('click', e => {
|
||||||
isVisible = !isVisible
|
isVisible = !isVisible
|
||||||
|
|
||||||
if (isVisible) {
|
if (isVisible) {
|
||||||
pager.className = 'pager'
|
show()
|
||||||
commentsContainer.className = 'comments-container pe-1'
|
|
||||||
dropImage.src = '/images/arrow_drop_up.svg'
|
|
||||||
commentCount.className = 'comment-count d-flex ms-auto me-4 text-p875rem'
|
|
||||||
} else {
|
} else {
|
||||||
dropImage.src = '/images/arrow_drop_down.svg'
|
dropImage.src = '/images/arrow_drop_down.svg'
|
||||||
hide()
|
hide()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// TODO Remove no-console ignore rule once things are out of rapid dev phase.
|
// TODO Remove no-console ignore rule once things are out of rapid dev phase.
|
||||||
/* eslint no-console: 0 */
|
/* eslint no-console: 0 */
|
||||||
/* global axios, initPagination */
|
/* global axios, initPagination, i18next */
|
||||||
const pageURL = location.pathname
|
const pageURL = location.pathname
|
||||||
|
|
||||||
window.addEventListener('load', () => {
|
window.addEventListener('load', () => {
|
||||||
@@ -127,20 +127,21 @@ function renderCommentCount(commentCount) {
|
|||||||
let displayText = `${commentCount} `
|
let displayText = `${commentCount} `
|
||||||
|
|
||||||
// TODO Let a i18n library handle the following logic.
|
// TODO Let a i18n library handle the following logic.
|
||||||
|
// TODO I18n
|
||||||
switch (commentCount % 100) {
|
switch (commentCount % 100) {
|
||||||
case 1:
|
case 1:
|
||||||
displayText += 'komentar'
|
displayText += i18next.t('komentar')
|
||||||
break
|
break
|
||||||
case 2:
|
case 2:
|
||||||
displayText += 'komentarja'
|
displayText += i18next.t('komentarja')
|
||||||
break
|
break
|
||||||
case 3:
|
case 3:
|
||||||
case 4:
|
case 4:
|
||||||
displayText += 'komentarji'
|
displayText += i18next.t('komentarji')
|
||||||
break
|
break
|
||||||
|
|
||||||
default:
|
default:
|
||||||
displayText += 'komentarjev'
|
displayText += i18next.t('komentarjev')
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,7 +310,7 @@ function submitComment() {
|
|||||||
let ctxId = null
|
let ctxId = null
|
||||||
if (ctxData.ctxId !== undefined) ctxId = ctxData.ctxId
|
if (ctxData.ctxId !== undefined) ctxId = ctxData.ctxId
|
||||||
if (!message) {
|
if (!message) {
|
||||||
alert('Vaš komentar je brez vsebine.')
|
alert(i18next.t('Vaš komentar je brez vsebine.'))
|
||||||
} else {
|
} else {
|
||||||
const payload = { message, ctxType, ctxId, quoteId: null }
|
const payload = { message, ctxType, ctxId, quoteId: null }
|
||||||
createComment(payload)
|
createComment(payload)
|
||||||
@@ -326,7 +327,7 @@ function submitCommentReply() {
|
|||||||
let ctxId = null
|
let ctxId = null
|
||||||
if (ctxData.ctxId !== undefined) ctxId = ctxData.ctxId
|
if (ctxData.ctxId !== undefined) ctxId = ctxData.ctxId
|
||||||
if (!message) {
|
if (!message) {
|
||||||
alert('Vaš komentar je brez vsebine.')
|
alert(i18next.t('Vaš komentar je brez vsebine.'))
|
||||||
} else {
|
} else {
|
||||||
const payload = { message, ctxType, ctxId, quoteId }
|
const payload = { message, ctxType, ctxId, quoteId }
|
||||||
createComment(payload)
|
createComment(payload)
|
||||||
@@ -577,11 +578,11 @@ async function onPageChange(newPage) {
|
|||||||
const { page, numberOfAllPages } = await displayComments(newPage)
|
const { page, numberOfAllPages } = await displayComments(newPage)
|
||||||
updatePager(page, numberOfAllPages)
|
updatePager(page, numberOfAllPages)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
let message = 'Prišlo je do napake.'
|
let message = i18next.t('Prišlo je do napake.')
|
||||||
if (error.response?.data) {
|
if (error.response?.data) {
|
||||||
message = error.response.data
|
message = error.response.data
|
||||||
} else if (error.request) {
|
} else if (error.request) {
|
||||||
message = 'Strežnik ni dosegljiv. Poskusite kasneje.'
|
message = i18next.t('Strežnik ni dosegljiv. Poskusite kasneje.')
|
||||||
}
|
}
|
||||||
alert(message)
|
alert(message)
|
||||||
updatePager()
|
updatePager()
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ function routeConsultancy(searchString) {
|
|||||||
} else {
|
} else {
|
||||||
url = new URL(location)
|
url = new URL(location)
|
||||||
|
|
||||||
|
url.searchParams.delete('p')
|
||||||
const sq = document.getElementById('search-query')
|
const sq = document.getElementById('search-query')
|
||||||
if (sq) {
|
if (sq) {
|
||||||
url.searchParams.set('q', sq.value)
|
url.searchParams.set('q', sq.value)
|
||||||
@@ -65,3 +66,14 @@ if (inputMainSearch) {
|
|||||||
|
|
||||||
propagateFunctionalityToASearchButton(searchButton)
|
propagateFunctionalityToASearchButton(searchButton)
|
||||||
propagateFunctionalityToASearchButton(advancedSearchButton)
|
propagateFunctionalityToASearchButton(advancedSearchButton)
|
||||||
|
|
||||||
|
$(document).ready(() => {
|
||||||
|
const focused = $('#description')
|
||||||
|
if (focused) {
|
||||||
|
focused.focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.location.pathname === '/svetovanje/iskanje') {
|
||||||
|
// todo
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
/* global $, handleDomainsClickForConsultancy, inputFieldsChecker, currentPagePath, axios, nthParent, tooltipListWOL, tooltipTriggerList */
|
/* global $, bootstrap, handleDomainsClickForConsultancy, initPagination, inputFieldsChecker, currentPagePath,
|
||||||
|
axios, nthParent, tooltipListWOL, tooltipTriggerList, removeAllChildNodes, i18next */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
author: Miha Stele, 2022
|
author: Miha Stele, 2022
|
||||||
@@ -9,6 +10,10 @@ let selectedID = -1
|
|||||||
|
|
||||||
// consultancy FORM
|
// consultancy FORM
|
||||||
let offsetMain
|
let offsetMain
|
||||||
|
let OFFSET_PADDING_MASK = 16
|
||||||
|
if (window.location.pathname === '/svetovanje/vprasanje/admin/svetovalci') {
|
||||||
|
OFFSET_PADDING_MASK = 0
|
||||||
|
}
|
||||||
function adjustOffsetBy() {
|
function adjustOffsetBy() {
|
||||||
offsetMain = document.querySelector('#offset-main')
|
offsetMain = document.querySelector('#offset-main')
|
||||||
const fixedTopSection = document.querySelector('#fixed-top-section')
|
const fixedTopSection = document.querySelector('#fixed-top-section')
|
||||||
@@ -22,15 +27,21 @@ function adjustOffsetBy() {
|
|||||||
if (offsetHeaderPadding !== null) offsetMain.style.paddingTop = `0px`
|
if (offsetHeaderPadding !== null) offsetMain.style.paddingTop = `0px`
|
||||||
} else {
|
} else {
|
||||||
for (let i = 0; i < offsetHeader.length; i++) {
|
for (let i = 0; i < offsetHeader.length; i++) {
|
||||||
offsetHeader[i].style.paddingTop = `${referenceHeight}px`
|
offsetHeader[i].style.paddingTop = `${
|
||||||
|
referenceHeight - OFFSET_PADDING_MASK
|
||||||
|
}px`
|
||||||
}
|
}
|
||||||
if (headerPadding !== null) {
|
if (headerPadding !== null) {
|
||||||
const headerPaddingHeight = headerPadding.offsetHeight
|
const headerPaddingHeight = headerPadding.offsetHeight
|
||||||
offsetMain.style.paddingTop = `${headerPaddingHeight}px`
|
offsetMain.style.paddingTop = `${
|
||||||
|
headerPaddingHeight - OFFSET_PADDING_MASK
|
||||||
|
}px`
|
||||||
}
|
}
|
||||||
if (offsetHeaderPadding !== null) {
|
if (offsetHeaderPadding !== null) {
|
||||||
const offsetHeaderHeight = offsetHeaderPadding.offsetHeight
|
const offsetHeaderHeight = offsetHeaderPadding.offsetHeight
|
||||||
offsetMain.style.paddingTop = `${referenceHeight + offsetHeaderHeight}px`
|
offsetMain.style.paddingTop = `${
|
||||||
|
referenceHeight + offsetHeaderHeight - OFFSET_PADDING_MASK
|
||||||
|
}px`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -46,7 +57,7 @@ window.addEventListener('load', () => {
|
|||||||
window.adminElements = ce
|
window.adminElements = ce
|
||||||
/* copy source admin.js in case of refactoring */
|
/* copy source admin.js in case of refactoring */
|
||||||
|
|
||||||
if (currentPagePath === '/svetovanje/vprasanje/admin/uporabniki') {
|
if (currentPagePath === '/svetovanje/vprasanje/admin/svetovalci') {
|
||||||
offsetMain.addEventListener('click', handleDomainsClickForConsultancy)
|
offsetMain.addEventListener('click', handleDomainsClickForConsultancy)
|
||||||
ce.name = document.getElementById('name-input')
|
ce.name = document.getElementById('name-input')
|
||||||
if (ce.name) {
|
if (ce.name) {
|
||||||
@@ -108,7 +119,7 @@ try {
|
|||||||
const summernote = $('.summernote')
|
const summernote = $('.summernote')
|
||||||
if (summernote) {
|
if (summernote) {
|
||||||
summernote.summernote({
|
summernote.summernote({
|
||||||
placeholder: 'Na kratko opišite zasnovo in namen slovarja.',
|
placeholder: i18next.t('Na kratko opišite zasnovo in namen slovarja.'),
|
||||||
height: 300,
|
height: 300,
|
||||||
minheight: 150,
|
minheight: 150,
|
||||||
toolbar: [
|
toolbar: [
|
||||||
@@ -383,7 +394,17 @@ if (createAnswerForm) {
|
|||||||
// console.log(data)
|
// console.log(data)
|
||||||
|
|
||||||
if (res.status === 201) {
|
if (res.status === 201) {
|
||||||
|
const infoModal = document.getElementById('begin-response')
|
||||||
|
if (infoModal) {
|
||||||
|
const responseModal = new bootstrap.Modal(infoModal)
|
||||||
|
responseModal.toggle()
|
||||||
|
}
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById('understand-btn')
|
||||||
|
.addEventListener('click', () => {
|
||||||
window.location.href = '/svetovanje'
|
window.location.href = '/svetovanje'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,7 +428,7 @@ if (insertConsultantForm) {
|
|||||||
})
|
})
|
||||||
.then(result => {
|
.then(result => {
|
||||||
console.log(result)
|
console.log(result)
|
||||||
window.location.href = '/svetovanje/vprasanje/admin/uporabniki'
|
window.location.href = '/svetovanje/vprasanje/admin/svetovalci'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -421,6 +442,243 @@ try {
|
|||||||
console.log(e)
|
console.log(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Frontend Pagination (as in other pages in this project)
|
||||||
|
|
||||||
|
function onResultClick(e) {
|
||||||
|
const entryEl = e.target.closest(`#${this.id} a.rl`)
|
||||||
|
if (!entryEl) return
|
||||||
|
|
||||||
|
const anchorId = entryEl.querySelector('.anchor').id
|
||||||
|
const anchoredUrl = new URL(location)
|
||||||
|
anchoredUrl.hash = anchorId
|
||||||
|
history.replaceState(null, '', anchoredUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
const resultsListEl = document.getElementById('results-data')
|
||||||
|
if (resultsListEl) {
|
||||||
|
resultsListEl.addEventListener('click', onResultClick)
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialPage = +new URL(location).searchParams.get('p') || 1
|
||||||
|
|
||||||
|
let updatePager
|
||||||
|
try {
|
||||||
|
updatePager = initPagination('pagination', onPageChange, initialPage)
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
async function onPageChange(newPage) {
|
||||||
|
const receivedPageNumber = await changePage(newPage)
|
||||||
|
updateUrlAndHistory(receivedPageNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function changePage(newPage) {
|
||||||
|
try {
|
||||||
|
const res = await getDataForPage(newPage)
|
||||||
|
|
||||||
|
const page = +res.headers.page
|
||||||
|
const numberOfAllPages = +res.headers['number-of-all-pages']
|
||||||
|
const resultsMarkup = res.data
|
||||||
|
|
||||||
|
removeAllChildNodes(resultsListEl)
|
||||||
|
renderResults(resultsMarkup)
|
||||||
|
updatePager(page, numberOfAllPages)
|
||||||
|
|
||||||
|
return page
|
||||||
|
} catch (error) {
|
||||||
|
// console.log(error)
|
||||||
|
let message = i18next.t('Prišlo je do napake.')
|
||||||
|
if (error.response?.data) {
|
||||||
|
message = error.response.data
|
||||||
|
} else if (error.request) {
|
||||||
|
return // return to bypass error caused (assumed) by popstate on iOS/MacOS
|
||||||
|
message = i18next.t('Strežnik ni dosegljiv. Poskusite kasneje.')
|
||||||
|
}
|
||||||
|
|
||||||
|
alert(message)
|
||||||
|
updatePager()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getDataForPage(page) {
|
||||||
|
const qParams = new URL(location).searchParams
|
||||||
|
qParams.set('p', page)
|
||||||
|
|
||||||
|
// const url = `/api/v1/search/main?${qParams}`
|
||||||
|
|
||||||
|
// let url = `/api/v1/consultancy/entry-pagination?${qParams}`
|
||||||
|
// URL PATTERN PARSING
|
||||||
|
const PATH = window.location.pathname
|
||||||
|
if (PATH.includes('/admin/')) {
|
||||||
|
// todo ADMIN URL handling
|
||||||
|
qParams.set('isAdmin', 'true')
|
||||||
|
// url = `/api/v1/consultancy/entry-pagination?${qParams}` // required due to updated queryParams
|
||||||
|
|
||||||
|
if (PATH.includes('/novo')) {
|
||||||
|
qParams.set('type', 'new')
|
||||||
|
} else if (PATH.includes('/pripravljeno')) {
|
||||||
|
qParams.set('type', 'review')
|
||||||
|
} else if (PATH.includes('/zavrnjeno')) {
|
||||||
|
qParams.set('type', 'rejected')
|
||||||
|
} else if (PATH.includes('/v-delu')) {
|
||||||
|
qParams.set('type', 'in progress')
|
||||||
|
} else if (PATH.includes('/objavljeno')) {
|
||||||
|
qParams.set('type', 'published')
|
||||||
|
} else {
|
||||||
|
// Invalid case
|
||||||
|
console.log('INVALID CASE, CHECK URL')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// const url = `/api/v1/search/main?${qParams}`
|
||||||
|
|
||||||
|
const url = `/api/v1/consultancy/entry-pagination?${qParams}`
|
||||||
|
console.log(url)
|
||||||
|
|
||||||
|
return await axios.get(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderResults(resultsMarkup) {
|
||||||
|
const wrapper = document.createElement('div')
|
||||||
|
wrapper.innerHTML = resultsMarkup
|
||||||
|
resultsListEl.appendChild(wrapper)
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUrlAndHistory(page) {
|
||||||
|
const newUrl = new URL(location)
|
||||||
|
newUrl.searchParams.set('p', page)
|
||||||
|
history.pushState(null, '', newUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('popstate', () => {
|
||||||
|
const page = +new URL(location).searchParams.get('p') || 1
|
||||||
|
changePage(page)
|
||||||
|
})
|
||||||
|
|
||||||
|
/// // PAGINATION (backend only) /////
|
||||||
|
|
||||||
|
/* const updatePager = initPaginationBE('pagination', onPageChangeBE)
|
||||||
|
|
||||||
|
function onPageChangeBE(newPage) {
|
||||||
|
const newUrl = new URL(location)
|
||||||
|
newUrl.searchParams.set('p', newPage)
|
||||||
|
history.pushState(null, '', newUrl)
|
||||||
|
window.location.href = newUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
function initPaginationBE(paginationRootElId, onPageChange, currentPage = 1) {
|
||||||
|
const rootEl = document.getElementById(paginationRootElId)
|
||||||
|
const btnFirstPage = rootEl.querySelector('.first-page')
|
||||||
|
const btnPreviousPage = rootEl.querySelector('.previous-page')
|
||||||
|
const btnNextPage = rootEl.querySelector('.next-page')
|
||||||
|
const btnLastPage = rootEl.querySelector('.last-page')
|
||||||
|
const formEl = rootEl.querySelector('form')
|
||||||
|
const pageInputEl = formEl.querySelector('input')
|
||||||
|
const pagesCountDisplayEl = formEl.querySelector('.pages-total')
|
||||||
|
|
||||||
|
let reqLock = false
|
||||||
|
|
||||||
|
rootEl.addEventListener('click', handleButtonClick)
|
||||||
|
formEl.addEventListener('submit', handleFormSubmit)
|
||||||
|
|
||||||
|
function handleButtonClick({ target }) {
|
||||||
|
if (reqLock) return
|
||||||
|
|
||||||
|
const buttonEl = target.closest(`#${paginationRootElId} button`)
|
||||||
|
if (!buttonEl) return
|
||||||
|
const numOfAllPages = +pagesCountDisplayEl.textContent
|
||||||
|
|
||||||
|
if (buttonEl.classList.contains('first-page')) {
|
||||||
|
if (currentPage === 1) return
|
||||||
|
enableLock()
|
||||||
|
onPageChange(1)
|
||||||
|
} else if (buttonEl.classList.contains('previous-page')) {
|
||||||
|
if (currentPage === 1) return
|
||||||
|
enableLock()
|
||||||
|
onPageChange(currentPage - 1)
|
||||||
|
} else if (buttonEl.classList.contains('next-page')) {
|
||||||
|
if (currentPage === numOfAllPages) return
|
||||||
|
enableLock()
|
||||||
|
onPageChange(currentPage + 1)
|
||||||
|
} else if (buttonEl.classList.contains('last-page')) {
|
||||||
|
if (currentPage === numOfAllPages) return
|
||||||
|
enableLock()
|
||||||
|
onPageChange(numOfAllPages)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFormSubmit(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
if (reqLock) return
|
||||||
|
|
||||||
|
const inputValue = +pageInputEl.value
|
||||||
|
if (!(inputValue > 0 && inputValue <= pagesCountDisplayEl.textContent)) {
|
||||||
|
alert('Nepravilna vrednost strani')
|
||||||
|
pageInputEl.value = currentPage
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
enableLock()
|
||||||
|
onPageChange(inputValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
function enableLock() {
|
||||||
|
reqLock = true
|
||||||
|
btnFirstPage.disabled = true
|
||||||
|
btnPreviousPage.disabled = true
|
||||||
|
btnNextPage.disabled = true
|
||||||
|
btnLastPage.disabled = true
|
||||||
|
pageInputEl.disabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function disableLock() {
|
||||||
|
reqLock = false
|
||||||
|
btnFirstPage.disabled = false
|
||||||
|
btnPreviousPage.disabled = false
|
||||||
|
btnNextPage.disabled = false
|
||||||
|
btnLastPage.disabled = false
|
||||||
|
pageInputEl.disabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePagerUi(newCurrentPage, newNumOfAllPages) {
|
||||||
|
disableLock()
|
||||||
|
if (!newCurrentPage) return
|
||||||
|
|
||||||
|
currentPage = newCurrentPage
|
||||||
|
pageInputEl.value = newCurrentPage
|
||||||
|
pagesCountDisplayEl.textContent = newNumOfAllPages
|
||||||
|
|
||||||
|
if (newCurrentPage === 1) {
|
||||||
|
btnFirstPage.disabled = true
|
||||||
|
btnPreviousPage.disabled = true
|
||||||
|
} else {
|
||||||
|
btnFirstPage.disabled = false
|
||||||
|
btnPreviousPage.disabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newCurrentPage === newNumOfAllPages) {
|
||||||
|
btnNextPage.disabled = true
|
||||||
|
btnLastPage.disabled = true
|
||||||
|
} else {
|
||||||
|
btnNextPage.disabled = false
|
||||||
|
btnLastPage.disabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePagerUi(
|
||||||
|
+new URL(location).searchParams.get('p') || 1,
|
||||||
|
+document.querySelector('.pages-total').innerHTML
|
||||||
|
)
|
||||||
|
|
||||||
|
return updatePagerUi
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('popstate', () => {
|
||||||
|
const page = +new URL(location).searchParams.get('p') || 1
|
||||||
|
onPageChangeBE(page)
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
/// ///////////////////
|
||||||
|
|
||||||
/*
|
/*
|
||||||
if (tooltipList) {
|
if (tooltipList) {
|
||||||
console.log(tooltipList)
|
console.log(tooltipList)
|
||||||
@@ -438,3 +696,10 @@ $(document).ready(() => {
|
|||||||
focused.focus()
|
focused.focus()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const askBackButton = document.querySelector('#cancel-cons-btn')
|
||||||
|
if (askBackButton) {
|
||||||
|
askBackButton.addEventListener('click', () => {
|
||||||
|
history.back()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,9 +7,15 @@
|
|||||||
|
|
||||||
// Paginacijo inicializiraš s klicem funkcije initPagination:
|
// Paginacijo inicializiraš s klicem funkcije initPagination:
|
||||||
// 1. parameter: id pager elementa. V demo-paginacija.pug je to #pagination.
|
// 1. parameter: id pager elementa. V demo-paginacija.pug je to #pagination.
|
||||||
|
// UPDATE: Sedaj sprejme tudi array idjev, če je pager kontrolerjev več (npr. en zgoraj (#pagination-top), en spodaj (#pagination-bottom)).
|
||||||
// 2. parameter: callback funkcijo, ki jo pager pokliče vsakič, ko uporabnik zahteva novo stran. Poliče jo s številko zahtevane strani.
|
// 2. parameter: callback funkcijo, ki jo pager pokliče vsakič, ko uporabnik zahteva novo stran. Poliče jo s številko zahtevane strani.
|
||||||
// vrnjena vrednost: funkcija, ki jo (kasneje) kličeš za posodobitev pagerja. Tu jo poimenujem updateDemoPager.
|
// vrnjena vrednost: funkcija, ki jo (kasneje) kličeš za posodobitev pagerja. Tu jo poimenujem updateDemoPager.
|
||||||
const updateDemoPager = initPagination('pagination', onPageChange)
|
const updateDemoPager = initPagination(
|
||||||
|
['pagination-top', 'pagination-bottom'],
|
||||||
|
onPageChange
|
||||||
|
)
|
||||||
|
// Za samo en kontroler je bilo:
|
||||||
|
// const updateDemoPager = initPagination('pagination', onPageChange)
|
||||||
|
|
||||||
// Fukncija, prejme številko nove strani in naj:
|
// Fukncija, prejme številko nove strani in naj:
|
||||||
// 1. Pridobi podatke nove strani.
|
// 1. Pridobi podatke nove strani.
|
||||||
@@ -62,7 +68,22 @@
|
|||||||
* Na vsaki strani, kjer bo paginacija, jo inicializiraj in uporabljaj po zgledu zgornje kode. *
|
* Na vsaki strani, kjer bo paginacija, jo inicializiraj in uporabljaj po zgledu zgornje kode. *
|
||||||
\** ****************************************************************************************************************************************** **/
|
\** ****************************************************************************************************************************************** **/
|
||||||
|
|
||||||
function initPagination(paginationRootElId, onPageChange, currentPage = 1) {
|
function initPagination(paginationRootElIds, onPageChange, currentPage = 1) {
|
||||||
|
let reqLock = false
|
||||||
|
let numOfAllPages
|
||||||
|
const backwardBtnEls = []
|
||||||
|
const forwardBtnEls = []
|
||||||
|
const pageInputEls = []
|
||||||
|
const pagesCountDisplayEls = []
|
||||||
|
|
||||||
|
if (Array.isArray(paginationRootElIds)) {
|
||||||
|
paginationRootElIds.forEach(id => initControls(id))
|
||||||
|
} else {
|
||||||
|
initControls(paginationRootElIds)
|
||||||
|
}
|
||||||
|
const allControlEls = [...backwardBtnEls, ...forwardBtnEls, ...pageInputEls]
|
||||||
|
|
||||||
|
function initControls(paginationRootElId) {
|
||||||
const rootEl = document.getElementById(paginationRootElId)
|
const rootEl = document.getElementById(paginationRootElId)
|
||||||
const btnFirstPage = rootEl.querySelector('.first-page')
|
const btnFirstPage = rootEl.querySelector('.first-page')
|
||||||
const btnPreviousPage = rootEl.querySelector('.previous-page')
|
const btnPreviousPage = rootEl.querySelector('.previous-page')
|
||||||
@@ -72,17 +93,24 @@ function initPagination(paginationRootElId, onPageChange, currentPage = 1) {
|
|||||||
const pageInputEl = formEl.querySelector('input')
|
const pageInputEl = formEl.querySelector('input')
|
||||||
const pagesCountDisplayEl = formEl.querySelector('.pages-total')
|
const pagesCountDisplayEl = formEl.querySelector('.pages-total')
|
||||||
|
|
||||||
let reqLock = false
|
numOfAllPages = +pagesCountDisplayEl.textContent
|
||||||
|
|
||||||
rootEl.addEventListener('click', handleButtonClick)
|
backwardBtnEls.push(btnFirstPage, btnPreviousPage)
|
||||||
formEl.addEventListener('submit', handleFormSubmit)
|
forwardBtnEls.push(btnNextPage, btnLastPage)
|
||||||
|
pageInputEls.push(pageInputEl)
|
||||||
|
pagesCountDisplayEls.push(pagesCountDisplayEl)
|
||||||
|
|
||||||
function handleButtonClick({ target }) {
|
rootEl.addEventListener('click', e => {
|
||||||
|
handleButtonClick(e, paginationRootElId)
|
||||||
|
})
|
||||||
|
formEl.addEventListener('submit', e => handleFormSubmit(e, pageInputEl))
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleButtonClick({ target }, paginationRootElId) {
|
||||||
if (reqLock) return
|
if (reqLock) return
|
||||||
|
|
||||||
const buttonEl = target.closest(`#${paginationRootElId} button`)
|
const buttonEl = target.closest(`#${paginationRootElId} button`)
|
||||||
if (!buttonEl) return
|
if (!buttonEl) return
|
||||||
const numOfAllPages = +pagesCountDisplayEl.textContent
|
|
||||||
|
|
||||||
if (buttonEl.classList.contains('first-page')) {
|
if (buttonEl.classList.contains('first-page')) {
|
||||||
if (currentPage === 1) return
|
if (currentPage === 1) return
|
||||||
@@ -103,12 +131,12 @@ function initPagination(paginationRootElId, onPageChange, currentPage = 1) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleFormSubmit(e) {
|
function handleFormSubmit(e, pageInputEl) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (reqLock) return
|
if (reqLock) return
|
||||||
|
|
||||||
const inputValue = +pageInputEl.value
|
const inputValue = +pageInputEl.value
|
||||||
if (!(inputValue > 0 && inputValue <= pagesCountDisplayEl.textContent)) {
|
if (!(inputValue > 0 && inputValue <= numOfAllPages)) {
|
||||||
alert('Nepravilna vrednost strani')
|
alert('Nepravilna vrednost strani')
|
||||||
pageInputEl.value = currentPage
|
pageInputEl.value = currentPage
|
||||||
return
|
return
|
||||||
@@ -120,20 +148,12 @@ function initPagination(paginationRootElId, onPageChange, currentPage = 1) {
|
|||||||
|
|
||||||
function enableLock() {
|
function enableLock() {
|
||||||
reqLock = true
|
reqLock = true
|
||||||
btnFirstPage.disabled = true
|
allControlEls.forEach(el => (el.disabled = true))
|
||||||
btnPreviousPage.disabled = true
|
|
||||||
btnNextPage.disabled = true
|
|
||||||
btnLastPage.disabled = true
|
|
||||||
pageInputEl.disabled = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function disableLock() {
|
function disableLock() {
|
||||||
reqLock = false
|
reqLock = false
|
||||||
btnFirstPage.disabled = false
|
allControlEls.forEach(el => (el.disabled = false))
|
||||||
btnPreviousPage.disabled = false
|
|
||||||
btnNextPage.disabled = false
|
|
||||||
btnLastPage.disabled = false
|
|
||||||
pageInputEl.disabled = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updatePagerUi(newCurrentPage, newNumOfAllPages) {
|
function updatePagerUi(newCurrentPage, newNumOfAllPages) {
|
||||||
@@ -141,23 +161,19 @@ function initPagination(paginationRootElId, onPageChange, currentPage = 1) {
|
|||||||
if (!newCurrentPage) return
|
if (!newCurrentPage) return
|
||||||
|
|
||||||
currentPage = newCurrentPage
|
currentPage = newCurrentPage
|
||||||
pageInputEl.value = newCurrentPage
|
pageInputEls.forEach(el => (el.value = newCurrentPage))
|
||||||
pagesCountDisplayEl.textContent = newNumOfAllPages
|
pagesCountDisplayEls.forEach(el => (el.textContent = newNumOfAllPages))
|
||||||
|
|
||||||
if (newCurrentPage === 1) {
|
if (newCurrentPage === 1) {
|
||||||
btnFirstPage.disabled = true
|
backwardBtnEls.forEach(el => (el.disabled = true))
|
||||||
btnPreviousPage.disabled = true
|
|
||||||
} else {
|
} else {
|
||||||
btnFirstPage.disabled = false
|
backwardBtnEls.forEach(el => (el.disabled = false))
|
||||||
btnPreviousPage.disabled = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newCurrentPage === newNumOfAllPages) {
|
if (newCurrentPage === newNumOfAllPages) {
|
||||||
btnNextPage.disabled = true
|
forwardBtnEls.forEach(el => (el.disabled = true))
|
||||||
btnLastPage.disabled = true
|
|
||||||
} else {
|
} else {
|
||||||
btnNextPage.disabled = false
|
forwardBtnEls.forEach(el => (el.disabled = false))
|
||||||
btnLastPage.disabled = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* global $, axios, bootstrap, currentPagePath, initPagination, removeAllChildNodes, unsavedData */
|
/* global $, axios, bootstrap, currentPagePath, initPagination, removeAllChildNodes, unsavedData, replaceContainer, i18next */
|
||||||
|
|
||||||
// Temporary workaround (use of currentPagePath).
|
// Temporary workaround (use of currentPagePath).
|
||||||
|
|
||||||
@@ -6,6 +6,8 @@ window.addEventListener('load', () => {
|
|||||||
initDictionaries()
|
initDictionaries()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let queryBattery = ''
|
||||||
|
|
||||||
function initDictionaries() {
|
function initDictionaries() {
|
||||||
const ce = {}
|
const ce = {}
|
||||||
window.dictionaryElements = ce
|
window.dictionaryElements = ce
|
||||||
@@ -166,7 +168,7 @@ function initDictionaries() {
|
|||||||
if (ce.fileUploadInput.files.item(0) !== null)
|
if (ce.fileUploadInput.files.item(0) !== null)
|
||||||
chosenFile.textContent = ce.fileUploadInput.files.item(0).name
|
chosenFile.textContent = ce.fileUploadInput.files.item(0).name
|
||||||
else {
|
else {
|
||||||
chosenFile.textContent = 'Izberi datoteko'
|
chosenFile.textContent = i18next.t('Izberi datoteko')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -185,12 +187,140 @@ function initDictionaries() {
|
|||||||
|
|
||||||
await axios.post(event.target.action, payload)
|
await axios.post(event.target.action, payload)
|
||||||
|
|
||||||
alert('SLOVAR UVOŽEN')
|
alert(i18next.t('SLOVAR UVOŽEN'))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert('NAPAKA V UVOZU')
|
alert(i18next.t('NAPAKA V UVOZU'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const resultsListEl = document.getElementById('page-results')
|
||||||
|
const dictionaryId = document.getElementById('dictionary-id').value
|
||||||
|
|
||||||
|
const updatePager = initPagination('pagination', onPageChange)
|
||||||
|
|
||||||
|
async function onPageChange(newPage) {
|
||||||
|
try {
|
||||||
|
const { page, numberOfAllPages, results } = await getDataForPage(
|
||||||
|
newPage
|
||||||
|
)
|
||||||
|
removeAllChildNodes(resultsListEl)
|
||||||
|
renderResults(results)
|
||||||
|
updatePager(page, numberOfAllPages)
|
||||||
|
} catch (error) {
|
||||||
|
let message = i18next.t('Prišlo je do napake.')
|
||||||
|
if (error.response?.data) {
|
||||||
|
message = error.response.data
|
||||||
|
} else if (error.request) {
|
||||||
|
message = i18next.t('Strežnik ni dosegljiv. Poskusite kasneje.')
|
||||||
|
}
|
||||||
|
alert(message)
|
||||||
|
updatePager()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getDataForPage(page) {
|
||||||
|
const url = `/api/v1/dictionaries/${dictionaryId}/showImportFromFileForm?p=${page}`
|
||||||
|
const { data } = await axios.get(url)
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderResults(results) {
|
||||||
|
const localeOptions = {
|
||||||
|
day: '2-digit',
|
||||||
|
month: '2-digit',
|
||||||
|
year: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
}
|
||||||
|
results.forEach(result => {
|
||||||
|
const rowEl = document.createElement('tr')
|
||||||
|
const td1 = document.createElement('td')
|
||||||
|
const td2 = document.createElement('td')
|
||||||
|
const td3 = document.createElement('td')
|
||||||
|
const td4 = document.createElement('td')
|
||||||
|
const date = new Date(result.time_started).toLocaleDateString(
|
||||||
|
'sl-SL',
|
||||||
|
localeOptions
|
||||||
|
)
|
||||||
|
td1.textContent = date
|
||||||
|
td2.textContent = result.file_format
|
||||||
|
td3.textContent = result.count_valid_entries
|
||||||
|
td4.textContent = result.status
|
||||||
|
rowEl.append(td1, td2, td3, td4)
|
||||||
|
resultsListEl.appendChild(rowEl)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (/\/slovarji\/\d+\/izvoz/.test(currentPagePath)) {
|
||||||
|
const resultsListEl = document.getElementById('page-results')
|
||||||
|
const dictionaryId = document.getElementById('dictionary-id').value
|
||||||
|
|
||||||
|
const updatePager = initPagination('pagination', onPageChange)
|
||||||
|
|
||||||
|
async function onPageChange(newPage) {
|
||||||
|
try {
|
||||||
|
const { page, numberOfAllPages, results } = await getDataForPage(
|
||||||
|
newPage
|
||||||
|
)
|
||||||
|
removeAllChildNodes(resultsListEl)
|
||||||
|
renderResults(results)
|
||||||
|
updatePager(page, numberOfAllPages)
|
||||||
|
} catch (error) {
|
||||||
|
let message = i18next.t('Prišlo je do napake.')
|
||||||
|
if (error.response?.data) {
|
||||||
|
message = error.response.data
|
||||||
|
} else if (error.request) {
|
||||||
|
message = i18next.t('Strežnik ni dosegljiv. Poskusite kasneje.')
|
||||||
|
}
|
||||||
|
alert(message)
|
||||||
|
updatePager()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getDataForPage(page) {
|
||||||
|
const url = `/api/v1/dictionaries/${dictionaryId}/showExportToFileForm?p=${page}`
|
||||||
|
const { data } = await axios.get(url)
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderResults(results) {
|
||||||
|
const localeOptions = {
|
||||||
|
day: '2-digit',
|
||||||
|
month: '2-digit',
|
||||||
|
year: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
}
|
||||||
|
results.forEach(result => {
|
||||||
|
const rowEl = document.createElement('tr')
|
||||||
|
const td1 = document.createElement('td')
|
||||||
|
const td2 = document.createElement('td')
|
||||||
|
const td3 = document.createElement('td')
|
||||||
|
const td4 = document.createElement('td')
|
||||||
|
const date = new Date(result.time_created).toLocaleDateString(
|
||||||
|
'sl-SL',
|
||||||
|
localeOptions
|
||||||
|
)
|
||||||
|
td1.textContent = date
|
||||||
|
td2.textContent = result.export_file_format
|
||||||
|
td3.textContent = result.entry_count
|
||||||
|
if (result.status === 'finished') {
|
||||||
|
const aEl = document.createElement('a')
|
||||||
|
aEl.href = `/slovarji/export-download/${result.id}`
|
||||||
|
aEl.textContent = i18next.t('Shrani')
|
||||||
|
aEl.className = 'btn btn-primary'
|
||||||
|
td4.append(aEl)
|
||||||
|
} else td4.textContent = ''
|
||||||
|
|
||||||
|
rowEl.append(td1, td2, td3, td4)
|
||||||
|
resultsListEl.appendChild(rowEl)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (/\/slovarji\/\d+\/vsebina/.test(currentPagePath)) {
|
if (/\/slovarji\/\d+\/vsebina/.test(currentPagePath)) {
|
||||||
ce.formEditContent = document.getElementById('form-edit-content')
|
ce.formEditContent = document.getElementById('form-edit-content')
|
||||||
ce.formEditContent.addEventListener('submit', updateEntry)
|
ce.formEditContent.addEventListener('submit', updateEntry)
|
||||||
@@ -233,6 +363,7 @@ function initDictionaries() {
|
|||||||
ce.clearSearchInput = document.querySelector('.clear-search-input')
|
ce.clearSearchInput = document.querySelector('.clear-search-input')
|
||||||
ce.collapsablePart = document.querySelectorAll('.collapsable-entry-part')
|
ce.collapsablePart = document.querySelectorAll('.collapsable-entry-part')
|
||||||
ce.scrollEl = document.querySelector('.content-nav-content')
|
ce.scrollEl = document.querySelector('.content-nav-content')
|
||||||
|
$('.multiple').on('select2:select', enableButton)
|
||||||
ce.collapsablePart.forEach(el => {
|
ce.collapsablePart.forEach(el => {
|
||||||
el.addEventListener('hide.bs.collapse', () =>
|
el.addEventListener('hide.bs.collapse', () =>
|
||||||
changePageContent('collapse-hidden')
|
changePageContent('collapse-hidden')
|
||||||
@@ -257,7 +388,7 @@ function initDictionaries() {
|
|||||||
})
|
})
|
||||||
ce.statusEditEl = document.getElementById('status-edit')
|
ce.statusEditEl = document.getElementById('status-edit')
|
||||||
// ce.deleteEntryBtn.addEventListener('click', () => ajaxDeleteEntry())
|
// ce.deleteEntryBtn.addEventListener('click', () => ajaxDeleteEntry())
|
||||||
ce.newEntryBtn.addEventListener('click', () => {
|
ce.newEntryBtn.addEventListener('click', e => {
|
||||||
ce.formEditContent.reset()
|
ce.formEditContent.reset()
|
||||||
changePageContent('new-entry-btn')
|
changePageContent('new-entry-btn')
|
||||||
})
|
})
|
||||||
@@ -307,6 +438,7 @@ function initDictionaries() {
|
|||||||
if (mcBtnsGrp.length) {
|
if (mcBtnsGrp.length) {
|
||||||
mcBtnsGrp.forEach(e => e.addEventListener('click', enableButton))
|
mcBtnsGrp.forEach(e => e.addEventListener('click', enableButton))
|
||||||
}
|
}
|
||||||
|
if (ce.newHeadwordGroupBtn)
|
||||||
ce.newHeadwordGroupBtn.addEventListener('click', createHeadword)
|
ce.newHeadwordGroupBtn.addEventListener('click', createHeadword)
|
||||||
if (ce.newConnectionBtn)
|
if (ce.newConnectionBtn)
|
||||||
ce.newConnectionBtn.addEventListener('click', () =>
|
ce.newConnectionBtn.addEventListener('click', () =>
|
||||||
@@ -408,7 +540,7 @@ function initDictionaries() {
|
|||||||
|
|
||||||
if (termListMenu.childElementCount > 1) {
|
if (termListMenu.childElementCount > 1) {
|
||||||
const termListLabels = termListMenu.querySelectorAll('label')
|
const termListLabels = termListMenu.querySelectorAll('label')
|
||||||
termListLabels[0].click()
|
if (termListLabels.length) termListLabels[0].click()
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleClick({ target }) {
|
function handleClick({ target }) {
|
||||||
@@ -423,10 +555,11 @@ function initDictionaries() {
|
|||||||
const okBtnText = modalUseBtn.textContent
|
const okBtnText = modalUseBtn.textContent
|
||||||
const cnclBtnTxt = modalCnclBtn.textContent
|
const cnclBtnTxt = modalCnclBtn.textContent
|
||||||
const modalMainTxt = modalMain.textContent
|
const modalMainTxt = modalMain.textContent
|
||||||
modalUseBtn.textContent = 'Shrani'
|
modalUseBtn.textContent = i18next.t('Shrani')
|
||||||
modalCnclBtn.textContent = 'Ne shrani'
|
modalCnclBtn.textContent = i18next.t('Ne shrani')
|
||||||
modalMain.textContent =
|
modalMain.textContent = i18next.t(
|
||||||
'Imate neshranjene spremembe. Ali jih želite shraniti?'
|
'Imate neshranjene spremembe. Ali jih želite shraniti?'
|
||||||
|
)
|
||||||
const alertModal = new bootstrap.Modal(
|
const alertModal = new bootstrap.Modal(
|
||||||
document.getElementById('alert-modal')
|
document.getElementById('alert-modal')
|
||||||
)
|
)
|
||||||
@@ -478,8 +611,8 @@ function initDictionaries() {
|
|||||||
if (deleteEntryEl) {
|
if (deleteEntryEl) {
|
||||||
const modalUseBtn = document.getElementById('modal-del-btn')
|
const modalUseBtn = document.getElementById('modal-del-btn')
|
||||||
const modalCnclBtn = document.getElementById('cancel-btn')
|
const modalCnclBtn = document.getElementById('cancel-btn')
|
||||||
modalUseBtn.textContent = 'Izbriši'
|
modalUseBtn.textContent = i18next.t('Izbriši')
|
||||||
modalCnclBtn.textContent = 'Ne izbriši'
|
modalCnclBtn.textContent = i18next.t('Ne izbriši')
|
||||||
const alertModal = new bootstrap.Modal(
|
const alertModal = new bootstrap.Modal(
|
||||||
document.getElementById('delete-modal')
|
document.getElementById('delete-modal')
|
||||||
)
|
)
|
||||||
@@ -497,8 +630,8 @@ function initDictionaries() {
|
|||||||
const { data } = await axios.get(
|
const { data } = await axios.get(
|
||||||
`/api/v1/entries/${entryId}/version-snapshots/${versionId}`
|
`/api/v1/entries/${entryId}/version-snapshots/${versionId}`
|
||||||
)
|
)
|
||||||
const payload = { entry: data }
|
const payload = { entry: data.data }
|
||||||
insertTermData(entryId, payload)
|
insertTermData(entryId, payload, data.author)
|
||||||
const isPublishedEl = payload.entry.is_published
|
const isPublishedEl = payload.entry.is_published
|
||||||
const info = new FormData(formEditContent)
|
const info = new FormData(formEditContent)
|
||||||
info.append('isPublishedEl', isPublishedEl)
|
info.append('isPublishedEl', isPublishedEl)
|
||||||
@@ -531,6 +664,7 @@ function initDictionaries() {
|
|||||||
info.append('isPublishedEl', isPublishedEl)
|
info.append('isPublishedEl', isPublishedEl)
|
||||||
loadPreview(info)
|
loadPreview(info)
|
||||||
changeVersionList(data.entry.versions)
|
changeVersionList(data.entry.versions)
|
||||||
|
setLatestVersion(data.entry)
|
||||||
ce.formEditContent.addEventListener('input', () => (unsaved = true))
|
ce.formEditContent.addEventListener('input', () => (unsaved = true))
|
||||||
formEditContent.dataset.entryId = entryId
|
formEditContent.dataset.entryId = entryId
|
||||||
formEditContent.action = '/api/v1/entries/update'
|
formEditContent.action = '/api/v1/entries/update'
|
||||||
@@ -553,7 +687,7 @@ function initDictionaries() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function insertTermData(entryId, data) {
|
function insertTermData(entryId, data, vAuthor) {
|
||||||
const { newConnectionBtn } = window.dictionaryElements
|
const { newConnectionBtn } = window.dictionaryElements
|
||||||
const localeOptions = {
|
const localeOptions = {
|
||||||
day: '2-digit',
|
day: '2-digit',
|
||||||
@@ -609,12 +743,20 @@ function initDictionaries() {
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
for (let i = 0; i < termIdText.length; i++) {
|
for (let i = 0; i < termIdText.length; i++) {
|
||||||
termIdText[i].textContent = `ID: Ni idja`
|
termIdText[i].textContent = i18next.t('ID: Ni idja')
|
||||||
}
|
}
|
||||||
|
if (vAuthor) {
|
||||||
|
ce.authorEl.textContent = vAuthor
|
||||||
|
ce.previewAuthor.textContent = vAuthor
|
||||||
|
} else {
|
||||||
ce.authorEl.textContent = data.entry.version_author
|
ce.authorEl.textContent = data.entry.version_author
|
||||||
ce.versionEl.textContent = `Verzija ${data.entry.version}`
|
|
||||||
ce.previewVersion.textContent = `Verzija ${data.entry.version}`
|
|
||||||
ce.previewAuthor.textContent = data.entry.version_author
|
ce.previewAuthor.textContent = data.entry.version_author
|
||||||
|
}
|
||||||
|
// ce.versionEl.textContent = `Verzija ${data.entry.version}`
|
||||||
|
ce.versionEl.textContent = i18next.t('Verzija') + `${data.entry.version}`
|
||||||
|
// ce.previewVersion.textContent = `Verzija ${data.entry.version}`
|
||||||
|
ce.previewVersion.textContent =
|
||||||
|
i18next.t('Verzija') + `${data.entry.version}`
|
||||||
termName.value = data.entry.term
|
termName.value = data.entry.term
|
||||||
? data.entry.term.replace(/"/g, '"')
|
? data.entry.term.replace(/"/g, '"')
|
||||||
: ''
|
: ''
|
||||||
@@ -757,18 +899,19 @@ function initDictionaries() {
|
|||||||
const payload = new URLSearchParams(new FormData(ce.formEditContent))
|
const payload = new URLSearchParams(new FormData(ce.formEditContent))
|
||||||
payload.set('entryId', entryId)
|
payload.set('entryId', entryId)
|
||||||
try {
|
try {
|
||||||
|
saveBtn.textContent = i18next.t('Shranjujem ...')
|
||||||
await axios.post(ce.formEditContent.action, payload)
|
await axios.post(ce.formEditContent.action, payload)
|
||||||
spinnerEl.classList.remove('d-none')
|
spinnerEl.classList.remove('d-none')
|
||||||
saveBtn.classList.add('saved-entry-btn')
|
saveBtn.classList.add('saved-entry-btn')
|
||||||
saveBtn.textContent = 'Shranjeno'
|
saveBtn.textContent = i18next.t('Shranjeno')
|
||||||
if (id != null) entryId = id
|
if (id != null) entryId = id
|
||||||
ajaxSideMenu(entryId)
|
ajaxSideMenu(entryId)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
let message = 'Prišlo je do napake.'
|
let message = i18next.t('Prišlo je do napake.')
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
message = error.response.data
|
message = error.response.data
|
||||||
} else if (error.request) {
|
} else if (error.request) {
|
||||||
message = 'Strežnik ni dosegljiv. Poskusite kasneje.'
|
message = i18next.t('Strežnik ni dosegljiv. Poskusite kasneje.')
|
||||||
}
|
}
|
||||||
messageContainer.textContent = message
|
messageContainer.textContent = message
|
||||||
}
|
}
|
||||||
@@ -776,19 +919,20 @@ function initDictionaries() {
|
|||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
const payload = new URLSearchParams(new FormData(ce.formEditContent))
|
const payload = new URLSearchParams(new FormData(ce.formEditContent))
|
||||||
try {
|
try {
|
||||||
|
saveBtn.textContent = i18next.t('Shranjujem ...')
|
||||||
const res = await axios.post(ce.formEditContent.action, payload)
|
const res = await axios.post(ce.formEditContent.action, payload)
|
||||||
spinnerEl.classList.remove('d-none')
|
spinnerEl.classList.remove('d-none')
|
||||||
saveBtn.classList.add('saved-entry-btn')
|
saveBtn.classList.add('saved-entry-btn')
|
||||||
saveBtn.textContent = 'Shranjeno'
|
saveBtn.textContent = i18next.t('Shranjeno')
|
||||||
let entryId = res.data.entryId
|
let entryId = res.data.entryId
|
||||||
if (id != null) entryId = id
|
if (id != null) entryId = id
|
||||||
ajaxSideMenu(entryId)
|
ajaxSideMenu(entryId)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
let message = 'Prišlo je do napake.'
|
let message = i18next.t('Prišlo je do napake.')
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
message = error.response.data
|
message = error.response.data
|
||||||
} else if (error.request) {
|
} else if (error.request) {
|
||||||
message = 'Strežnik ni dosegljiv. Poskusite kasneje.'
|
message = i18next.t('Strežnik ni dosegljiv. Poskusite kasneje.')
|
||||||
}
|
}
|
||||||
messageContainer.textContent = message
|
messageContainer.textContent = message
|
||||||
} finally {
|
} finally {
|
||||||
@@ -806,11 +950,11 @@ function initDictionaries() {
|
|||||||
})
|
})
|
||||||
renderTerms(data, entryId)
|
renderTerms(data, entryId)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
let message = 'Prišlo je do napake.'
|
let message = i18next.t('Prišlo je do napake.')
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
message = error.response.data
|
message = error.response.data
|
||||||
} else if (error.request) {
|
} else if (error.request) {
|
||||||
message = 'Strežnik ni dosegljiv. Poskusite kasneje.'
|
message = i18next.t('Strežnik ni dosegljiv. Poskusite kasneje.')
|
||||||
}
|
}
|
||||||
messageContainer.textContent = message
|
messageContainer.textContent = message
|
||||||
}
|
}
|
||||||
@@ -829,7 +973,7 @@ function initDictionaries() {
|
|||||||
selectNextEntry(selectedIndex)
|
selectNextEntry(selectedIndex)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
const message = 'Prišlo je do napake.'
|
const message = i18next.t('Prišlo je do napake.')
|
||||||
messageContainer.textContent = message
|
messageContainer.textContent = message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -870,21 +1014,27 @@ function initDictionaries() {
|
|||||||
/"/g,
|
/"/g,
|
||||||
'"'
|
'"'
|
||||||
)}`
|
)}`
|
||||||
} else labelElement.textContent = '[ni termina]'
|
} else labelElement.textContent = i18next.t('[ni termina]')
|
||||||
} else labelElement.textContent = '[ni termina]'
|
} else labelElement.textContent = i18next.t('[ni termina]')
|
||||||
} else {
|
} else {
|
||||||
if (el.isValid) {
|
if (el.isValid) {
|
||||||
if (el.isPublished) {
|
if (el.isPublished) {
|
||||||
labelElement.textContent = sloTerm
|
labelElement.textContent = `${sloTerm} ${
|
||||||
|
el.homonymSort ? '(' + el.homonymSort + ')' : ''
|
||||||
|
}`
|
||||||
labelElement.className =
|
labelElement.className =
|
||||||
'terms-label term-good-btn btn p-2 ms-2 me-3 text-truncate justify-content-start d-inline-block'
|
'terms-label term-good-btn btn p-2 ms-2 me-3 text-truncate justify-content-start d-inline-block'
|
||||||
} else {
|
} else {
|
||||||
labelElement.className =
|
labelElement.className =
|
||||||
'terms-label btn p-2 ms-2 me-3 text-truncate justify-content-start d-inline-block'
|
'terms-label btn p-2 ms-2 me-3 text-truncate justify-content-start d-inline-block'
|
||||||
labelElement.textContent = sloTerm
|
labelElement.textContent = `${sloTerm} ${
|
||||||
|
el.homonymSort ? '(' + el.homonymSort + ')' : ''
|
||||||
|
}`
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
labelElement.textContent = sloTerm
|
labelElement.textContent = `${sloTerm} ${
|
||||||
|
el.homonymSort ? '(' + el.homonymSort + ')' : ''
|
||||||
|
}`
|
||||||
labelElement.className =
|
labelElement.className =
|
||||||
'terms-label not-valid-not-published btn p-2 ms-2 me-3 text-truncate justify-content-start d-inline-block'
|
'terms-label not-valid-not-published btn p-2 ms-2 me-3 text-truncate justify-content-start d-inline-block'
|
||||||
}
|
}
|
||||||
@@ -949,7 +1099,6 @@ function initDictionaries() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
|
||||||
const resultsListEl = document.getElementById('page-results')
|
const resultsListEl = document.getElementById('page-results')
|
||||||
const dictionaryId = document.getElementById('subareas-dict-id').value
|
const dictionaryId = document.getElementById('subareas-dict-id').value
|
||||||
|
|
||||||
@@ -957,18 +1106,19 @@ function initDictionaries() {
|
|||||||
|
|
||||||
async function onPageChange(newPage) {
|
async function onPageChange(newPage) {
|
||||||
try {
|
try {
|
||||||
const { page, numberOfAllPages, results } = await getDataForPage(
|
const results = await getDataForPage(newPage)
|
||||||
newPage
|
|
||||||
)
|
const numberOfAllPages = +results.headers['number-of-all-pages']
|
||||||
|
const page = +results.headers.page
|
||||||
removeAllChildNodes(resultsListEl)
|
removeAllChildNodes(resultsListEl)
|
||||||
renderResults(results)
|
renderResults(results.data)
|
||||||
updatePager(page, numberOfAllPages)
|
updatePager(page, numberOfAllPages)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
let message = 'Prišlo je do napake.'
|
let message = i18next.t('Prišlo je do napake.')
|
||||||
if (error.response?.data) {
|
if (error.response?.data) {
|
||||||
message = error.response.data
|
message = error.response.data
|
||||||
} else if (error.request) {
|
} else if (error.request) {
|
||||||
message = 'Strežnik ni dosegljiv. Poskusite kasneje.'
|
message = i18next.t('Strežnik ni dosegljiv. Poskusite kasneje.')
|
||||||
}
|
}
|
||||||
alert(message)
|
alert(message)
|
||||||
updatePager()
|
updatePager()
|
||||||
@@ -976,57 +1126,56 @@ function initDictionaries() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getDataForPage(page) {
|
async function getDataForPage(page) {
|
||||||
const url = `/api/v1/dictionaries/${dictionaryId}/listDomainLabels?p=${page}`
|
// const url = `/api/v1/dictionaries/${dictionaryId}/listDomainLabels?p=${page}`
|
||||||
const { data } = await axios.get(url)
|
const url = `/api/v1/dictionaries/${dictionaryId}/domainLabels?q=${queryBattery}&p=${page}`
|
||||||
return data
|
// const { data } = await axios.get(url)
|
||||||
|
return await axios.get(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderResults(results) {
|
function renderResults(results) {
|
||||||
results.forEach(result => {
|
replaceContainer('page-results', results)
|
||||||
const rowEl = document.createElement('tr')
|
}
|
||||||
const input = document.createElement('input')
|
|
||||||
const th = document.createElement('th')
|
|
||||||
const input2 = document.createElement('input')
|
|
||||||
const td1 = document.createElement('td')
|
|
||||||
const td2 = document.createElement('td')
|
|
||||||
const div = document.createElement('div')
|
|
||||||
const editBtn = document.createElement('button')
|
|
||||||
const deleteBtn = document.createElement('button')
|
|
||||||
const imgEditEl = document.createElement('img')
|
|
||||||
const imgDelEl = document.createElement('img')
|
|
||||||
input.value = result.id
|
|
||||||
input.type = 'hidden'
|
|
||||||
input.name = 'domainLabelId'
|
|
||||||
th.scope = 'row'
|
|
||||||
input2.className = 'form-check checkbox-table'
|
|
||||||
input2.type = 'checkbox'
|
|
||||||
input2.name = 'isVisible'
|
|
||||||
input2.disabled = true
|
|
||||||
input2.checked = !!result.isVisible
|
|
||||||
th.append(input2)
|
|
||||||
td1.className = 'tdata-area'
|
|
||||||
td1.textContent = result.name
|
|
||||||
|
|
||||||
td2.classList.add('buttons-group')
|
const updatePaginationOnFilter = axiosResult => {
|
||||||
div.classList.add('table-buttons')
|
const numberOfAllPages = +axiosResult.headers['number-of-all-pages']
|
||||||
editBtn.className = 'p-0 table-button-grp me-3 edit-row-btn'
|
const page = +axiosResult.headers.page
|
||||||
editBtn.type = 'button'
|
// removeAllChildNodes(resultsListEl)
|
||||||
imgEditEl.src = '/images/u_edit-alt.svg'
|
// renderResults(results.data)
|
||||||
deleteBtn.className = 'p-0 table-button-grp delete-row-btn'
|
// console.log(numberOfAllPages)
|
||||||
deleteBtn.dataset.bsTarget = '#alert-modal'
|
// console.log(page)
|
||||||
deleteBtn.dataset.bsToggle = 'modal'
|
// console.log(updatePager)
|
||||||
deleteBtn.type = 'button'
|
updatePager(page, numberOfAllPages)
|
||||||
imgDelEl.src = '/images/red-trash-icon.svg'
|
}
|
||||||
td2.append(div)
|
|
||||||
div.append(editBtn, deleteBtn)
|
|
||||||
editBtn.append(imgEditEl)
|
|
||||||
deleteBtn.append(imgDelEl)
|
|
||||||
|
|
||||||
rowEl.append(input, th, td1, td2)
|
/// / Due to unsual design, the function was moved inside
|
||||||
resultsListEl.appendChild(rowEl)
|
function searchController() {
|
||||||
|
if (window.location.pathname.includes('podrocne-oznake')) {
|
||||||
|
queryBattery = document.getElementById('input-search').value
|
||||||
|
// console.log(queryBattery)
|
||||||
|
axios
|
||||||
|
.get(
|
||||||
|
`/api/v1/dictionaries/${
|
||||||
|
window.location.pathname.split('/')[2]
|
||||||
|
}/domainLabels?q=${queryBattery}&p=${1}`
|
||||||
|
)
|
||||||
|
.then(result => {
|
||||||
|
updatePaginationOnFilter(result)
|
||||||
|
replaceContainer('page-results', result.data)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const inlineSearchButton = document.getElementById('inline-search-btn')
|
||||||
|
|
||||||
|
if (inlineSearchButton) {
|
||||||
|
inlineSearchButton.addEventListener('click', searchController)
|
||||||
|
$('#input-search').on('keyup', function (e) {
|
||||||
|
if (e.code === 'Enter' || e.code === 'NumpadEnter') {
|
||||||
|
searchController()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/// //
|
||||||
}
|
}
|
||||||
|
|
||||||
if (/\/slovarji\/\d+\/napredno/.test(currentPagePath)) {
|
if (/\/slovarji\/\d+\/napredno/.test(currentPagePath)) {
|
||||||
@@ -1217,8 +1366,8 @@ function mobileMoveContent() {
|
|||||||
currentPagePath.includes('slovarji') &&
|
currentPagePath.includes('slovarji') &&
|
||||||
!currentPagePath.includes('admin')
|
!currentPagePath.includes('admin')
|
||||||
)
|
)
|
||||||
navTitle.textContent = 'Urejanje'
|
navTitle.textContent = i18next.t('Urejanje')
|
||||||
else navTitle.textContent = 'Administrator'
|
else navTitle.textContent = i18next.t('Administracija')
|
||||||
siteHeading.style.display = 'block'
|
siteHeading.style.display = 'block'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1310,9 +1459,9 @@ function createNewAreaInput(pageForm) {
|
|||||||
divSmallNameArea.className = 'author mt-4 added-field'
|
divSmallNameArea.className = 'author mt-4 added-field'
|
||||||
divSubjectName.className = 'subject-name'
|
divSubjectName.className = 'subject-name'
|
||||||
spanInputNameTxtSlo.className = 'input-name-txt'
|
spanInputNameTxtSlo.className = 'input-name-txt'
|
||||||
spanInputNameTxtSlo.textContent = 'NOVO PODPODROČJE (slovensko)'
|
spanInputNameTxtSlo.textContent = i18next.t('NOVO PODPODROČJE (slovensko)')
|
||||||
spanInputNameTxtEng.className = 'input-name-txt mt-4'
|
spanInputNameTxtEng.className = 'input-name-txt mt-4'
|
||||||
spanInputNameTxtEng.textContent = 'NOVO PODPODROČJE (angleško)'
|
spanInputNameTxtEng.textContent = i18next.t('NOVO PODPODROČJE (angleško)')
|
||||||
divRow.className = 'row align-items-center'
|
divRow.className = 'row align-items-center'
|
||||||
divRow2.className = 'row align-items-center'
|
divRow2.className = 'row align-items-center'
|
||||||
divEnglishInput.className = 'mt-4'
|
divEnglishInput.className = 'mt-4'
|
||||||
@@ -1333,11 +1482,12 @@ function createNewAreaInput(pageForm) {
|
|||||||
divColSm.className = 'col-sm mt-3 mt-sm-0'
|
divColSm.className = 'col-sm mt-3 mt-sm-0'
|
||||||
divColSm2.className = 'col-sm mt-3 mt-sm-0'
|
divColSm2.className = 'col-sm mt-3 mt-sm-0'
|
||||||
spanNameInfoTxt.className = 'd-sm-inline name-info-txt ms-xxl-3 ms-md-3'
|
spanNameInfoTxt.className = 'd-sm-inline name-info-txt ms-xxl-3 ms-md-3'
|
||||||
spanNameInfoTxt.textContent =
|
spanNameInfoTxt.textContent = i18next.t(
|
||||||
'Vpišite novo podpodročje. Na seznamu podpodročij bo vidno takoj po potrditvi administratorja portala.'
|
'Vpišite novo podpodročje. Na seznamu podpodročij bo vidno takoj po potrditvi administratorja portala.'
|
||||||
|
)
|
||||||
spanNameInfoTxtEng.className =
|
spanNameInfoTxtEng.className =
|
||||||
'd-sm-inline name-info-txt ms-xxl-3 ms-md-3 mt-4'
|
'd-sm-inline name-info-txt ms-xxl-3 ms-md-3 mt-4'
|
||||||
spanNameInfoTxtEng.textContent = 'Novo podpodročje (angleško).'
|
spanNameInfoTxtEng.textContent = i18next.t('Novo podpodročje (angleško).')
|
||||||
|
|
||||||
divSmallNameArea.appendChild(divSubjectName)
|
divSmallNameArea.appendChild(divSubjectName)
|
||||||
divSubjectName.appendChild(spanInputNameTxtSlo)
|
divSubjectName.appendChild(spanInputNameTxtSlo)
|
||||||
@@ -1468,6 +1618,10 @@ function changePageContent(el) {
|
|||||||
const collapsableBtn = document.querySelectorAll('.collapsable-entry-btn')
|
const collapsableBtn = document.querySelectorAll('.collapsable-entry-btn')
|
||||||
const addedFields = document.querySelectorAll('.added-field')
|
const addedFields = document.querySelectorAll('.added-field')
|
||||||
const collapsibleData = document.querySelectorAll('.collapsible-data')
|
const collapsibleData = document.querySelectorAll('.collapsible-data')
|
||||||
|
const responseModal = new bootstrap.Modal(
|
||||||
|
document.getElementById('duplicate-modal'),
|
||||||
|
{ keyboard: false }
|
||||||
|
)
|
||||||
switch (el) {
|
switch (el) {
|
||||||
case 'content-preview':
|
case 'content-preview':
|
||||||
loadPreview(info)
|
loadPreview(info)
|
||||||
@@ -1530,6 +1684,7 @@ function changePageContent(el) {
|
|||||||
break
|
break
|
||||||
case 'new-entry-btn': {
|
case 'new-entry-btn': {
|
||||||
// formEditContent.reset()
|
// formEditContent.reset()
|
||||||
|
|
||||||
formEditContent.removeAttribute('data-entry-id')
|
formEditContent.removeAttribute('data-entry-id')
|
||||||
formEditContent.action = '/api/v1/entries/create'
|
formEditContent.action = '/api/v1/entries/create'
|
||||||
if (editSection.classList.contains('d-none'))
|
if (editSection.classList.contains('d-none'))
|
||||||
@@ -1546,6 +1701,16 @@ function changePageContent(el) {
|
|||||||
previewBtnEl.disabled = true
|
previewBtnEl.disabled = true
|
||||||
delete newEntryBtn.dataset.term
|
delete newEntryBtn.dataset.term
|
||||||
}
|
}
|
||||||
|
// editBtnEl.click()
|
||||||
|
editBtnEl.classList.add('active-site-link')
|
||||||
|
previewBtnEl.classList.remove('active-site-link')
|
||||||
|
commentsBtnEl.classList.remove('active-site-link')
|
||||||
|
previewButtonsGroup.classList.remove('d-none')
|
||||||
|
commentsButtonsGroup.classList.add('d-none')
|
||||||
|
previewSection.classList.add('d-none')
|
||||||
|
editSection.classList.remove('d-none')
|
||||||
|
commentsSection.classList.add('d-none')
|
||||||
|
resizeFields.forEach(el => autoResize(el))
|
||||||
authorEl.textContent = ''
|
authorEl.textContent = ''
|
||||||
versionEl.textContent = 'Verzija 1'
|
versionEl.textContent = 'Verzija 1'
|
||||||
previewAuthor.textContent = ''
|
previewAuthor.textContent = ''
|
||||||
@@ -1558,15 +1723,19 @@ function changePageContent(el) {
|
|||||||
deleteEntry.disabled = true
|
deleteEntry.disabled = true
|
||||||
changeCollapsedContent()
|
changeCollapsedContent()
|
||||||
if (addedFields.length) addedFields.forEach(el => el.remove())
|
if (addedFields.length) addedFields.forEach(el => el.remove())
|
||||||
editBtnEl.click()
|
|
||||||
termInputField.focus()
|
|
||||||
const messageContainer = document.querySelectorAll('.message-container')
|
const messageContainer = document.querySelectorAll('.message-container')
|
||||||
messageContainer.forEach(el => el.classList.add('d-none'))
|
messageContainer.forEach(el => el.classList.add('d-none'))
|
||||||
window.scrollTo({ top: 0, behavior: 'smooth' })
|
window.scrollTo({ top: 0, behavior: 'smooth' })
|
||||||
selectedEntry.classList.remove('selected-term-btn')
|
selectedEntry.classList.remove('selected-term-btn')
|
||||||
$('.multiple').val(null).trigger('change')
|
$('.multiple').val(null).trigger('change')
|
||||||
$('.without-dropdown').val(null).trigger('change')
|
$('.without-dropdown').val(null).trigger('change')
|
||||||
editBtnEl.click()
|
// editBtnEl.click()
|
||||||
|
// termInputField.addEventListener('focus', () =>
|
||||||
|
// showMCButtons(termInputField, 'link')
|
||||||
|
// )
|
||||||
|
termInputField.focus()
|
||||||
|
termInputField.click()
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'first':
|
case 'first':
|
||||||
@@ -1610,7 +1779,6 @@ function changePageContent(el) {
|
|||||||
commentsBtnEl.disabled = true
|
commentsBtnEl.disabled = true
|
||||||
commentsBtnEl.classList.add('disabled')
|
commentsBtnEl.classList.add('disabled')
|
||||||
showDates.disabled = true
|
showDates.disabled = true
|
||||||
duplicateEntry.disabled = true
|
|
||||||
deleteEntry.disabled = true
|
deleteEntry.disabled = true
|
||||||
changeCollapsedContent()
|
changeCollapsedContent()
|
||||||
editBtnEl.click()
|
editBtnEl.click()
|
||||||
@@ -1618,6 +1786,8 @@ function changePageContent(el) {
|
|||||||
window.scrollTo({ top: 0, behavior: 'smooth' })
|
window.scrollTo({ top: 0, behavior: 'smooth' })
|
||||||
selectedEntry.classList.remove('selected-term-btn')
|
selectedEntry.classList.remove('selected-term-btn')
|
||||||
editBtnEl.click()
|
editBtnEl.click()
|
||||||
|
responseModal.toggle()
|
||||||
|
duplicateEntry.disabled = true
|
||||||
break
|
break
|
||||||
case 'true':
|
case 'true':
|
||||||
spanFilterText.classList.remove('normal-gray-label')
|
spanFilterText.classList.remove('normal-gray-label')
|
||||||
@@ -1798,8 +1968,9 @@ function loadPreview(info) {
|
|||||||
: linksArr[i][key] === 'broader'
|
: linksArr[i][key] === 'broader'
|
||||||
? 'BT:'
|
? 'BT:'
|
||||||
: ''
|
: ''
|
||||||
|
linkedTerms.innerHTML += keyTxt + ' ' + key
|
||||||
linkedTerms.innerHTML += keyTxt + ' ' + key + ' '
|
if (parseInt(i) !== linksArr.length - 1) linkedTerms.innerHTML += ', '
|
||||||
|
else linkedTerms.innerHTML += ' '
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else changeClasses(previewLinkedTerms, 'hide')
|
} else changeClasses(previewLinkedTerms, 'hide')
|
||||||
@@ -1924,6 +2095,7 @@ function changeVersionList(versions) {
|
|||||||
dateLabel.dataset.bsCustomClass = 'dark-gray-tooltip'
|
dateLabel.dataset.bsCustomClass = 'dark-gray-tooltip'
|
||||||
dateLabel.dataset.bsPlacement = 'bottom'
|
dateLabel.dataset.bsPlacement = 'bottom'
|
||||||
dateLabel.dataset.bsToggle = 'tooltip'
|
dateLabel.dataset.bsToggle = 'tooltip'
|
||||||
|
dateLabel.dataset.bsHtml = 'true'
|
||||||
const date = new Date(el.version_time).toLocaleDateString(
|
const date = new Date(el.version_time).toLocaleDateString(
|
||||||
'sl-SL',
|
'sl-SL',
|
||||||
localeOptions
|
localeOptions
|
||||||
@@ -1932,9 +2104,16 @@ function changeVersionList(versions) {
|
|||||||
dateRadio.id = `date${el.version}`
|
dateRadio.id = `date${el.version}`
|
||||||
dateRadio.value = `${el.version}`
|
dateRadio.value = `${el.version}`
|
||||||
dateLabel.htmlFor = `date${el.version}`
|
dateLabel.htmlFor = `date${el.version}`
|
||||||
|
// new bootstrap.Tooltip(dateLabel, {
|
||||||
|
// title: `Verzija ${el.version} <br> Avtor: ${el.version_author}`
|
||||||
|
// })
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
new bootstrap.Tooltip(dateLabel, {
|
new bootstrap.Tooltip(dateLabel, {
|
||||||
title: `Verzija ${el.version}`
|
title:
|
||||||
|
i18next.t('Verzija') +
|
||||||
|
`${el.version} <br>` +
|
||||||
|
i18next.t('Avtor:') +
|
||||||
|
`${el.version_author}`
|
||||||
})
|
})
|
||||||
allDatesEl.append(dateRadio)
|
allDatesEl.append(dateRadio)
|
||||||
allDatesEl.appendChild(dateLabel)
|
allDatesEl.appendChild(dateLabel)
|
||||||
@@ -1942,6 +2121,30 @@ function changeVersionList(versions) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setLatestVersion(data) {
|
||||||
|
const latestVersionLabel = document.getElementById('latest-version-label')
|
||||||
|
// const tooltip = new bootstrap.Tooltip(latestVersionLabel, {
|
||||||
|
// title: `Verzija: ${data.version} <br> Avtor: ${data.version_author}`,
|
||||||
|
// customClass: 'dark-gray-tooltip',
|
||||||
|
// html: true,
|
||||||
|
// placement: 'bottom'
|
||||||
|
// })
|
||||||
|
// eslint-disable-next-line
|
||||||
|
const tooltip = new bootstrap.Tooltip(latestVersionLabel, {
|
||||||
|
title:
|
||||||
|
i18next.t('Verzija:') +
|
||||||
|
`${data.version} <br>` +
|
||||||
|
i18next.t('Avtor:') +
|
||||||
|
`${data.version_author}`,
|
||||||
|
customClass: 'dark-gray-tooltip',
|
||||||
|
html: true,
|
||||||
|
placement: 'bottom'
|
||||||
|
})
|
||||||
|
// Can't think of any better solution for the not disapearing label bug
|
||||||
|
const dateArea = document.querySelector('.date-scroller')
|
||||||
|
dateArea.addEventListener('click', () => tooltip.hide())
|
||||||
|
}
|
||||||
|
|
||||||
function removeOldMedia(images, audio, video) {
|
function removeOldMedia(images, audio, video) {
|
||||||
if (images !== null || audio !== null || video !== null) {
|
if (images !== null || audio !== null || video !== null) {
|
||||||
const media = [images, audio, video]
|
const media = [images, audio, video]
|
||||||
@@ -1998,7 +2201,7 @@ function showSelectedDateData(date) {
|
|||||||
// classicOverview.classList.add('d-none')
|
// classicOverview.classList.add('d-none')
|
||||||
// selectedOverview.classList.remove('d-none')
|
// selectedOverview.classList.remove('d-none')
|
||||||
btnSaveIcon.firstChild.src = '/images/u_redo.svg'
|
btnSaveIcon.firstChild.src = '/images/u_redo.svg'
|
||||||
btnSaveIcon.children[1].textContent = 'Obnovi'
|
btnSaveIcon.children[1].textContent = i18next.t('Obnovi')
|
||||||
btnSaveIcon.id = 'redo-action'
|
btnSaveIcon.id = 'redo-action'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2038,7 +2241,7 @@ function changeCollapsedContent() {
|
|||||||
|
|
||||||
function createHeadword() {
|
function createHeadword() {
|
||||||
const { termInputField, headerwordTable } = window.dictionaryElements
|
const { termInputField, headerwordTable } = window.dictionaryElements
|
||||||
if (!termInputField.value.length) alert('Vnesti morate termin')
|
if (!termInputField.value.length) alert(i18next.t('Vnesti morate termin'))
|
||||||
else {
|
else {
|
||||||
const createHeadwordGroup = document.getElementById('create-headword-group')
|
const createHeadwordGroup = document.getElementById('create-headword-group')
|
||||||
const createdHeadwordGroup = document.getElementById(
|
const createdHeadwordGroup = document.getElementById(
|
||||||
@@ -2181,21 +2384,21 @@ function addField(form, element, content) {
|
|||||||
if (formEditContent) {
|
if (formEditContent) {
|
||||||
switch (element) {
|
switch (element) {
|
||||||
case newImageBtn:
|
case newImageBtn:
|
||||||
spanName.textContent = 'SLIKA'
|
spanName.textContent = i18next.t('SLIKA')
|
||||||
inputField.name = 'image'
|
inputField.name = 'image'
|
||||||
spanNameInfoTxt.textContent = 'Nova slika.'
|
spanNameInfoTxt.textContent = i18next.t('Nova slika.')
|
||||||
if (content) inputField.value = content
|
if (content) inputField.value = content
|
||||||
break
|
break
|
||||||
case newAudioBtn:
|
case newAudioBtn:
|
||||||
spanName.textContent = 'ZVOK'
|
spanName.textContent = i18next.t('ZVOK')
|
||||||
inputField.name = 'audio'
|
inputField.name = 'audio'
|
||||||
spanNameInfoTxt.textContent = 'Nov zvok.'
|
spanNameInfoTxt.textContent = i18next.t('Nov zvok.')
|
||||||
if (content) inputField.value = content
|
if (content) inputField.value = content
|
||||||
break
|
break
|
||||||
case newVideoBtn:
|
case newVideoBtn:
|
||||||
spanName.textContent = 'VIDEO'
|
spanName.textContent = i18next.t('VIDEO')
|
||||||
inputField.name = 'video'
|
inputField.name = 'video'
|
||||||
spanNameInfoTxt.textContent = 'Nov video.'
|
spanNameInfoTxt.textContent = i18next.t('Nov video.')
|
||||||
if (content) inputField.value = content
|
if (content) inputField.value = content
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -2204,10 +2407,11 @@ function addField(form, element, content) {
|
|||||||
element.parentElement.parentElement.parentElement
|
element.parentElement.parentElement.parentElement
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
spanName.textContent = 'AVTOR'
|
spanName.textContent = i18next.t('AVTOR')
|
||||||
inputField.name = 'author'
|
inputField.name = 'author'
|
||||||
spanNameInfoTxt.textContent =
|
spanNameInfoTxt.textContent = i18next.t(
|
||||||
'Dodajte ime in priimek naslednjega avtorja slovarja..'
|
'Dodajte ime in priimek naslednjega avtorja slovarja.'
|
||||||
|
)
|
||||||
form.insertBefore(
|
form.insertBefore(
|
||||||
divMarginTop,
|
divMarginTop,
|
||||||
element.parentElement.parentElement.parentElement
|
element.parentElement.parentElement.parentElement
|
||||||
@@ -2224,7 +2428,6 @@ function addConnectionField(form, element, data) {
|
|||||||
const divRow = document.createElement('div')
|
const divRow = document.createElement('div')
|
||||||
const divColLg2 = document.createElement('div')
|
const divColLg2 = document.createElement('div')
|
||||||
const divColLg4 = document.createElement('div')
|
const divColLg4 = document.createElement('div')
|
||||||
const divColLg = document.createElement('div')
|
|
||||||
const colLg2Input = document.createElement('select')
|
const colLg2Input = document.createElement('select')
|
||||||
const inputGroup = document.createElement('div')
|
const inputGroup = document.createElement('div')
|
||||||
const inputField = document.createElement('input')
|
const inputField = document.createElement('input')
|
||||||
@@ -2243,7 +2446,6 @@ function addConnectionField(form, element, data) {
|
|||||||
divColLg2.appendChild(colLg2Input)
|
divColLg2.appendChild(colLg2Input)
|
||||||
colLg2Input.appendChild(opt)
|
colLg2Input.appendChild(opt)
|
||||||
divRow.appendChild(divColLg4)
|
divRow.appendChild(divColLg4)
|
||||||
divRow.appendChild(divColLg)
|
|
||||||
divColLg4.appendChild(inputGroup)
|
divColLg4.appendChild(inputGroup)
|
||||||
inputGroup.appendChild(inputField)
|
inputGroup.appendChild(inputField)
|
||||||
inputGroup.appendChild(spanInputGroup)
|
inputGroup.appendChild(spanInputGroup)
|
||||||
@@ -2252,32 +2454,34 @@ function addConnectionField(form, element, data) {
|
|||||||
divColSm.appendChild(spanNameInfoTxt)
|
divColSm.appendChild(spanNameInfoTxt)
|
||||||
|
|
||||||
divMarginTop.className = 'mt-4 added-field'
|
divMarginTop.className = 'mt-4 added-field'
|
||||||
divSubjectName.className = 'subject-name'
|
divSubjectName.className =
|
||||||
|
'subject-name col-sm-6 d-flex justify-content-between'
|
||||||
spanName.className = 'input-name-txt'
|
spanName.className = 'input-name-txt'
|
||||||
divRow.className = 'row align-items-center'
|
divRow.className = 'row align-items-center'
|
||||||
divColLg2.className = 'col-lg-2 col-6'
|
divColLg2.className = 'col-xl-2 col-4'
|
||||||
divColLg4.className = 'col-lg-4 mt-2 mt-lg-0'
|
divColLg4.className = 'col-8 col-xl-6 col-xxl-4'
|
||||||
divColLg.className = 'col-lg align-items-center'
|
|
||||||
colLg2Input.className = 'name-input form-select d-inline'
|
colLg2Input.className = 'name-input form-select d-inline'
|
||||||
colLg2Input.name = 'type'
|
colLg2Input.name = 'type'
|
||||||
inputGroup.className = 'input-group'
|
inputGroup.className = 'input-group'
|
||||||
inputField.className = 'name-input d-inline form-control icon-trash mc-field'
|
inputField.className =
|
||||||
|
'name-input d-inline form-control icon-trash mc-field dispatch-tab'
|
||||||
spanInputGroup.className = 'input-group-text delete-author-btn'
|
spanInputGroup.className = 'input-group-text delete-author-btn'
|
||||||
spanInputGroup.id = 'trash-icon-btn'
|
spanInputGroup.id = 'trash-icon-btn'
|
||||||
spanInputGroup.addEventListener('click', deleteField)
|
spanInputGroup.addEventListener('click', deleteField)
|
||||||
imgTrashIcon.className = 'delete-author p-0'
|
imgTrashIcon.className = 'delete-author p-0'
|
||||||
imgTrashIcon.src = '/images/red-trash-icon.svg'
|
imgTrashIcon.src = '/images/red-trash-icon.svg'
|
||||||
imgTrashIcon.alt = 'Delete'
|
imgTrashIcon.alt = 'Delete'
|
||||||
divColSm.className = 'col-sm'
|
divColSm.className = 'col d-none d-xl-flex align-items-center'
|
||||||
spanNameInfoTxt.className = 'name-info-txt mt-3'
|
spanNameInfoTxt.className = 'd-md-inline d-block name-info-txt mt-3 mt-sm-0'
|
||||||
spanName.textContent = 'POVEZAVA'
|
spanNameInfoTxt.textContent = i18next.t('Nov povezan termin.')
|
||||||
|
spanName.textContent = i18next.t('POVEZANI TERMIN')
|
||||||
inputField.name = 'links'
|
inputField.name = 'links'
|
||||||
opt.value = 'broader'
|
opt.value = 'broader'
|
||||||
opt.text = 'Širši'
|
opt.text = i18next.t('Širši')
|
||||||
opt2.value = 'related'
|
opt2.value = 'related'
|
||||||
opt3.value = 'narrow'
|
opt3.value = 'narrow'
|
||||||
opt2.text = 'Sorodni'
|
opt2.text = i18next.t('Sorodni')
|
||||||
opt3.text = 'Ožji'
|
opt3.text = i18next.t('Ožji')
|
||||||
colLg2Input.add(opt2)
|
colLg2Input.add(opt2)
|
||||||
colLg2Input.add(opt)
|
colLg2Input.add(opt)
|
||||||
colLg2Input.add(opt3)
|
colLg2Input.add(opt3)
|
||||||
@@ -2286,6 +2490,32 @@ function addConnectionField(form, element, data) {
|
|||||||
divMarginTop,
|
divMarginTop,
|
||||||
element.parentElement.parentElement.parentElement
|
element.parentElement.parentElement.parentElement
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Create mixed content buttons above input field
|
||||||
|
const mcDiv = document.createElement('div')
|
||||||
|
const supscriptBtn = document.createElement('button')
|
||||||
|
const subscriptBtn = document.createElement('button')
|
||||||
|
const supImg = document.createElement('img')
|
||||||
|
const subImg = document.createElement('img')
|
||||||
|
|
||||||
|
mcDiv.appendChild(supscriptBtn)
|
||||||
|
mcDiv.appendChild(subscriptBtn)
|
||||||
|
supscriptBtn.appendChild(supImg)
|
||||||
|
subscriptBtn.appendChild(subImg)
|
||||||
|
|
||||||
|
mcDiv.className = 'mc-buttons-group d-none'
|
||||||
|
supscriptBtn.className = 'mc-button mc-supscript'
|
||||||
|
supscriptBtn.type = 'button'
|
||||||
|
subscriptBtn.className = 'mc-button mc-subscript'
|
||||||
|
subscriptBtn.type = 'button'
|
||||||
|
supImg.src = '/images/superscript.svg'
|
||||||
|
supImg.className = 'mc-center-img'
|
||||||
|
subImg.className = 'mc-center-img'
|
||||||
|
subImg.src = '/images/subscript.svg'
|
||||||
|
|
||||||
|
divSubjectName.appendChild(mcDiv)
|
||||||
|
// eslint-disable-next-line
|
||||||
|
inputField.addEventListener('focus', () => showMCButtons(inputField, 'link'))
|
||||||
if (data) {
|
if (data) {
|
||||||
inputField.value = data.link
|
inputField.value = data.link
|
||||||
colLg2Input.value = data.type
|
colLg2Input.value = data.type
|
||||||
@@ -2336,14 +2566,14 @@ function handleAreasClick({ target }) {
|
|||||||
const saveButton = document.createElement('button')
|
const saveButton = document.createElement('button')
|
||||||
saveButton.type = 'button'
|
saveButton.type = 'button'
|
||||||
cancelButton.type = 'button'
|
cancelButton.type = 'button'
|
||||||
cancelButton.textContent = 'Prekliči'
|
cancelButton.textContent = i18next.t('Prekliči')
|
||||||
cancelButton.className = 'btn btn-secondary me-2'
|
cancelButton.className = 'btn btn-secondary me-2'
|
||||||
cancelButton.style.height = '33px'
|
cancelButton.style.height = '33px'
|
||||||
cancelButton.style.width = '105px'
|
cancelButton.style.width = '105px'
|
||||||
cancelButton.addEventListener('click', () =>
|
cancelButton.addEventListener('click', () =>
|
||||||
abortEditing(newButtonGroup, tableButtons, tDataArea)
|
abortEditing(newButtonGroup, tableButtons, tDataArea)
|
||||||
)
|
)
|
||||||
saveButton.textContent = 'POTRDI'
|
saveButton.textContent = i18next.t('POTRDI')
|
||||||
saveButton.type = 'button'
|
saveButton.type = 'button'
|
||||||
saveButton.className = 'btn btn-primary'
|
saveButton.className = 'btn btn-primary'
|
||||||
saveButton.style.height = '33px'
|
saveButton.style.height = '33px'
|
||||||
@@ -2666,7 +2896,7 @@ function checkLanguages() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$('.summernote').summernote({
|
$('.summernote').summernote({
|
||||||
placeholder: 'Na kratko opišite zasnovo in namen slovarja.',
|
placeholder: i18next.t('Na kratko opišite zasnovo in namen slovarja.'),
|
||||||
height: 300,
|
height: 300,
|
||||||
minheight: 150,
|
minheight: 150,
|
||||||
toolbar: [
|
toolbar: [
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/* globals axios, i18next */
|
||||||
|
|
||||||
|
const exportForm = document.forms['dictionary-export']
|
||||||
|
|
||||||
|
exportForm.addEventListener('submit', async event => {
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
const payload = Object.fromEntries(new FormData(event.target))
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.post(event.target.action, payload)
|
||||||
|
// TODO This (page refresh) is a temporary hack. Replace with proper UI updating logic.
|
||||||
|
location.reload()
|
||||||
|
} catch (error) {
|
||||||
|
let message = i18next.t('Prišlo je do napake.')
|
||||||
|
if (error.response) {
|
||||||
|
message = error.response.data
|
||||||
|
} else if (error.request) {
|
||||||
|
message = i18next.t('Strežnik ni dosegljiv. Poskusite kasneje.')
|
||||||
|
}
|
||||||
|
|
||||||
|
alert(message)
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -1,11 +1,16 @@
|
|||||||
/* global axios, initPagination, removeAllChildNodes */
|
/* global axios, initPagination, removeAllChildNodes, i18next, dictionaryId */
|
||||||
{
|
{
|
||||||
const selectorEl = document.getElementById('select-extraction-name')
|
const selectorEl = document.getElementById('select-extraction-name')
|
||||||
selectorEl.addEventListener('change', () => loadCandidates(selectorEl.value))
|
selectorEl.addEventListener('change', () => loadCandidates(selectorEl.value))
|
||||||
const resultsListEl = document.getElementById('page-results')
|
const resultsListEl = document.getElementById('page-results')
|
||||||
|
const importFormEl = document.getElementById('import-form')
|
||||||
|
const importButtonEl = document.getElementById('import-btn')
|
||||||
let termCandidates
|
let termCandidates
|
||||||
let hitsPerPage
|
let hitsPerPage
|
||||||
let numberOfAllPages
|
let numberOfAllPages
|
||||||
|
|
||||||
|
importFormEl.addEventListener('submit', submitForm)
|
||||||
|
|
||||||
async function loadCandidates(id) {
|
async function loadCandidates(id) {
|
||||||
try {
|
try {
|
||||||
const { data } = await axios.get(
|
const { data } = await axios.get(
|
||||||
@@ -18,6 +23,8 @@
|
|||||||
const results = getDataForFirstPage(data)
|
const results = getDataForFirstPage(data)
|
||||||
renderResults(results)
|
renderResults(results)
|
||||||
updateDemoPager(1, numberOfAllPages)
|
updateDemoPager(1, numberOfAllPages)
|
||||||
|
importFormEl.action = `/api/v1/dictionaries/${dictionaryId}/import-extraction/${id}`
|
||||||
|
importButtonEl.disabled = false
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
}
|
}
|
||||||
@@ -64,9 +71,27 @@
|
|||||||
tdId.textContent = sequentialCount
|
tdId.textContent = sequentialCount
|
||||||
tdName.textContent = candidate.kanonicnaoblika
|
tdName.textContent = candidate.kanonicnaoblika
|
||||||
tdSize.textContent = candidate.ranking
|
tdSize.textContent = candidate.ranking
|
||||||
tdDate.textContent = candidate.pogostostpojavljanja
|
tdDate.textContent = candidate.pogostostpojavljanja[0]
|
||||||
rowEl.append(tdId, tdName, tdSize, tdDate)
|
rowEl.append(tdId, tdName, tdSize, tdDate)
|
||||||
resultsListEl.appendChild(rowEl)
|
resultsListEl.appendChild(rowEl)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function submitForm(event) {
|
||||||
|
event.preventDefault()
|
||||||
|
const payload = Object.fromEntries(new FormData(event.target))
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.post(event.target.action, payload)
|
||||||
|
alert(i18next.t('SLOVAR UVOŽEN'))
|
||||||
|
} catch (error) {
|
||||||
|
let message = i18next.t('Prišlo je do napake.')
|
||||||
|
if (error.response) {
|
||||||
|
message = error.response.data
|
||||||
|
} else if (error.request) {
|
||||||
|
message = i18next.t('Strežnik ni dosegljiv. Poskusite kasneje.')
|
||||||
|
}
|
||||||
|
alert(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
const nameEl = document.getElementById('name')
|
||||||
|
nameEl.addEventListener('input', enableButton)
|
||||||
|
|
||||||
|
function enableButton() {
|
||||||
|
const disabledBtn = document.getElementById('mpbtn')
|
||||||
|
disabledBtn.classList.remove('disabled')
|
||||||
|
}
|
||||||
@@ -1,16 +1,20 @@
|
|||||||
/* global axios, removeAllChildNodes, bootstrap */
|
/* global axios, removeAllChildNodes, bootstrap, i18next */
|
||||||
|
|
||||||
|
const fileUploadContainerEl = document.getElementById('upload-files-container')
|
||||||
const fileUploadForm = document.forms['upload-files']
|
const fileUploadForm = document.forms['upload-files']
|
||||||
const fileInputEl = fileUploadForm.querySelector('input[type="file"]')
|
const fileInputEl = fileUploadForm.querySelector('input[type="file"]')
|
||||||
const filesListEl = document.getElementById('files-list')
|
const filesListEl = document.getElementById('files-list')
|
||||||
const dropArea = document.querySelector('.drag-area')
|
const dropArea = document.querySelector('.drag-area')
|
||||||
const dragButton = dropArea.querySelector('button')
|
const dragButton = dropArea.querySelector('button')
|
||||||
const dragInput = dropArea.querySelector('input')
|
const dragInput = dropArea.querySelector('input')
|
||||||
const modalSpinner = new bootstrap.Modal(
|
|
||||||
document.getElementById('modal-spinner')
|
|
||||||
)
|
|
||||||
const modalAlert = new bootstrap.Modal(document.getElementById('alert-modal'))
|
const modalAlert = new bootstrap.Modal(document.getElementById('alert-modal'))
|
||||||
let file
|
let file
|
||||||
|
let fileListElCount = filesListEl.childElementCount
|
||||||
|
let isUploadInProgress = false
|
||||||
|
let isDeletionInProgress = false
|
||||||
|
const existingFileNames = [...filesListEl.children].map(
|
||||||
|
fileListEl => fileListEl.querySelector('.file-name').textContent
|
||||||
|
)
|
||||||
|
|
||||||
dragButton.onclick = () => {
|
dragButton.onclick = () => {
|
||||||
dragInput.click()
|
dragInput.click()
|
||||||
@@ -68,96 +72,216 @@ fileInputEl.addEventListener('change', submitFiles)
|
|||||||
filesListEl.addEventListener('click', handleFileClick)
|
filesListEl.addEventListener('click', handleFileClick)
|
||||||
|
|
||||||
async function submitFiles() {
|
async function submitFiles() {
|
||||||
// TODO Lock additional submits for the duration of this function execution?
|
if (isUploadInProgress) return
|
||||||
|
isUploadInProgress = true
|
||||||
|
fileUploadContainerEl.hidden = true
|
||||||
const MAX_FILE_SIZE = 10 ** 9 // 1 GB
|
const MAX_FILE_SIZE = 10 ** 9 // 1 GB
|
||||||
|
const filesToUpload = []
|
||||||
const failedUploads = []
|
const failedUploads = []
|
||||||
|
const succeededFileListEls = []
|
||||||
modalSpinner.toggle()
|
let didRemoveAnyFileListEls = false
|
||||||
|
|
||||||
for (const file of fileInputEl.files) {
|
for (const file of fileInputEl.files) {
|
||||||
|
const filename = file.name
|
||||||
|
|
||||||
|
const indexOfSameNamed = existingFileNames.indexOf(filename)
|
||||||
|
if (indexOfSameNamed !== -1) {
|
||||||
|
filesListEl.children[indexOfSameNamed].remove()
|
||||||
|
existingFileNames.splice(indexOfSameNamed, 1)
|
||||||
|
didRemoveAnyFileListEls = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileListEl = createFileListEl(filename)
|
||||||
|
filesListEl.appendChild(fileListEl)
|
||||||
|
existingFileNames.push(filename)
|
||||||
|
|
||||||
if (file.size > MAX_FILE_SIZE) {
|
if (file.size > MAX_FILE_SIZE) {
|
||||||
const failedUpload = {
|
const failedUpload = {
|
||||||
filename: file.name,
|
filename,
|
||||||
message: 'File too large. Must not be over 1 GB.'
|
message: 'File too large. Must not be over 1 GB.'
|
||||||
}
|
}
|
||||||
failedUploads.push(failedUpload)
|
failedUploads.push(failedUpload)
|
||||||
|
updateFileListEl(fileListEl, { status: 'failed' })
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filesToUpload.push([file, fileListEl])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (didRemoveAnyFileListEls) reindexFileListEls()
|
||||||
|
|
||||||
|
for (const [file, fileListEl] of filesToUpload) {
|
||||||
|
try {
|
||||||
const payload = new FormData()
|
const payload = new FormData()
|
||||||
payload.set(fileInputEl.name, file)
|
payload.set(fileInputEl.name, file)
|
||||||
try {
|
const { data: fileStats } = await axios.put(apiEndpointBase, payload, {
|
||||||
await axios.put(apiEndpointBase, payload)
|
onUploadProgress: displayUploadProgress(fileListEl)
|
||||||
|
})
|
||||||
|
succeededFileListEls.push(fileListEl)
|
||||||
|
updateFileListEl(fileListEl, { status: 'success', fileStats })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const failedUpload = {
|
const failedUpload = {
|
||||||
filename: file.name,
|
filename: file.name,
|
||||||
message: error.response.data
|
message: error.response.data
|
||||||
}
|
}
|
||||||
failedUploads.push(failedUpload)
|
failedUploads.push(failedUpload)
|
||||||
|
updateFileListEl(fileListEl, { status: 'failed' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
const { data: files } = await axios.get(apiEndpointBase)
|
|
||||||
updateFilesList(files)
|
|
||||||
} catch {
|
|
||||||
alert('Pri posodobljanju seznama naloženih datotek je prišlo do napake.')
|
|
||||||
}
|
|
||||||
|
|
||||||
fileInputEl.value = ''
|
fileInputEl.value = ''
|
||||||
displayFailedUploads(failedUploads)
|
succeededFileListEls.forEach(el => updateFileListEl(el, { status: 'done' }))
|
||||||
modalSpinner.toggle()
|
if (failedUploads.length) displayFailedUploads(failedUploads)
|
||||||
|
isUploadInProgress = false
|
||||||
|
fileUploadContainerEl.hidden = false
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateFilesList(files) {
|
function createFileListEl(filename) {
|
||||||
removeAllChildNodes(filesListEl)
|
const indexEl = document.createElement('td')
|
||||||
files.forEach(({ filename, size, timeModified }, index) => {
|
indexEl.className = 'file-index'
|
||||||
const rowEl = document.createElement('tr')
|
indexEl.textContent = ++fileListElCount
|
||||||
const tdId = document.createElement('td')
|
|
||||||
const tdName = document.createElement('td')
|
const nameEl = document.createElement('td')
|
||||||
const tdSize = document.createElement('td')
|
nameEl.className = 'file-name'
|
||||||
const tdDate = document.createElement('td')
|
nameEl.textContent = filename
|
||||||
const tdDelete = document.createElement('td')
|
|
||||||
tdId.textContent = index + 1
|
const sizeEl = document.createElement('td')
|
||||||
tdName.textContent = filename
|
sizeEl.className = 'file-size'
|
||||||
tdName.className = 'filename'
|
|
||||||
tdSize.textContent = size
|
const dateEl = document.createElement('td')
|
||||||
const formattedDate = new Date(timeModified).toLocaleDateString('sl-SL')
|
dateEl.className = 'file-date-modified'
|
||||||
tdDate.textContent = formattedDate
|
|
||||||
const delBtn = document.createElement('button')
|
const progressBarId = `progress-bar-${fileListElCount}`
|
||||||
delBtn.className = 'p-0 delete-file delete-btn-table'
|
const progressLabelEl = document.createElement('label')
|
||||||
delBtn.type = 'button'
|
progressLabelEl.className = 'me-1'
|
||||||
const deleteImg = document.createElement('img')
|
progressLabelEl.for = progressBarId
|
||||||
deleteImg.src = '/images/red-trash-icon.svg'
|
progressLabelEl.textContent = '0%'
|
||||||
deleteImg.alt = 'Izbriši'
|
const progressBarEl = document.createElement('progress')
|
||||||
const delSpan = document.createElement('span')
|
progressBarEl.id = progressBarId
|
||||||
delSpan.className = 'ms-2'
|
progressBarEl.className = 'upload-progress'
|
||||||
delSpan.textContent = 'Briši'
|
progressBarEl.value = 0
|
||||||
delBtn.appendChild(deleteImg)
|
progressBarEl.max = 100
|
||||||
delBtn.appendChild(delSpan)
|
const lastEl = document.createElement('td')
|
||||||
tdDelete.appendChild(delBtn)
|
lastEl.className = 'file-last-cell'
|
||||||
rowEl.append(tdId, tdName, tdSize, tdDate, tdDelete)
|
lastEl.append(progressLabelEl, progressBarEl)
|
||||||
filesListEl.appendChild(rowEl)
|
|
||||||
})
|
const fileListEl = document.createElement('tr')
|
||||||
|
fileListEl.append(indexEl, nameEl, sizeEl, dateEl, lastEl)
|
||||||
|
|
||||||
|
return fileListEl
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFileListEl(fileListEl, { status, fileStats, index }) {
|
||||||
|
if (status === 'success') {
|
||||||
|
const nameEl = fileListEl.querySelector('.file-name')
|
||||||
|
nameEl.textContent = fileStats.filename
|
||||||
|
|
||||||
|
const sizeEl = fileListEl.querySelector('.file-size')
|
||||||
|
sizeEl.textContent = fileStats.size
|
||||||
|
|
||||||
|
const dateEl = fileListEl.querySelector('.file-date-modified')
|
||||||
|
const formattedDate = new Date(fileStats.timeModified).toLocaleDateString(
|
||||||
|
'sl-SL'
|
||||||
|
)
|
||||||
|
dateEl.textContent = formattedDate
|
||||||
|
|
||||||
|
const successImgEl = document.createElement('img')
|
||||||
|
successImgEl.src = '/images/valid.svg'
|
||||||
|
successImgEl.alt = i18next.t('Datoteka uspešno naložena')
|
||||||
|
const lastEl = fileListEl.querySelector('.file-last-cell')
|
||||||
|
removeAllChildNodes(lastEl)
|
||||||
|
lastEl.append(successImgEl)
|
||||||
|
} else if (status === 'done') {
|
||||||
|
const deleteImgEl = document.createElement('img')
|
||||||
|
deleteImgEl.src = '/images/red-trash-icon.svg'
|
||||||
|
deleteImgEl.alt = ''
|
||||||
|
const deleteSpanEl = document.createElement('span')
|
||||||
|
deleteSpanEl.className = 'ms-2'
|
||||||
|
deleteSpanEl.textContent = 'Briši'
|
||||||
|
const deleteButtonEl = document.createElement('button')
|
||||||
|
deleteButtonEl.className = 'p-0 delete-file delete-btn-table'
|
||||||
|
deleteButtonEl.append(deleteImgEl, deleteSpanEl)
|
||||||
|
const lastEl = fileListEl.querySelector('.file-last-cell')
|
||||||
|
removeAllChildNodes(lastEl)
|
||||||
|
lastEl.append(deleteButtonEl)
|
||||||
|
} else if (status === 'reindex') {
|
||||||
|
const indexEl = fileListEl.querySelector('.file-index')
|
||||||
|
indexEl.textContent = index + 1
|
||||||
|
} else if (status === 'failed') {
|
||||||
|
const failImgEl = document.createElement('img')
|
||||||
|
failImgEl.src = '/images/x_red.svg'
|
||||||
|
failImgEl.alt = i18next.t('Napaka pri nalaganju datoteke')
|
||||||
|
const lastEl = fileListEl.querySelector('.file-last-cell')
|
||||||
|
removeAllChildNodes(lastEl)
|
||||||
|
lastEl.append(failImgEl)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function displayFailedUploads(failedUploads) {
|
function displayFailedUploads(failedUploads) {
|
||||||
|
const modalContentEl = modalAlert._element.querySelector(
|
||||||
|
'.modal-alert-content'
|
||||||
|
)
|
||||||
|
modalContentEl.classList.remove('d-flex')
|
||||||
|
|
||||||
|
const descriptionEl = modalAlert._element.querySelector('#alert-text')
|
||||||
|
descriptionEl.textContent = i18next.t(
|
||||||
|
'Naslednje datoteke niso bile naložene zaradi navedenih razlogov:'
|
||||||
|
)
|
||||||
|
|
||||||
|
modalContentEl.querySelector('ul')?.remove()
|
||||||
|
|
||||||
|
const listEl = document.createElement('ul')
|
||||||
|
modalContentEl.append(listEl)
|
||||||
|
|
||||||
failedUploads.forEach(({ filename, message }) => {
|
failedUploads.forEach(({ filename, message }) => {
|
||||||
const alertText = modalAlert.querySelector('#alert-text')
|
const bulletEl = document.createElement('li')
|
||||||
alertText.textContent = `NAPAKA - Ime datoteke: ${filename}, razlog: ${message}`
|
bulletEl.textContent = `${filename} - ${message}`
|
||||||
modalAlert.toggle()
|
listEl.appendChild(bulletEl)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
modalAlert.toggle()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleFileClick(e) {
|
async function handleFileClick(e) {
|
||||||
if (e.target.closest('.delete-file')) {
|
if (e.target.closest('.delete-file')) {
|
||||||
|
if (isUploadInProgress) {
|
||||||
|
alert(
|
||||||
|
i18next.t('Brisanje je onemogočeno, doker se nalagajo nove datoteke.')
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (isDeletionInProgress) {
|
||||||
|
alert(i18next.t('Brisanje je onemogočeno, saj je eno še v procesu.'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isDeletionInProgress = true
|
||||||
const fileEl = e.target.closest('tr')
|
const fileEl = e.target.closest('tr')
|
||||||
const filename = fileEl.querySelector('.filename').textContent
|
const filename = fileEl.querySelector('.file-name').textContent
|
||||||
try {
|
try {
|
||||||
await axios.delete(`${apiEndpointBase}/${filename}`)
|
await axios.delete(`${apiEndpointBase}/${filename}`)
|
||||||
fileEl.remove()
|
fileEl.remove()
|
||||||
|
existingFileNames.splice(existingFileNames.indexOf(filename), 1)
|
||||||
|
reindexFileListEls()
|
||||||
|
isDeletionInProgress = false
|
||||||
} catch {
|
} catch {
|
||||||
alert('Pri brisanju datoteke je prišlo do napake.')
|
alert(i18next.t('Pri brisanju datoteke je prišlo do napake.'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function displayUploadProgress(fileListEl) {
|
||||||
|
const progressEl = fileListEl.querySelector('.upload-progress')
|
||||||
|
return function ({ loaded, total }) {
|
||||||
|
const percentCompleted = Math.round((loaded / total) * 100)
|
||||||
|
progressEl.setAttribute('value', percentCompleted)
|
||||||
|
progressEl.previousElementSibling.textContent = `${percentCompleted}%`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function reindexFileListEls() {
|
||||||
|
fileListElCount = filesListEl.childElementCount
|
||||||
|
for (let i = 0; i < fileListElCount; i++) {
|
||||||
|
const fileListEl = filesListEl.children[i]
|
||||||
|
updateFileListEl(fileListEl, { status: 'reindex', index: i })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,15 +16,20 @@ async function onListClick({ target }) {
|
|||||||
modalUseBtn.addEventListener('click', async () => {
|
modalUseBtn.addEventListener('click', async () => {
|
||||||
await axios.delete(`/api/v1/extraction/${extractionId}`)
|
await axios.delete(`/api/v1/extraction/${extractionId}`)
|
||||||
extractionEl.remove()
|
extractionEl.remove()
|
||||||
|
// TODO This (page refresh) is an unneeded workaround for an otherwise simple user friendlier solution.
|
||||||
|
location.reload()
|
||||||
})
|
})
|
||||||
} else if (target.classList.contains('btn-begin')) {
|
} else if (target.closest('.btn-begin')) {
|
||||||
const extractionEl = target.closest('.task')
|
const extractionEl = target.closest('.task')
|
||||||
const extractionId = extractionEl.dataset.id
|
const extractionId = extractionEl.dataset.id
|
||||||
await axios.put(`/api/v1/extraction/${extractionId}/begin`)
|
await axios.put(`/api/v1/extraction/${extractionId}/begin`)
|
||||||
const responseModal = new bootstrap.Modal(
|
const beginExtractionModalEl = document.getElementById('begin-response')
|
||||||
document.getElementById('begin-response')
|
const responseModal = new bootstrap.Modal(beginExtractionModalEl)
|
||||||
)
|
|
||||||
responseModal.toggle()
|
responseModal.toggle()
|
||||||
|
// TODO This (page refresh) is a temporary hack. Replace with proper UI updating logic.
|
||||||
|
beginExtractionModalEl.addEventListener('hide.bs.modal', () =>
|
||||||
|
location.reload()
|
||||||
|
)
|
||||||
} else if (target.classList.contains('btn-duplicate')) {
|
} else if (target.classList.contains('btn-duplicate')) {
|
||||||
const extractionEl = target.closest('.task')
|
const extractionEl = target.closest('.task')
|
||||||
const extractionId = extractionEl.dataset.id
|
const extractionId = extractionEl.dataset.id
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* global $, axios, bootstrap */
|
/* global $, axios, bootstrap, i18next */
|
||||||
|
|
||||||
$('.pick-multiple').select2()
|
$('.pick-multiple').select2()
|
||||||
$('.enter-multiple').select2({
|
$('.enter-multiple').select2({
|
||||||
@@ -41,8 +41,8 @@ async function handleSearch() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleSearchError() {
|
function handleSearchError() {
|
||||||
const alertText = modalAlert.querySelector('#alert-text')
|
const alertText = modalAlert._element.querySelector('#alert-text')
|
||||||
alertText.textContent = `NAPAKA pri iskanju`
|
alertText.textContent = i18next.t('NAPAKA pri iskanju')
|
||||||
modalAlert.toggle()
|
modalAlert.toggle()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
tdId.textContent = sequentialCount
|
tdId.textContent = sequentialCount
|
||||||
tdName.textContent = candidate.kanonicnaoblika
|
tdName.textContent = candidate.kanonicnaoblika
|
||||||
tdSize.textContent = candidate.ranking
|
tdSize.textContent = candidate.ranking
|
||||||
tdDate.textContent = candidate.pogostostpojavljanja
|
tdDate.textContent = candidate.pogostostpojavljanja[0]
|
||||||
rowEl.append(tdId, tdName, tdSize, tdDate)
|
rowEl.append(tdId, tdName, tdSize, tdDate)
|
||||||
resultsListEl.appendChild(rowEl)
|
resultsListEl.appendChild(rowEl)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* global currentPagePath */
|
/* global currentPagePath, i18next */
|
||||||
|
|
||||||
// const currentPagePath = location.pathname
|
// const currentPagePath = location.pathname
|
||||||
|
|
||||||
@@ -103,22 +103,22 @@ function initExtraction() {
|
|||||||
btnStart.type = 'button'
|
btnStart.type = 'button'
|
||||||
btnImg.src = '/images/fi_arrow-right-circle.svg'
|
btnImg.src = '/images/fi_arrow-right-circle.svg'
|
||||||
btnSpan.className = 'ms-1'
|
btnSpan.className = 'ms-1'
|
||||||
btnSpan.textContent = 'Začni'
|
btnSpan.textContent = i18next.t('Začni')
|
||||||
hr.className = 'mt-2 mb-3'
|
hr.className = 'mt-2 mb-3'
|
||||||
divSmFlex2.className = 'd-flex justify-content-between'
|
divSmFlex2.className = 'd-flex justify-content-between'
|
||||||
divSmFlexACMb.className = 'd-sm-flex align-items-center mb-0'
|
divSmFlexACMb.className = 'd-sm-flex align-items-center mb-0'
|
||||||
imgAlertCircle.src = '/images/alert-circle.svg'
|
imgAlertCircle.src = '/images/alert-circle.svg'
|
||||||
spanNew.className = 'normal-gray ms-1'
|
spanNew.className = 'normal-gray ms-1'
|
||||||
spanNew.textContent = 'Nov'
|
spanNew.textContent = i18next.t('Nov')
|
||||||
divSmFlexACMbMe.className = 'd-sm-flex align-content-center mb-0 me-3'
|
divSmFlexACMbMe.className = 'd-sm-flex align-content-center mb-0 me-3'
|
||||||
divEditTask.className = 'align-items-center me-3 edit-task'
|
divEditTask.className = 'align-items-center me-3 edit-task'
|
||||||
imgEditAlt.src = '/images/u_edit-alt.svg'
|
imgEditAlt.src = '/images/u_edit-alt.svg'
|
||||||
spanEdit.className = 'ms-1 normal-gray'
|
spanEdit.className = 'ms-1 normal-gray'
|
||||||
spanEdit.textContent = 'Uredi'
|
spanEdit.textContent = i18next.t('Uredi')
|
||||||
divDeleteTask.className = 'ms-3 align-items-center delete-task'
|
divDeleteTask.className = 'ms-3 align-items-center delete-task'
|
||||||
imgDeleteAlt.src = '/images/red-trash-icon.svg'
|
imgDeleteAlt.src = '/images/red-trash-icon.svg'
|
||||||
spanDelete.className = 'ms-1 normal-gray'
|
spanDelete.className = 'ms-1 normal-gray'
|
||||||
spanDelete.textContent = 'Briši'
|
spanDelete.textContent = i18next.t('Briši')
|
||||||
|
|
||||||
taskNewContainer.appendChild(divSmFlex)
|
taskNewContainer.appendChild(divSmFlex)
|
||||||
divSmFlex.appendChild(divSmGrid)
|
divSmFlex.appendChild(divSmGrid)
|
||||||
@@ -233,7 +233,7 @@ function mobileMoveContent() {
|
|||||||
primaryButton.style.whiteSpace = ''
|
primaryButton.style.whiteSpace = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
navTitle.textContent = 'Urejanje'
|
navTitle.textContent = i18next.t('Urejanje')
|
||||||
siteHeading.style.display = 'block'
|
siteHeading.style.display = 'block'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* global axios, validator, removeJumpLogic */
|
/* global axios, validator, $, i18next */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
@@ -27,6 +27,7 @@ const listenRegisterBtn = event => {
|
|||||||
loginHeader.className = loginHeader.className + ' d-none'
|
loginHeader.className = loginHeader.className + ' d-none'
|
||||||
regDescription.className = regDescription.className.replace('d-none', '')
|
regDescription.className = regDescription.className.replace('d-none', '')
|
||||||
loginDescription.className = loginDescription.className + ' d-none'
|
loginDescription.className = loginDescription.className + ' d-none'
|
||||||
|
clearErrorView()
|
||||||
}
|
}
|
||||||
|
|
||||||
const listenLoginBtn = event => {
|
const listenLoginBtn = event => {
|
||||||
@@ -38,6 +39,7 @@ const listenLoginBtn = event => {
|
|||||||
regHeader.className = regHeader.className + ' d-none'
|
regHeader.className = regHeader.className + ' d-none'
|
||||||
loginDescription.className = loginDescription.className.replace('d-none', '')
|
loginDescription.className = loginDescription.className.replace('d-none', '')
|
||||||
regDescription.className = regDescription.className + ' d-none'
|
regDescription.className = regDescription.className + ' d-none'
|
||||||
|
clearErrorView()
|
||||||
}
|
}
|
||||||
|
|
||||||
regBtn.addEventListener('click', listenRegisterBtn)
|
regBtn.addEventListener('click', listenRegisterBtn)
|
||||||
@@ -106,6 +108,23 @@ function updateLoginRegisterErrorView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearErrorView() {
|
||||||
|
Object.keys(registerErrorIndicatorpairs).forEach(key => {
|
||||||
|
registerErrorIndicatorpairs[key][0].style.visibility = 'hidden'
|
||||||
|
registerErrorIndicatorpairs[key][1].style.visibility = 'hidden'
|
||||||
|
})
|
||||||
|
|
||||||
|
document.querySelectorAll('.error-login-icon').forEach(e => {
|
||||||
|
e.style.visibility = 'hidden'
|
||||||
|
})
|
||||||
|
|
||||||
|
document.querySelector('.error-text').style.visibility = 'hidden'
|
||||||
|
|
||||||
|
document.getElementById('login-remember').checked = false
|
||||||
|
document.getElementById('terms-of-use').checked = false
|
||||||
|
document.getElementById('privacy-policy').checked = false
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Show error message from the label that represents the error returned from the server
|
Show error message from the label that represents the error returned from the server
|
||||||
Includes error X icons for login
|
Includes error X icons for login
|
||||||
@@ -152,14 +171,23 @@ function updateRegisterWindowOnSuccess(
|
|||||||
footer.appendChild(document.createElement('button'))
|
footer.appendChild(document.createElement('button'))
|
||||||
const btn = footer.firstElementChild
|
const btn = footer.firstElementChild
|
||||||
btn.classList = 'btn btn-secondary text-secondary'
|
btn.classList = 'btn btn-secondary text-secondary'
|
||||||
btn.textContent = 'ZAPRI'
|
btn.textContent = i18next.t('ZAPRI')
|
||||||
btn.ariaLabel = 'Close'
|
btn.ariaLabel = 'Close'
|
||||||
btn.dataset.bsDismiss = 'modal'
|
btn.dataset.bsDismiss = 'modal'
|
||||||
const successDescriptionPararaph = body.children[1]
|
const successDescriptionPararaph = body.children[1]
|
||||||
|
|
||||||
successDescriptionPararaph.textContent = `Pozdravljeni ${name} ${surname},
|
// successDescriptionPararaph.textContent = `Pozdravljeni ${name} ${surname},
|
||||||
na vaš elektronski naslov smo vam poslali sporočilo s povezavo, s katero boste potrdili svoj uporabniški račun na Terminološkem portalu.
|
// na vaš elektronski naslov smo vam poslali sporočilo s povezavo, s katero boste potrdili svoj uporabniški račun na Terminološkem portalu.
|
||||||
Povezava za potrditev je veljavna ${days} dni.`
|
// Povezava za potrditev je veljavna ${days} dni.`
|
||||||
|
|
||||||
|
successDescriptionPararaph.textContent =
|
||||||
|
i18next.t('Pozdravljeni ') +
|
||||||
|
`${name} ${surname}, ` +
|
||||||
|
i18next.t(
|
||||||
|
'na vaš elektronski naslov smo vam poslali sporočilo s povezavo, s katero boste potrdili svoj uporabniški račun na Terminološkem portalu. Povezava za potrditev je veljavna '
|
||||||
|
) +
|
||||||
|
`${days} ` +
|
||||||
|
i18next.t('dni.')
|
||||||
}
|
}
|
||||||
|
|
||||||
// login and register
|
// login and register
|
||||||
@@ -199,7 +227,7 @@ function updateRegisterWindowOnSuccess(
|
|||||||
updateLoginRegisterErrorView(
|
updateLoginRegisterErrorView(
|
||||||
registerErrorLabels[iterator],
|
registerErrorLabels[iterator],
|
||||||
true,
|
true,
|
||||||
'Prazno obvezno polje'
|
i18next.t('Prazno obvezno polje')
|
||||||
)
|
)
|
||||||
failFrontendValidation = true
|
failFrontendValidation = true
|
||||||
} else {
|
} else {
|
||||||
@@ -215,7 +243,7 @@ function updateRegisterWindowOnSuccess(
|
|||||||
updateLoginRegisterErrorView(
|
updateLoginRegisterErrorView(
|
||||||
'error-email',
|
'error-email',
|
||||||
true,
|
true,
|
||||||
'Neveljavna e-pošta'
|
i18next.t('Neveljavna e-pošta')
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
updateLoginRegisterErrorView('error-email', false)
|
updateLoginRegisterErrorView('error-email', false)
|
||||||
@@ -226,7 +254,7 @@ function updateRegisterWindowOnSuccess(
|
|||||||
updateLoginRegisterErrorView(
|
updateLoginRegisterErrorView(
|
||||||
'error-password',
|
'error-password',
|
||||||
true,
|
true,
|
||||||
'Geslo je prekratko'
|
i18next.t('Geslo je prekratko')
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
updateLoginRegisterErrorView('error-password', false)
|
updateLoginRegisterErrorView('error-password', false)
|
||||||
@@ -237,7 +265,7 @@ function updateRegisterWindowOnSuccess(
|
|||||||
updateLoginRegisterErrorView(
|
updateLoginRegisterErrorView(
|
||||||
'error-password-repeat',
|
'error-password-repeat',
|
||||||
true,
|
true,
|
||||||
'Geslo se ne ujema'
|
i18next.t('Geslo se ne ujema')
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -263,11 +291,11 @@ function updateRegisterWindowOnSuccess(
|
|||||||
|
|
||||||
// event.target.messageBind.textContent = data
|
// event.target.messageBind.textContent = data
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
let message = 'Prišlo je do napake.'
|
let message = i18next.t('Prišlo je do napake.')
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
message = error.response.data
|
message = error.response.data
|
||||||
} else if (error.request) {
|
} else if (error.request) {
|
||||||
message = 'Strežnik ni dosegljiv. Poskusite kasneje.'
|
message = i18next.t('Strežnik ni dosegljiv. Poskusite kasneje.')
|
||||||
}
|
}
|
||||||
|
|
||||||
showErrorReturnedFromServer(
|
showErrorReturnedFromServer(
|
||||||
@@ -279,3 +307,28 @@ function updateRegisterWindowOnSuccess(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function registerConditions() {
|
||||||
|
// Requirement 1: Terms of Use Checkbox checked
|
||||||
|
const requirement1 = document.getElementById('terms-of-use')
|
||||||
|
// Requirement 2: Privacy Policy checked
|
||||||
|
const requirement2 = document.getElementById('privacy-policy')
|
||||||
|
|
||||||
|
const registerButton = document.getElementById('regbtnmain')
|
||||||
|
if (requirement1.checked && requirement2.checked) {
|
||||||
|
registerButton.disabled = false
|
||||||
|
} else {
|
||||||
|
registerButton.disabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById('terms-of-use')
|
||||||
|
.addEventListener('change', registerConditions)
|
||||||
|
document
|
||||||
|
.getElementById('privacy-policy')
|
||||||
|
.addEventListener('change', registerConditions)
|
||||||
|
|
||||||
|
$('#staticBackdrop').on('hidden.bs.modal', function () {
|
||||||
|
clearErrorView()
|
||||||
|
})
|
||||||
|
|||||||
@@ -154,6 +154,21 @@ function searchQuery(searchString, searchFilterDOM = {}) {
|
|||||||
document.location.href = url
|
document.location.href = url
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function searchQueryWithExistingFilters(searchString) {
|
||||||
|
const stringBuilder = `/iskanje?q=${searchString}`
|
||||||
|
const thisUrl = new URL(location)
|
||||||
|
const url = new URL(stringBuilder, location.protocol + '//' + location.host)
|
||||||
|
|
||||||
|
thisUrl.searchParams.forEach((value, key, parent) => {
|
||||||
|
// console.log(`VALUE: ${value}`)
|
||||||
|
if (!(key === 'q' || key === 'p')) {
|
||||||
|
url.searchParams.append(key, value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
document.location.href = url
|
||||||
|
}
|
||||||
|
|
||||||
// end search implementation functions
|
// end search implementation functions
|
||||||
|
|
||||||
// search filter functions
|
// search filter functions
|
||||||
@@ -173,7 +188,11 @@ function sbmFn(sbm) {
|
|||||||
inputString = '*'
|
inputString = '*'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sbm === document.querySelector('.search-btn-a')) {
|
||||||
searchQuery(inputString, searchFilterDOM)
|
searchQuery(inputString, searchFilterDOM)
|
||||||
|
} else {
|
||||||
|
searchQueryWithExistingFilters(inputString)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,42 @@
|
|||||||
const allMixedContentFields = document.querySelectorAll('.mc-field')
|
const allMixedContentFields = document.querySelectorAll('.mc-field')
|
||||||
|
|
||||||
allMixedContentFields.forEach(el =>
|
allMixedContentFields.forEach(el =>
|
||||||
el.addEventListener('focusin', () => showMCButtons(el))
|
el.addEventListener('focus', () => showMCButtons(el))
|
||||||
)
|
)
|
||||||
|
|
||||||
document.addEventListener('keypress', e => {
|
document.addEventListener('keypress', e => {
|
||||||
if (e.key === 'Enter' && e.target.classList.contains('dispatch-tab')) {
|
if (e.key === 'Enter' && e.target.classList.contains('dispatch-tab')) {
|
||||||
const form = event.target.form
|
|
||||||
const index = Array.prototype.indexOf.call(form, e.target)
|
|
||||||
form.elements[index + 1]?.focus()
|
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
const currentNode = e.target
|
||||||
|
const allElements = document.querySelectorAll('input, select, textarea')
|
||||||
|
const currentIndex = [...allElements].findIndex(el =>
|
||||||
|
currentNode.isEqualNode(el)
|
||||||
|
)
|
||||||
|
const targetIndex = (currentIndex + 1) % allElements.length
|
||||||
|
allElements[targetIndex].focus()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
function showMCButtons(element) {
|
function showMCButtons(element, linkTerm) {
|
||||||
const parent = element.parentElement.parentElement.parentElement
|
let parent
|
||||||
|
if (linkTerm) {
|
||||||
|
parent = element.parentElement.parentElement.parentElement.parentElement
|
||||||
|
} else {
|
||||||
|
parent = element.parentElement.parentElement.parentElement
|
||||||
|
}
|
||||||
|
const mainContent = document.getElementById('offset-main')
|
||||||
|
const sideMenu = document.querySelector('.content-nav-content')
|
||||||
const btnGrp = parent.querySelector('.mc-buttons-group')
|
const btnGrp = parent.querySelector('.mc-buttons-group')
|
||||||
|
// Warning, do not touch *_*
|
||||||
|
document.addEventListener('click', function (event) {
|
||||||
|
if (mainContent.contains(event.target) || sideMenu.contains(event.target)) {
|
||||||
|
if (element !== event.target && document.activeElement !== element) {
|
||||||
|
if (!parent.contains(event.target)) {
|
||||||
|
btnGrp.classList.add('d-none')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
btnGrp.classList.remove('d-none')
|
btnGrp.classList.remove('d-none')
|
||||||
const btnGrpChildren = btnGrp.children
|
const btnGrpChildren = btnGrp.children
|
||||||
const childrenBtns = Array.from(btnGrpChildren)
|
const childrenBtns = Array.from(btnGrpChildren)
|
||||||
@@ -29,10 +50,10 @@ function showMCButtons(element) {
|
|||||||
{ once: true }
|
{ once: true }
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
document.addEventListener('click', function (event) {
|
|
||||||
if (parent !== event.target && !parent.contains(event.target)) {
|
document.addEventListener('keyup', function (e) {
|
||||||
btnGrp.classList.add('d-none')
|
const selectedEl = document.activeElement
|
||||||
}
|
if (!parent.contains(selectedEl)) btnGrp.classList.add('d-none')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,6 +73,7 @@ function addMixedContentInput(el, selectedEl) {
|
|||||||
inputText.substring(selectedStart, selectedEnd) +
|
inputText.substring(selectedStart, selectedEnd) +
|
||||||
'</b>' +
|
'</b>' +
|
||||||
inputText.substring(selectedEnd)
|
inputText.substring(selectedEnd)
|
||||||
|
placeCaret(selectedEl, selectedEnd + 7)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (el.classList.contains('mc-italic') && selectedText.length) {
|
if (el.classList.contains('mc-italic') && selectedText.length) {
|
||||||
@@ -61,6 +83,7 @@ function addMixedContentInput(el, selectedEl) {
|
|||||||
inputText.substring(selectedStart, selectedEnd) +
|
inputText.substring(selectedStart, selectedEnd) +
|
||||||
'</i>' +
|
'</i>' +
|
||||||
inputText.substring(selectedEnd)
|
inputText.substring(selectedEnd)
|
||||||
|
placeCaret(selectedEl, selectedEnd + 7)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (el.classList.contains('mc-supscript') && selectedText.length) {
|
if (el.classList.contains('mc-supscript') && selectedText.length) {
|
||||||
@@ -70,6 +93,7 @@ function addMixedContentInput(el, selectedEl) {
|
|||||||
inputText.substring(selectedStart, selectedEnd) +
|
inputText.substring(selectedStart, selectedEnd) +
|
||||||
'</sup>' +
|
'</sup>' +
|
||||||
inputText.substring(selectedEnd)
|
inputText.substring(selectedEnd)
|
||||||
|
placeCaret(selectedEl, selectedEnd + 11)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (el.classList.contains('mc-subscript') && selectedText.length) {
|
if (el.classList.contains('mc-subscript') && selectedText.length) {
|
||||||
@@ -79,14 +103,15 @@ function addMixedContentInput(el, selectedEl) {
|
|||||||
inputText.substring(selectedStart, selectedEnd) +
|
inputText.substring(selectedStart, selectedEnd) +
|
||||||
'</sub>' +
|
'</sub>' +
|
||||||
inputText.substring(selectedEnd)
|
inputText.substring(selectedEnd)
|
||||||
|
placeCaret(selectedEl, selectedEnd + 11)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (el.classList.contains('mc-hyperlink') && selectedText.length) {
|
if (el.classList.contains('mc-hyperlink') && selectedText.length) {
|
||||||
selectedEl.value =
|
selectedEl.value =
|
||||||
inputText.substring(0, selectedStart) +
|
inputText.substring(0, selectedStart) +
|
||||||
'<link url="">' +
|
'<a href="">' +
|
||||||
inputText.substring(selectedStart, selectedEnd) +
|
inputText.substring(selectedStart, selectedEnd) +
|
||||||
'</link> ' +
|
'</a>' +
|
||||||
inputText.substring(selectedEnd)
|
inputText.substring(selectedEnd)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,9 +120,22 @@ function addMixedContentInput(el, selectedEl) {
|
|||||||
inputText.substring(0, selectedStart) +
|
inputText.substring(0, selectedStart) +
|
||||||
'<br/>' +
|
'<br/>' +
|
||||||
inputText.substring(selectedEnd)
|
inputText.substring(selectedEnd)
|
||||||
|
placeCaret(selectedEl, selectedEnd + 5)
|
||||||
el.removeEventListener('click', () => addMixedContentInput, false)
|
el.removeEventListener('click', () => addMixedContentInput, false)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// selectedEl.focus()
|
function placeCaret(elem, caretPos) {
|
||||||
// selectedEl.setSelectionRange(selectedStart, selectedEnd)
|
if (elem != null) {
|
||||||
|
if (elem.createTextRange) {
|
||||||
|
const range = elem.createTextRange()
|
||||||
|
range.move('character', caretPos)
|
||||||
|
range.select()
|
||||||
|
} else {
|
||||||
|
if (elem.selectionStart) {
|
||||||
|
elem.focus()
|
||||||
|
elem.setSelectionRange(caretPos, caretPos)
|
||||||
|
} else elem.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,8 @@
|
|||||||
return await axios.get(url)
|
return await axios.get(url)
|
||||||
} */
|
} */
|
||||||
|
|
||||||
|
// TODO MARK FOR REVIEW WHETHER THIS METHODS ARE STILL NEEDED
|
||||||
|
|
||||||
function prepareQueryArrayForArrayWithIDs(page, searchQuery, filters = {}) {
|
function prepareQueryArrayForArrayWithIDs(page, searchQuery, filters = {}) {
|
||||||
const qParams = new URL(location).searchParams
|
const qParams = new URL(location).searchParams
|
||||||
qParams.set('p', page)
|
qParams.set('p', page)
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
/* global isI18nReady */
|
||||||
|
|
||||||
|
isI18nReady.then(t => {
|
||||||
|
const alertText = document.querySelector('#alert-text')
|
||||||
|
alertText.textContent = t(
|
||||||
|
'Ali želite zbrisati ta profil. S tem bodo izbrisani vsi podatki, ki ste jih ustvarili'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const redConfirm = document.querySelector('#modal-use-btn')
|
||||||
|
redConfirm.style.backgroundColor = '#AC7171'
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// Note to myself: please optimize and remove the redundacy of the code written in multiple classes to achieve the same result as this
|
||||||
|
|
||||||
|
const focusOnCompletePromtBase = queryInput => {
|
||||||
|
queryInput.focus()
|
||||||
|
queryInput.select()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tradeoff: Declare a variable for the element to reduce duplication?
|
||||||
|
const inputQuery = document.querySelector('#search-query')
|
||||||
|
if (inputQuery) {
|
||||||
|
focusOnCompletePromtBase(inputQuery)
|
||||||
|
|
||||||
|
if (window.location.pathname === '/slovarji') {
|
||||||
|
document
|
||||||
|
.querySelector('#dicts-search-btn')
|
||||||
|
.addEventListener('click', () => {
|
||||||
|
focusOnCompletePromtBase(inputQuery)
|
||||||
|
})
|
||||||
|
|
||||||
|
document
|
||||||
|
.querySelector('#search-in-filter-modal')
|
||||||
|
.addEventListener('click', () => {
|
||||||
|
focusOnCompletePromtBase(inputQuery)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/* global $, axios, i18next */
|
||||||
|
|
||||||
|
// Function to verify password and repeat password
|
||||||
|
function verifyPassword() {
|
||||||
|
const password = document.getElementById('reset-password').value
|
||||||
|
const repeatPassword = document.getElementById('reset-password-repeat').value
|
||||||
|
if (password !== repeatPassword) {
|
||||||
|
alert(i18next.t('Gesli se ne ujemata')) // 'Passwords do not match')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle submit event
|
||||||
|
document
|
||||||
|
.querySelector('#reset-and-redirect')
|
||||||
|
.addEventListener('submit', event => {
|
||||||
|
event.preventDefault()
|
||||||
|
if (verifyPassword()) {
|
||||||
|
const password = document.getElementById('reset-password').value
|
||||||
|
const repeatPassword = document.getElementById(
|
||||||
|
'reset-password-repeat'
|
||||||
|
).value
|
||||||
|
const token = document.getElementById('token').value
|
||||||
|
// const email = document.getElementById('email').value
|
||||||
|
// const data = { password, repeatPassword, token, email }
|
||||||
|
axios
|
||||||
|
.post('/api/v1/users/reset-passwordPLACEHOLDER_URL', {
|
||||||
|
password,
|
||||||
|
repeatPassword,
|
||||||
|
token
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (response.data.success) {
|
||||||
|
$('#reset-pass-info').modal('show')
|
||||||
|
// window.location = '/'
|
||||||
|
} else {
|
||||||
|
alert(response.data.message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
// Very unlikely, but can happen
|
||||||
|
alert(i18next.t('Napaka na strežniku'))
|
||||||
|
console.log(error)
|
||||||
|
// DEBUG SUCCESS DUE TO NO ENDPOINT $('#reset-pass-info').modal('show') <<- REMOVE
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Handle cancel event
|
||||||
|
document.querySelector('#cancel-btn').addEventListener('click', event => {
|
||||||
|
event.preventDefault()
|
||||||
|
window.location = '/'
|
||||||
|
})
|
||||||
|
|
||||||
|
// Handle redirect on success
|
||||||
|
document.querySelector('#modal-fp-info-close').addEventListener('click', () => {
|
||||||
|
window.location = '/'
|
||||||
|
})
|
||||||
@@ -1,4 +1,14 @@
|
|||||||
/* global $, bootstrap */
|
/* global $, bootstrap, axios, i18next, i18nextHttpBackend */
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const isI18nReady = i18next.use(i18nextHttpBackend).init({
|
||||||
|
lng: document.documentElement.lang ?? 'sl',
|
||||||
|
ns: 'core',
|
||||||
|
nsSeparator: false,
|
||||||
|
keySeparator: false,
|
||||||
|
...(window.inDevEnv && { saveMissing: true }),
|
||||||
|
...(!window.inDevEnv && { fallbackLng: false })
|
||||||
|
})
|
||||||
|
|
||||||
const windows = ['#suggestions-root', '.advanced-search-root', '.kbd-root']
|
const windows = ['#suggestions-root', '.advanced-search-root', '.kbd-root']
|
||||||
|
|
||||||
@@ -302,7 +312,7 @@ function renderSuggestions(
|
|||||||
|
|
||||||
ajustElementSettings(preElement, options)
|
ajustElementSettings(preElement, options)
|
||||||
|
|
||||||
preElement.textContent = 'Za to besedo še ni podatkov.'
|
preElement.textContent = i18next.t('Za to besedo še ni podatkov.')
|
||||||
preElement.disabled = true
|
preElement.disabled = true
|
||||||
preElement.classList = ['w-100']
|
preElement.classList = ['w-100']
|
||||||
preElement.classList += ` ${type}-sugg`
|
preElement.classList += ` ${type}-sugg`
|
||||||
@@ -646,11 +656,26 @@ document.querySelectorAll('.loginregisterlabel').forEach(label => {
|
|||||||
/**
|
/**
|
||||||
* Enables pagination logic for the specified pager UI.
|
* Enables pagination logic for the specified pager UI.
|
||||||
*
|
*
|
||||||
* @param {string} paginationRootElId - Pager element id.
|
* @param {(string|string[])} paginationRootElIds - Pager element id.
|
||||||
* @param {function} onPageChange - Gets called with the number of the page the user requested.
|
* @param {function} onPageChange - Gets called with the number of the page the user requested.
|
||||||
* @returns {function} - Call it with (newCurrentPage, newNumOfAllPages) to update the ui. Or without arguments, after handling a potential error.
|
* @returns {function} - Call it with (newCurrentPage, newNumOfAllPages) to update the ui. Or without arguments, after handling a potential error.
|
||||||
*/
|
*/
|
||||||
function initPagination(paginationRootElId, onPageChange, currentPage = 1) {
|
function initPagination(paginationRootElIds, onPageChange, currentPage = 1) {
|
||||||
|
let reqLock = false
|
||||||
|
let numOfAllPages
|
||||||
|
const backwardBtnEls = []
|
||||||
|
const forwardBtnEls = []
|
||||||
|
const pageInputEls = []
|
||||||
|
const pagesCountDisplayEls = []
|
||||||
|
|
||||||
|
if (Array.isArray(paginationRootElIds)) {
|
||||||
|
paginationRootElIds.forEach(id => initControls(id))
|
||||||
|
} else {
|
||||||
|
initControls(paginationRootElIds)
|
||||||
|
}
|
||||||
|
const allControlEls = [...backwardBtnEls, ...forwardBtnEls, ...pageInputEls]
|
||||||
|
|
||||||
|
function initControls(paginationRootElId) {
|
||||||
const rootEl = document.getElementById(paginationRootElId)
|
const rootEl = document.getElementById(paginationRootElId)
|
||||||
const btnFirstPage = rootEl.querySelector('.first-page')
|
const btnFirstPage = rootEl.querySelector('.first-page')
|
||||||
const btnPreviousPage = rootEl.querySelector('.previous-page')
|
const btnPreviousPage = rootEl.querySelector('.previous-page')
|
||||||
@@ -660,17 +685,24 @@ function initPagination(paginationRootElId, onPageChange, currentPage = 1) {
|
|||||||
const pageInputEl = formEl.querySelector('input')
|
const pageInputEl = formEl.querySelector('input')
|
||||||
const pagesCountDisplayEl = formEl.querySelector('.pages-total')
|
const pagesCountDisplayEl = formEl.querySelector('.pages-total')
|
||||||
|
|
||||||
let reqLock = false
|
numOfAllPages = +pagesCountDisplayEl.textContent
|
||||||
|
|
||||||
rootEl.addEventListener('click', handleButtonClick)
|
backwardBtnEls.push(btnFirstPage, btnPreviousPage)
|
||||||
formEl.addEventListener('submit', handleFormSubmit)
|
forwardBtnEls.push(btnNextPage, btnLastPage)
|
||||||
|
pageInputEls.push(pageInputEl)
|
||||||
|
pagesCountDisplayEls.push(pagesCountDisplayEl)
|
||||||
|
|
||||||
function handleButtonClick({ target }) {
|
rootEl.addEventListener('click', e => {
|
||||||
|
handleButtonClick(e, paginationRootElId)
|
||||||
|
})
|
||||||
|
formEl.addEventListener('submit', e => handleFormSubmit(e, pageInputEl))
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleButtonClick({ target }, paginationRootElId) {
|
||||||
if (reqLock) return
|
if (reqLock) return
|
||||||
|
|
||||||
const buttonEl = target.closest(`#${paginationRootElId} button`)
|
const buttonEl = target.closest(`#${paginationRootElId} button`)
|
||||||
if (!buttonEl) return
|
if (!buttonEl) return
|
||||||
const numOfAllPages = +pagesCountDisplayEl.textContent
|
|
||||||
|
|
||||||
if (buttonEl.classList.contains('first-page')) {
|
if (buttonEl.classList.contains('first-page')) {
|
||||||
if (currentPage === 1) return
|
if (currentPage === 1) return
|
||||||
@@ -691,13 +723,13 @@ function initPagination(paginationRootElId, onPageChange, currentPage = 1) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleFormSubmit(e) {
|
function handleFormSubmit(e, pageInputEl) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (reqLock) return
|
if (reqLock) return
|
||||||
|
|
||||||
const inputValue = +pageInputEl.value
|
const inputValue = +pageInputEl.value
|
||||||
if (!(inputValue > 0 && inputValue <= pagesCountDisplayEl.textContent)) {
|
if (!(inputValue > 0 && inputValue <= numOfAllPages)) {
|
||||||
alert('Nepravilna vrednost strani')
|
alert(i18next.t('Nepravilna vrednost strani'))
|
||||||
pageInputEl.value = currentPage
|
pageInputEl.value = currentPage
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -708,20 +740,12 @@ function initPagination(paginationRootElId, onPageChange, currentPage = 1) {
|
|||||||
|
|
||||||
function enableLock() {
|
function enableLock() {
|
||||||
reqLock = true
|
reqLock = true
|
||||||
btnFirstPage.disabled = true
|
allControlEls.forEach(el => (el.disabled = true))
|
||||||
btnPreviousPage.disabled = true
|
|
||||||
btnNextPage.disabled = true
|
|
||||||
btnLastPage.disabled = true
|
|
||||||
pageInputEl.disabled = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function disableLock() {
|
function disableLock() {
|
||||||
reqLock = false
|
reqLock = false
|
||||||
btnFirstPage.disabled = false
|
allControlEls.forEach(el => (el.disabled = false))
|
||||||
btnPreviousPage.disabled = false
|
|
||||||
btnNextPage.disabled = false
|
|
||||||
btnLastPage.disabled = false
|
|
||||||
pageInputEl.disabled = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updatePagerUi(newCurrentPage, newNumOfAllPages) {
|
function updatePagerUi(newCurrentPage, newNumOfAllPages) {
|
||||||
@@ -729,23 +753,20 @@ function initPagination(paginationRootElId, onPageChange, currentPage = 1) {
|
|||||||
if (!newCurrentPage) return
|
if (!newCurrentPage) return
|
||||||
|
|
||||||
currentPage = newCurrentPage
|
currentPage = newCurrentPage
|
||||||
pageInputEl.value = newCurrentPage
|
numOfAllPages = newNumOfAllPages
|
||||||
pagesCountDisplayEl.textContent = newNumOfAllPages
|
pageInputEls.forEach(el => (el.value = newCurrentPage))
|
||||||
|
pagesCountDisplayEls.forEach(el => (el.textContent = newNumOfAllPages))
|
||||||
|
|
||||||
if (newCurrentPage === 1) {
|
if (newCurrentPage === 1) {
|
||||||
btnFirstPage.disabled = true
|
backwardBtnEls.forEach(el => (el.disabled = true))
|
||||||
btnPreviousPage.disabled = true
|
|
||||||
} else {
|
} else {
|
||||||
btnFirstPage.disabled = false
|
backwardBtnEls.forEach(el => (el.disabled = false))
|
||||||
btnPreviousPage.disabled = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newCurrentPage === newNumOfAllPages) {
|
if (newCurrentPage === newNumOfAllPages) {
|
||||||
btnNextPage.disabled = true
|
forwardBtnEls.forEach(el => (el.disabled = true))
|
||||||
btnLastPage.disabled = true
|
|
||||||
} else {
|
} else {
|
||||||
btnNextPage.disabled = false
|
forwardBtnEls.forEach(el => (el.disabled = false))
|
||||||
btnLastPage.disabled = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -923,11 +944,11 @@ if (registerSwithcButton) {
|
|||||||
|
|
||||||
function initSelect2(querySelector, placeholder) {
|
function initSelect2(querySelector, placeholder) {
|
||||||
$(querySelector).select2({
|
$(querySelector).select2({
|
||||||
allowClear: true,
|
// allowClear: true,
|
||||||
placeholder: placeholder
|
placeholder: placeholder
|
||||||
})
|
})
|
||||||
|
|
||||||
$(querySelector).val(null).trigger('change')
|
// $(querySelector).val(null).trigger('change')
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
@@ -935,7 +956,7 @@ if (registerSwithcButton) {
|
|||||||
$('.select-search-field').select2({})
|
$('.select-search-field').select2({})
|
||||||
|
|
||||||
initSelect2('.select-domain-field', 'Področje')
|
initSelect2('.select-domain-field', 'Področje')
|
||||||
initSelect2('.select-src-lang-field', 'Izvorni jezik')
|
initSelect2('.select-src-lang-field', 'Jezik iskanja')
|
||||||
initSelect2('.select-dest-lang-field', 'Ciljni jezik')
|
initSelect2('.select-dest-lang-field', 'Ciljni jezik')
|
||||||
initSelect2('.select-dict-field', 'Slovar')
|
initSelect2('.select-dict-field', 'Slovar')
|
||||||
initSelect2('.select-source-field', 'Vir')
|
initSelect2('.select-source-field', 'Vir')
|
||||||
@@ -978,7 +999,7 @@ if (registerSwithcButton) {
|
|||||||
$('.select-search-field').select2({})
|
$('.select-search-field').select2({})
|
||||||
|
|
||||||
initSelect2('.select-domain-field', 'Področje')
|
initSelect2('.select-domain-field', 'Področje')
|
||||||
initSelect2('.select-src-lang-field', 'Izvorni jezik')
|
initSelect2('.select-src-lang-field', 'Jezik iskanja')
|
||||||
initSelect2('.select-dest-lang-field', 'Ciljni jezik')
|
initSelect2('.select-dest-lang-field', 'Ciljni jezik')
|
||||||
initSelect2('.select-dict-field', 'Slovar')
|
initSelect2('.select-dict-field', 'Slovar')
|
||||||
initSelect2('.select-source-field', 'Vir')
|
initSelect2('.select-source-field', 'Vir')
|
||||||
@@ -1097,7 +1118,7 @@ function transferText(sideMenuText, removeOptionalBreak = false) {
|
|||||||
|
|
||||||
const optionalBreak = document.getElementById('disposable-break')
|
const optionalBreak = document.getElementById('disposable-break')
|
||||||
|
|
||||||
if (document.body.clientWidth <= 1200) {
|
if (window.innerWidth < 1200) {
|
||||||
navTitle.textContent = siteHeadingTextContent
|
navTitle.textContent = siteHeadingTextContent
|
||||||
siteHeading.style.display = 'none'
|
siteHeading.style.display = 'none'
|
||||||
if (removeOptionalBreak) {
|
if (removeOptionalBreak) {
|
||||||
@@ -1111,3 +1132,140 @@ function transferText(sideMenuText, removeOptionalBreak = false) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function replaceContainer(id, HTMLContent) {
|
||||||
|
const el = document.getElementById(id)
|
||||||
|
if (el) {
|
||||||
|
el.innerHTML = HTMLContent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// STATE MANAGER FOR WINDOWS IN FORGOT PASSWORD
|
||||||
|
class StateManager {
|
||||||
|
constructor() {
|
||||||
|
this.state = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(newState) {
|
||||||
|
this.state = { ...this.state, ...newState }
|
||||||
|
}
|
||||||
|
|
||||||
|
getState() {
|
||||||
|
return this.state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class StateManagerInvoker {
|
||||||
|
constructor(stateManager, invokeFunction) {
|
||||||
|
this.stateManager = stateManager
|
||||||
|
this.invokeFn = invokeFunction
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(newState) {
|
||||||
|
this.stateManager.setState(newState)
|
||||||
|
this.invoke()
|
||||||
|
}
|
||||||
|
|
||||||
|
invoke() {
|
||||||
|
this.invokeFn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const stateManager = new StateManager()
|
||||||
|
const forgottenPasswordInvoker = new StateManagerInvoker(stateManager, () => {
|
||||||
|
function closeAllFPRelatedModals() {
|
||||||
|
$('#staticBackdrop').modal('hide')
|
||||||
|
$('#reset-pass-modal').modal('hide')
|
||||||
|
$('#reset-pass-info').modal('hide')
|
||||||
|
}
|
||||||
|
|
||||||
|
function showLoginModal() {
|
||||||
|
$('#staticBackdrop').modal('show')
|
||||||
|
}
|
||||||
|
|
||||||
|
function showResetPasswordModal() {
|
||||||
|
$('#reset-pass-modal').modal('show')
|
||||||
|
}
|
||||||
|
|
||||||
|
function showResetPasswordModalSuccessOrFail() {
|
||||||
|
$('#reset-pass-info').modal('show')
|
||||||
|
}
|
||||||
|
|
||||||
|
function apllyDescriptionAccordingToState() {
|
||||||
|
$('#alert-text').text(stateManager.getState().fpassWindowDescription)
|
||||||
|
}
|
||||||
|
|
||||||
|
closeAllFPRelatedModals()
|
||||||
|
switch (stateManager.getState().fpassWindowState) {
|
||||||
|
case ForgotPasswordState.LOGIN_SIGNUP:
|
||||||
|
showLoginModal()
|
||||||
|
break
|
||||||
|
case ForgotPasswordState.FORGOT_PASSWORD_MODAL:
|
||||||
|
showResetPasswordModal()
|
||||||
|
break
|
||||||
|
case ForgotPasswordState.FORGOT_PASSWORD_ERROR:
|
||||||
|
case ForgotPasswordState.FORGOT_PASSWORD_SUCCESS:
|
||||||
|
showResetPasswordModalSuccessOrFail()
|
||||||
|
apllyDescriptionAccordingToState()
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
console.log('Unknown state')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const ForgotPasswordState = {
|
||||||
|
LOGIN_SIGNUP: 1,
|
||||||
|
FORGOT_PASSWORD_MODAL: 2,
|
||||||
|
FORGOT_PASSWORD_ERROR: 3,
|
||||||
|
FORGOT_PASSWORD_SUCCESS: 4
|
||||||
|
}
|
||||||
|
|
||||||
|
function onForgotPassword() {
|
||||||
|
forgottenPasswordInvoker.setState({
|
||||||
|
fpassWindowState: ForgotPasswordState.FORGOT_PASSWORD_MODAL
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCancelForgotPassword() {
|
||||||
|
forgottenPasswordInvoker.setState({
|
||||||
|
fpassWindowState: ForgotPasswordState.LOGIN_SIGNUP
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSendForgottenEmailRequest() {
|
||||||
|
axios
|
||||||
|
.post('/REPLACETHISDUMMYURL', {
|
||||||
|
usernameOrEmail: document.getElementById('forgot-pass-input').value
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
// const EMAIL = 'DUMMY_EMAIL'
|
||||||
|
|
||||||
|
// State manager because we don't want to invoker twice
|
||||||
|
// it is required to update description first before updating state of the invoker
|
||||||
|
stateManager.setState({
|
||||||
|
fpassWindowDescription: i18next.t(
|
||||||
|
'Na vaš elektronski naslov smo vam posredovali povezavo za ponastavitev gesla. Prosimo preverite svoj elektronski predal.'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
forgottenPasswordInvoker.setState({
|
||||||
|
fpassWindowState: ForgotPasswordState.FORGOT_PASSWORD_SUCCESS
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
stateManager.setState({
|
||||||
|
fpassWindowDescription: i18next.t(
|
||||||
|
'Prišlo je do napake pri pošiljanju sporočila na vaš elektronski naslov. Poskusite ponovno.'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
forgottenPasswordInvoker.setState({
|
||||||
|
fpassWindowState: ForgotPasswordState.FORGOT_PASSWORD_ERROR
|
||||||
|
})
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#modal-fp-cancel-btn').on('click', onCancelForgotPassword)
|
||||||
|
$('#modal-fp-use-btn').on('click', onSendForgottenEmailRequest)
|
||||||
|
$('#modal-fp-info-close').on('click', () => $('#reset-pass-info').modal('hide'))
|
||||||
|
$('#forgotten-password').on('click', onForgotPassword)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
/* global $, axios, initPagination, removeAllChildNodes, closeUtilityContainers, prepareQueryArrayForArrayWithIDs, getDataFromURLSearchParams, prepareQueryParams, focusOnCompletePrompt */
|
/* global $, axios, initPagination, removeAllChildNodes, closeUtilityContainers, prepareQueryArrayForArrayWithIDs,
|
||||||
|
getDataFromURLSearchParams, prepareQueryParams, focusOnCompletePrompt, i18next */
|
||||||
|
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
@@ -117,11 +118,11 @@
|
|||||||
|
|
||||||
return page
|
return page
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
let message = 'Prišlo je do napake.'
|
let message = i18next.t('Prišlo je do napake.')
|
||||||
if (error.response?.data) {
|
if (error.response?.data) {
|
||||||
message = error.response.data
|
message = error.response.data
|
||||||
} else if (error.request) {
|
} else if (error.request) {
|
||||||
message = 'Strežnik ni dosegljiv. Poskusite kasneje.'
|
message = i18next.t('Strežnik ni dosegljiv. Poskusite kasneje.')
|
||||||
}
|
}
|
||||||
alert(message)
|
alert(message)
|
||||||
updatePager()
|
updatePager()
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
/* global $, axios, currentPagePath, initPagination, removeAllChildNodes, transferText, tooltipTriggerList, tooltipList, reinitalizeDefaultTooltipSet, createTooltip */
|
/* global $, axios, currentPagePath, initPagination,
|
||||||
|
removeAllChildNodes, transferText, tooltipTriggerList,
|
||||||
|
tooltipList, createTooltip, resetTooltipTriggerList,
|
||||||
|
transferTextExtended, i18next */
|
||||||
|
|
||||||
// position correction functions
|
// position correction functions
|
||||||
|
|
||||||
function adjustOffsetBy() {
|
function adjustOffsetBy() {
|
||||||
// const { offsetMain, fixedTopSection } = window.dictionaryElements
|
// const { offsetMain, fixedTopSection } = window.dictionaryElements
|
||||||
|
|
||||||
|
const BROWSER_UNUSUAL_OFFSET = 17 // computer from chrome
|
||||||
|
|
||||||
const offsetMain = document.querySelector('#offset-main')
|
const offsetMain = document.querySelector('#offset-main')
|
||||||
const fixedTopSection = document.querySelector('#fixed-top-section')
|
const fixedTopSection = document.querySelector('#fixed-top-section')
|
||||||
|
|
||||||
@@ -14,7 +19,7 @@ function adjustOffsetBy() {
|
|||||||
// const adminNavMobile = document.getElementsByClassName('admin-nav')
|
// const adminNavMobile = document.getElementsByClassName('admin-nav')
|
||||||
const headerPadding = document.getElementById('header-padding')
|
const headerPadding = document.getElementById('header-padding')
|
||||||
|
|
||||||
if (document.body.clientWidth < 1200) {
|
if (document.body.clientWidth + BROWSER_UNUSUAL_OFFSET < 1200) {
|
||||||
if (offsetHeaderPadding !== null) offsetMain.style.paddingTop = `0px`
|
if (offsetHeaderPadding !== null) offsetMain.style.paddingTop = `0px`
|
||||||
} else {
|
} else {
|
||||||
for (let i = 0; i < offsetHeader.length; i++) {
|
for (let i = 0; i < offsetHeader.length; i++) {
|
||||||
@@ -80,8 +85,14 @@ function mobileMoveContent() {
|
|||||||
'.header-container-divider-right'
|
'.header-container-divider-right'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// let actualInnerWidth = $("body").prop("clientWidth"); // El. width minus scrollbar width
|
||||||
|
// let actualInnerWidth = $("body").prop("scrollWidth"); // El. width minus scrollbar width
|
||||||
|
|
||||||
|
// const BROWSER_UNUSUAL_OFFSET = 17 // computer from chrome
|
||||||
|
|
||||||
|
// console.log($('body').innerWidth() + BROWSER_UNUSUAL_OFFSET)
|
||||||
try {
|
try {
|
||||||
if (document.body.clientWidth <= 1200) {
|
if (window.innerWidth < 1200) {
|
||||||
if (secondaryButton) {
|
if (secondaryButton) {
|
||||||
mobileRightHolder.appendChild(secondaryButton)
|
mobileRightHolder.appendChild(secondaryButton)
|
||||||
// secondaryButton.style.height = '28px'
|
// secondaryButton.style.height = '28px'
|
||||||
@@ -98,9 +109,11 @@ function mobileMoveContent() {
|
|||||||
primaryButton.style.whiteSpace = 'nowrap'
|
primaryButton.style.whiteSpace = 'nowrap'
|
||||||
}
|
}
|
||||||
navTitle.textContent = siteHeadingTextContent
|
navTitle.textContent = siteHeadingTextContent
|
||||||
siteHeading.style.display = 'none'
|
// siteHeading.style.display = 'none'
|
||||||
|
|
||||||
|
siteHeading.style.display = 'inline' // I know this is hacky, but sadly due to design this is mandatory in order to fix the bug
|
||||||
}
|
}
|
||||||
if (document.body.clientWidth > 1200) {
|
if (window.innerWidth >= 1200) {
|
||||||
if (secondaryButton) {
|
if (secondaryButton) {
|
||||||
headerContainerRight.appendChild(secondaryButton)
|
headerContainerRight.appendChild(secondaryButton)
|
||||||
secondaryButton.style.height = ''
|
secondaryButton.style.height = ''
|
||||||
@@ -117,7 +130,7 @@ function mobileMoveContent() {
|
|||||||
primaryButton.style.whiteSpace = ''
|
primaryButton.style.whiteSpace = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
navTitle.textContent = 'Urejanje'
|
navTitle.textContent = i18next.t('Urejanje')
|
||||||
siteHeading.style.display = 'block'
|
siteHeading.style.display = 'block'
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
@@ -128,7 +141,11 @@ if (resultsListEl) {
|
|||||||
resultsListEl.addEventListener('click', onResultClick)
|
resultsListEl.addEventListener('click', onResultClick)
|
||||||
}
|
}
|
||||||
const initialPage = +new URL(location).searchParams.get('p') || 1
|
const initialPage = +new URL(location).searchParams.get('p') || 1
|
||||||
const updatePager = initPagination('pagination', onPageChange, initialPage)
|
const updatePager = initPagination(
|
||||||
|
['pagination-top', 'pagination-bottom'],
|
||||||
|
onPageChange,
|
||||||
|
initialPage
|
||||||
|
)
|
||||||
|
|
||||||
async function onPageChange(newPage) {
|
async function onPageChange(newPage) {
|
||||||
const receivedPageNumber = await changePage(newPage)
|
const receivedPageNumber = await changePage(newPage)
|
||||||
@@ -146,7 +163,16 @@ async function changePage(newPage) {
|
|||||||
removeAllChildNodes(resultsListEl)
|
removeAllChildNodes(resultsListEl)
|
||||||
renderResults(resultsMarkup)
|
renderResults(resultsMarkup)
|
||||||
updatePager(page, numberOfAllPages)
|
updatePager(page, numberOfAllPages)
|
||||||
reinitalizeDefaultTooltipSet()
|
// reinitalizeDefaultTooltipSet()
|
||||||
|
// console.log($('[data-toggle="tooltip"]'))
|
||||||
|
// $('[data-toggle="tooltip"]').tooltip()
|
||||||
|
|
||||||
|
// toltip reinitialization after moving a page
|
||||||
|
resetTooltipTriggerList()
|
||||||
|
if (tooltipTriggerList.length > 0) {
|
||||||
|
// initialize tooltips
|
||||||
|
tooltipList(null, 'dark-gray-tooltip') // ADD for tooltip debug in the end -> [0].show()
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
try {
|
try {
|
||||||
@@ -160,12 +186,12 @@ async function changePage(newPage) {
|
|||||||
return page
|
return page
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// console.log(error)
|
// console.log(error)
|
||||||
let message = 'Prišlo je do napake.'
|
let message = i18next.t('Prišlo je do napake.')
|
||||||
if (error.response?.data) {
|
if (error.response?.data) {
|
||||||
message = error.response.data
|
message = error.response.data
|
||||||
} else if (error.request) {
|
} else if (error.request) {
|
||||||
return // return to bypass error caused (assumed) by popstate on iOS/MacOS
|
return // return to bypass error caused (assumed) by popstate on iOS/MacOS
|
||||||
message = 'Strežnik ni dosegljiv. Poskusite kasneje.'
|
message = i18next.t('Strežnik ni dosegljiv. Poskusite kasneje.')
|
||||||
}
|
}
|
||||||
alert(message)
|
alert(message)
|
||||||
updatePager()
|
updatePager()
|
||||||
@@ -210,119 +236,10 @@ function onResultClick(e) {
|
|||||||
try {
|
try {
|
||||||
if (tooltipTriggerList.length > 0) {
|
if (tooltipTriggerList.length > 0) {
|
||||||
// initialize tooltips
|
// initialize tooltips
|
||||||
tooltipList(null, 'gray-tooltip') // ADD for tooltip debug in the end -> [0].show()
|
tooltipList(null, 'dark-gray-tooltip') // ADD for tooltip debug in the end -> [0].show()
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
/*
|
|
||||||
// refresh: bool
|
|
||||||
function largeStringsOnSmallScreen(alwaysRefresh) {
|
|
||||||
let headwordTexts
|
|
||||||
let translationWords
|
|
||||||
let synonymWords
|
|
||||||
function init() {
|
|
||||||
headwordTexts = [...$('.rihw')]
|
|
||||||
translationWords = [...$('.term-h')].map(e => {
|
|
||||||
return e.children[0]
|
|
||||||
})
|
|
||||||
synonymWords = [...$('.syn-h'), ...$('.risy')]
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupToolTips() {
|
|
||||||
setDynamicTooltipList([])
|
|
||||||
if (headwordTexts) {
|
|
||||||
headwordTexts.map(ttElement =>
|
|
||||||
createTooltip(ttElement, ttElement.innerHTML, '')
|
|
||||||
)
|
|
||||||
// setDynamicTooltipList([...dynamicTooltipList, ...tt])
|
|
||||||
}
|
|
||||||
if (translationWords) {
|
|
||||||
translationWords.map(ttElement =>
|
|
||||||
createTooltip(ttElement, ttElement.innerHTML, '')
|
|
||||||
)
|
|
||||||
// setDynamicTooltipList([...dynamicTooltipList, ...tt])
|
|
||||||
}
|
|
||||||
if (synonymWords) {
|
|
||||||
synonymWords.map(ttElement =>
|
|
||||||
createTooltip(ttElement, ttElement.innerHTML, '')
|
|
||||||
)
|
|
||||||
// setDynamicTooltipList([...dynamicTooltipList, ...tt])
|
|
||||||
}
|
|
||||||
// console.log(dynamicTooltipList)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!headwordTexts || !translationWords || !synonymWords || alwaysRefresh) {
|
|
||||||
init()
|
|
||||||
}
|
|
||||||
|
|
||||||
function initTooltips() {
|
|
||||||
try {
|
|
||||||
// if (dynamicTooltipList.length || alwaysRefresh) {
|
|
||||||
setupToolTips()
|
|
||||||
// }
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeTooltips() {
|
|
||||||
try {
|
|
||||||
$('.rihw').tooltip('dispose')
|
|
||||||
$('.term-h').children().tooltip('dispose')
|
|
||||||
$('.syn-h').tooltip('dispose')
|
|
||||||
$('.risy').tooltip('dispose')
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
function manageResize() {
|
|
||||||
const tmpTerms = [...$('.term-h')].map(e => {
|
|
||||||
return e.children[0]
|
|
||||||
})
|
|
||||||
const elements = [...tmpTerms, ...$('.syn-h')]
|
|
||||||
// console.log(document.documentElement.clientWidth)
|
|
||||||
|
|
||||||
const dcw = document.documentElement.clientWidth
|
|
||||||
const checkBorders = (dcw > 991 && dcw < 1600) || dcw < 370
|
|
||||||
if (checkBorders) {
|
|
||||||
const TEXT_LIMIT = 17
|
|
||||||
elements.forEach(el => {
|
|
||||||
el.dataset.textStore = el.innerHTML.includes('...')
|
|
||||||
? el.dataset.textStore
|
|
||||||
: el.innerHTML
|
|
||||||
el.innerHTML = `${el.innerHTML.slice(0, TEXT_LIMIT)}${
|
|
||||||
el.innerHTML.length > TEXT_LIMIT ? '...' : ''
|
|
||||||
}`
|
|
||||||
})
|
|
||||||
|
|
||||||
initTooltips()
|
|
||||||
} else {
|
|
||||||
elements.forEach(el => {
|
|
||||||
if (el.dataset.textStore) {
|
|
||||||
el.innerHTML = el.dataset.textStore
|
|
||||||
}
|
|
||||||
})
|
|
||||||
removeTooltips()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
init: function () {
|
|
||||||
init()
|
|
||||||
},
|
|
||||||
refresh: function () {
|
|
||||||
init()
|
|
||||||
try {
|
|
||||||
setupToolTips()
|
|
||||||
} catch (e) {}
|
|
||||||
},
|
|
||||||
manageResize: manageResize
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const textsScreenManager = largeStringsOnSmallScreen(true)
|
|
||||||
|
|
||||||
textsScreenManager.manageResize()
|
|
||||||
|
|
||||||
window.onresize = textsScreenManager.manageResize
|
|
||||||
*/
|
|
||||||
|
|
||||||
// refresh: bool
|
// refresh: bool
|
||||||
function largeStringsOnSmallScreen(alwaysRefresh) {
|
function largeStringsOnSmallScreen(alwaysRefresh) {
|
||||||
let headwordTexts
|
let headwordTexts
|
||||||
@@ -336,30 +253,6 @@ function largeStringsOnSmallScreen(alwaysRefresh) {
|
|||||||
synonymWords = [...$('.syn-h'), ...$('.risy')]
|
synonymWords = [...$('.syn-h'), ...$('.risy')]
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
function setupToolTips() {
|
|
||||||
setDynamicTooltipList([])
|
|
||||||
if (headwordTexts) {
|
|
||||||
headwordTexts.map(ttElement =>
|
|
||||||
createTooltip(ttElement, ttElement.innerHTML, '')
|
|
||||||
)
|
|
||||||
// setDynamicTooltipList([...dynamicTooltipList, ...tt])
|
|
||||||
}
|
|
||||||
if (translationWords) {
|
|
||||||
translationWords.map(ttElement =>
|
|
||||||
createTooltip(ttElement, ttElement.innerHTML, '')
|
|
||||||
)
|
|
||||||
// setDynamicTooltipList([...dynamicTooltipList, ...tt])
|
|
||||||
}
|
|
||||||
if (synonymWords) {
|
|
||||||
synonymWords.map(ttElement =>
|
|
||||||
createTooltip(ttElement, ttElement.innerHTML, '')
|
|
||||||
)
|
|
||||||
// setDynamicTooltipList([...dynamicTooltipList, ...tt])
|
|
||||||
}
|
|
||||||
// console.log(dynamicTooltipList)
|
|
||||||
} */
|
|
||||||
|
|
||||||
if (!headwordTexts || !translationWords || !synonymWords || alwaysRefresh) {
|
if (!headwordTexts || !translationWords || !synonymWords || alwaysRefresh) {
|
||||||
init()
|
init()
|
||||||
}
|
}
|
||||||
@@ -379,169 +272,20 @@ function largeStringsOnSmallScreen(alwaysRefresh) {
|
|||||||
ttElement.tooltip('dispose')
|
ttElement.tooltip('dispose')
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
function initTooltips() {
|
|
||||||
try {
|
|
||||||
// if (dynamicTooltipList.length || alwaysRefresh) {
|
|
||||||
setupToolTips()
|
|
||||||
// }
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeTooltips() {
|
window.addEventListener('load', () => {
|
||||||
try {
|
$('[data-toggle="tooltip"]').tooltip()
|
||||||
$('.rihw').tooltip('dispose')
|
|
||||||
$('.term-h').children().tooltip('dispose')
|
|
||||||
$('.syn-h').tooltip('dispose')
|
|
||||||
$('.risy').tooltip('dispose')
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* let OLD_TEXT_LIMIT = 5000 // Guard to not prevent overdisposing
|
|
||||||
function manageResize() {
|
|
||||||
const tmpTerms = [...$('.term-h')].map(e => {
|
|
||||||
return e.children[0]
|
|
||||||
})
|
})
|
||||||
const elements = [...tmpTerms, ...$('.syn-h')]
|
|
||||||
// console.log(document.documentElement.clientWidth) */
|
|
||||||
|
|
||||||
/*
|
|
||||||
$('.word-constraint').each((i, element) => {
|
|
||||||
// WRONG const wordBr = $(`.word-constraint:nth-child(${i}) > .word-breakable`)
|
|
||||||
const wordList = $(element).find('.word-breakable')
|
|
||||||
|
|
||||||
wordList.each((i, wordElement) => {
|
|
||||||
// console.log(wordElement.innerText)
|
|
||||||
|
|
||||||
if (
|
|
||||||
wordElement.clientWidth > element.clientWidth &&
|
|
||||||
wordElement.innerText.split(' ').length <= 1
|
|
||||||
) {
|
|
||||||
console.log('Lion')
|
|
||||||
createTooltip($(wordElement))
|
|
||||||
wordElement.classList.push('text-ellipsis')
|
|
||||||
} else {
|
|
||||||
removeTooltip($(wordElement))
|
|
||||||
// console.log(wordElement.classList)
|
|
||||||
wordElement.className = Array.from(wordElement.classList)
|
|
||||||
.filter(e => e !== 'text-ellipsis')
|
|
||||||
.join(' ')
|
|
||||||
// console.log(wordElement.classList)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// console.log(wordBr)
|
|
||||||
})
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* const dcw = document.documentElement.clientWidth
|
|
||||||
// const checkBorders = (dcw > 420 && dcw < 750) || dcw < 370
|
|
||||||
|
|
||||||
let borderIndex
|
|
||||||
|
|
||||||
// check _common.scc long-text-strip and make sure the widths are aligned
|
|
||||||
if (dcw < 420) {
|
|
||||||
borderIndex = 0 // width = 8 chars (<420)
|
|
||||||
} else if (dcw < 750 || (dcw > 990 && dcw < 1750)) {
|
|
||||||
borderIndex = 1 // width = 12 chars (420 - 750, 990-1750)
|
|
||||||
} else if (dcw > 1750) {
|
|
||||||
borderIndex = 3 // width = 100% (>1759)
|
|
||||||
} else {
|
|
||||||
borderIndex = 2 // width = 25 chars (750-990)
|
|
||||||
}
|
|
||||||
|
|
||||||
let TEXT_LIMIT
|
|
||||||
if (borderIndex === 0) {
|
|
||||||
TEXT_LIMIT = 8
|
|
||||||
} else if (borderIndex === 1) {
|
|
||||||
TEXT_LIMIT = 12
|
|
||||||
} else if (borderIndex === 2) {
|
|
||||||
TEXT_LIMIT = 25
|
|
||||||
} else {
|
|
||||||
TEXT_LIMIT = null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (OLD_TEXT_LIMIT !== TEXT_LIMIT) {
|
|
||||||
elements.forEach(el => {
|
|
||||||
// console.log(el.innerHTML)
|
|
||||||
if (TEXT_LIMIT && el.innerHTML.length > TEXT_LIMIT) {
|
|
||||||
console.log(`DEBUG TEXT LIMIT: ${TEXT_LIMIT}`)
|
|
||||||
console.log(`DEBUG EL LEN: ${el.innerHTML.length}`)
|
|
||||||
console.log(`DEBUG EL TEXT: ${el.innerHTML}`)
|
|
||||||
console.log('TOOLTIP CREATED')
|
|
||||||
createTooltipEl(el, 'gray-tooltip')
|
|
||||||
} else {
|
|
||||||
removeTooltip($(el))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
OLD_TEXT_LIMIT = TEXT_LIMIT */
|
|
||||||
|
|
||||||
/*
|
|
||||||
if (checkBorders) {
|
|
||||||
console.log('HEYOO')
|
|
||||||
const TEXT_LIMIT = 17
|
|
||||||
elements.forEach(el => {
|
|
||||||
el.dataset.textStore = el.innerHTML.includes('...')
|
|
||||||
? el.dataset.textStore
|
|
||||||
: el.innerHTML
|
|
||||||
el.innerHTML = `${el.innerHTML.slice(0, TEXT_LIMIT)}${
|
|
||||||
el.innerHTML.length > TEXT_LIMIT ? '...' : ''
|
|
||||||
}`
|
|
||||||
})
|
|
||||||
|
|
||||||
initTooltips()
|
|
||||||
} else {
|
|
||||||
elements.forEach(el => {
|
|
||||||
if (el.dataset.textStore) {
|
|
||||||
el.innerHTML = el.dataset.textStore
|
|
||||||
}
|
|
||||||
})
|
|
||||||
removeTooltips()
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
/* }
|
|
||||||
|
|
||||||
return {
|
|
||||||
init: function () {
|
|
||||||
init()
|
|
||||||
},
|
|
||||||
refresh: function () {
|
|
||||||
init()
|
|
||||||
},
|
|
||||||
manageResize: manageResize
|
|
||||||
} */
|
|
||||||
}
|
|
||||||
|
|
||||||
// const textsScreenManager = largeStringsOnSmallScreen(true)
|
|
||||||
|
|
||||||
// textsScreenManager.manageResize()
|
|
||||||
|
|
||||||
// window.onresize = textsScreenManager.manageResize
|
|
||||||
|
|
||||||
/*
|
|
||||||
function transferText(sideMenuText) {
|
|
||||||
const siteHeading = document.getElementById('site-heading')
|
|
||||||
const siteHeadingTextContent = siteHeading.textContent
|
|
||||||
const navTitle = document.getElementById('nav-title')
|
|
||||||
|
|
||||||
if (document.body.clientWidth <= 1200) {
|
|
||||||
navTitle.textContent = siteHeadingTextContent
|
|
||||||
siteHeading.style.display = 'none'
|
|
||||||
} else {
|
|
||||||
navTitle.textContent = sideMenuText
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
function handleProperTextDisplay() {
|
function handleProperTextDisplay() {
|
||||||
|
// const BROWSER_UNUSUAL_OFFSET = 17
|
||||||
if (/\/iskanje/.test(currentPagePath)) {
|
if (/\/iskanje/.test(currentPagePath)) {
|
||||||
transferText('Iskanje po slovarjih', true)
|
transferText('Iskanje po slovarjih', true, 'site-heading') //,
|
||||||
|
// BROWSER_UNUSUAL_OFFSET
|
||||||
|
// )
|
||||||
} else if (/\/termin/.test(currentPagePath)) {
|
} else if (/\/termin/.test(currentPagePath)) {
|
||||||
transferText('', true)
|
transferText('', true, 'site-heading') // , BROWSER_UNUSUAL_OFFSET)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* global $, currentPagePath */
|
/* global $, currentPagePath, i18next */
|
||||||
|
|
||||||
// const currentPagePath = location.pathname
|
// const currentPagePath = location.pathname
|
||||||
|
|
||||||
@@ -60,13 +60,13 @@ function initSelect() {
|
|||||||
const userPassword2 = ce.userConfirmationPassEl.value
|
const userPassword2 = ce.userConfirmationPassEl.value
|
||||||
|
|
||||||
if (userPassword1 === '') {
|
if (userPassword1 === '') {
|
||||||
alert('Prosim, vnesite geslo')
|
alert(i18next.t('Prosim, vnesite geslo'))
|
||||||
} else if (userPassword2 === '') {
|
} else if (userPassword2 === '') {
|
||||||
alert('Prosim, ponovno vnesite geslo')
|
alert(i18next.t('Prosim, ponovno vnesite geslo'))
|
||||||
} else if (userPassword1 !== userPassword2) {
|
} else if (userPassword1 !== userPassword2) {
|
||||||
alert('Gesli se ne ujemata')
|
alert(i18next.t('Gesli se ne ujemata'))
|
||||||
} else {
|
} else {
|
||||||
alert('Gesli se ujemata!')
|
alert(i18next.t('Gesli se ujemata!'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,7 +115,7 @@ function deleteField(ele) {
|
|||||||
// Summernote
|
// Summernote
|
||||||
|
|
||||||
$('.summernote').summernote({
|
$('.summernote').summernote({
|
||||||
placeholder: 'Na kratko opišite zasnovo in namen slovarja.',
|
placeholder: i18next.t('Na kratko opišite zasnovo in namen slovarja.'),
|
||||||
height: 300,
|
height: 300,
|
||||||
minheight: 150,
|
minheight: 150,
|
||||||
toolbar: [
|
toolbar: [
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* global axios */
|
/* global axios, i18next */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
TODO
|
TODO
|
||||||
@@ -8,6 +8,8 @@ not just language filter, but filter for many properties...
|
|||||||
|
|
||||||
/** BEGIN VARIABLE CONSTRUCTION AREA */
|
/** BEGIN VARIABLE CONSTRUCTION AREA */
|
||||||
|
|
||||||
|
let allAggregationDataForSF = null
|
||||||
|
|
||||||
// TODO insert all sections of ids
|
// TODO insert all sections of ids
|
||||||
const ids = [
|
const ids = [
|
||||||
'src-lang-side',
|
'src-lang-side',
|
||||||
@@ -176,6 +178,10 @@ function cleanOldList() {
|
|||||||
modalListRoot.innerHTML = ''
|
modalListRoot.innerHTML = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setTitleOnModal(title) {
|
||||||
|
document.getElementById('modal-filter-label').innerHTML = title
|
||||||
|
}
|
||||||
|
|
||||||
async function selectMoreListener(event) {
|
async function selectMoreListener(event) {
|
||||||
const idstring = `${event.currentTarget.id
|
const idstring = `${event.currentTarget.id
|
||||||
.replace('select-', '')
|
.replace('select-', '')
|
||||||
@@ -183,12 +189,17 @@ async function selectMoreListener(event) {
|
|||||||
.concat('-side')}`
|
.concat('-side')}`
|
||||||
// console.log(extractMap(idstring))
|
// console.log(extractMap(idstring))
|
||||||
|
|
||||||
|
const titleText =
|
||||||
|
event.currentTarget.parentElement.parentElement.children[0].textContent
|
||||||
|
|
||||||
|
setTitleOnModal(titleText)
|
||||||
|
|
||||||
// mp is the array of the currently displayed filters (required to get the checked information)
|
// mp is the array of the currently displayed filters (required to get the checked information)
|
||||||
// const mp = initializedValues[idstring]
|
// const mp = initializedValues[idstring]
|
||||||
|
|
||||||
// all filters for search
|
// all filters for search
|
||||||
// const allAggregationData = { id: idstring }
|
// const allAggregationData = { id: idstring }
|
||||||
const allAggregationData = { id: idstring }
|
allAggregationDataForSF = { id: idstring }
|
||||||
|
|
||||||
const currentURL = new URL(window.location.href)
|
const currentURL = new URL(window.location.href)
|
||||||
const searchParams = currentURL.searchParams
|
const searchParams = currentURL.searchParams
|
||||||
@@ -200,10 +211,10 @@ async function selectMoreListener(event) {
|
|||||||
|
|
||||||
// const parsed = JSON.parse(event.currentTarget.dataset.aggregationInfo ?? '[]')
|
// const parsed = JSON.parse(event.currentTarget.dataset.aggregationInfo ?? '[]')
|
||||||
|
|
||||||
allAggregationData.items = data[idToSearchFiltersMapper(idstring)]
|
allAggregationDataForSF.items = data[idToSearchFiltersMapper(idstring)]
|
||||||
|
|
||||||
cleanOldList()
|
cleanOldList()
|
||||||
initModalList(allAggregationData.id, allAggregationData.items)
|
initModalList(allAggregationDataForSF.id, allAggregationDataForSF.items)
|
||||||
}
|
}
|
||||||
|
|
||||||
function idToSearchFiltersMapper(inpt) {
|
function idToSearchFiltersMapper(inpt) {
|
||||||
@@ -297,6 +308,18 @@ function finishSelectingFromModal(e) {
|
|||||||
window.location.href = url
|
window.location.href = url
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function finishSelectingFromModal2(e) {
|
||||||
|
const url = new URL(window.location.href)
|
||||||
|
const searchParams = url.searchParams
|
||||||
|
searchParams.delete(queryOrderedArr[sectionIDBattery])
|
||||||
|
|
||||||
|
selectedIds[sectionIDBattery].forEach(e => {
|
||||||
|
searchParams.append(queryOrderedArr[sectionIDBattery], e)
|
||||||
|
})
|
||||||
|
|
||||||
|
window.location.href = url
|
||||||
|
}
|
||||||
|
|
||||||
function reRenderSpecific(event) {
|
function reRenderSpecific(event) {
|
||||||
const idstring = event.currentTarget.id
|
const idstring = event.currentTarget.id
|
||||||
const id = idstring.replace('show-all-', '')
|
const id = idstring.replace('show-all-', '')
|
||||||
@@ -349,7 +372,7 @@ function rePlotAndCheck(sectionId, selectedIdsArray, plotInverse = false) {
|
|||||||
const img = document.createElement('img')
|
const img = document.createElement('img')
|
||||||
img.setAttribute('src', '/images/chevron-left-blue.svg')
|
img.setAttribute('src', '/images/chevron-left-blue.svg')
|
||||||
const span = document.createElement('span')
|
const span = document.createElement('span')
|
||||||
span.innerHTML = 'PRIKAŽI VSE'
|
span.innerHTML = i18next.t('PRIKAŽI VSE')
|
||||||
|
|
||||||
anchr.appendChild(img)
|
anchr.appendChild(img)
|
||||||
anchr.appendChild(span)
|
anchr.appendChild(span)
|
||||||
@@ -378,6 +401,7 @@ function clearModalFilters() {
|
|||||||
e.children[0].children[0].checked = false
|
e.children[0].children[0].checked = false
|
||||||
})
|
})
|
||||||
selectedIds[sectionIDBattery] = []
|
selectedIds[sectionIDBattery] = []
|
||||||
|
clearSnapshots()
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearModalFiltersWID(id) {
|
function clearModalFiltersWID(id) {
|
||||||
@@ -402,7 +426,7 @@ function initializeClickables() {
|
|||||||
.addEventListener('click', selectFromModal)
|
.addEventListener('click', selectFromModal)
|
||||||
document
|
document
|
||||||
.querySelector('#sf-modal-button')
|
.querySelector('#sf-modal-button')
|
||||||
.addEventListener('click', finishSelectingFromModal)
|
.addEventListener('click', finishSelectingFromModal2)
|
||||||
|
|
||||||
document.querySelector('#ccf').addEventListener('click', clearModalFilters)
|
document.querySelector('#ccf').addEventListener('click', clearModalFilters)
|
||||||
|
|
||||||
@@ -428,6 +452,40 @@ function initializeClickables() {
|
|||||||
|
|
||||||
// initModalList('id1', langsMapModal)
|
// initModalList('id1', langsMapModal)
|
||||||
|
|
||||||
|
function filterModalEntries(event) {
|
||||||
|
cleanOldList()
|
||||||
|
|
||||||
|
// console.log(allAggregationDataForSF.items)
|
||||||
|
const prompt = document.getElementById('filterSfmText')
|
||||||
|
|
||||||
|
let filtered
|
||||||
|
if (prompt) {
|
||||||
|
filtered = allAggregationDataForSF.items.filter(item =>
|
||||||
|
item.name.toLowerCase().includes(prompt.value.toLowerCase())
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
initModalList(allAggregationDataForSF.id, filtered)
|
||||||
|
}
|
||||||
|
|
||||||
|
const sfmFilterButton = document.getElementById('filter-sfm')
|
||||||
|
|
||||||
|
const sfmOnEnterListener = document.querySelector('.onEnterListener')
|
||||||
|
|
||||||
|
if (sfmFilterButton) {
|
||||||
|
sfmFilterButton.addEventListener('click', filterModalEntries)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sfmOnEnterListener) {
|
||||||
|
sfmOnEnterListener.addEventListener('keyup', event => {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
filterModalEntries(event)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// global run
|
/// global run
|
||||||
|
|
||||||
initializeClickables()
|
initializeClickables()
|
||||||
|
|||||||
@@ -70,20 +70,23 @@ function generateTooltipWithStyles(
|
|||||||
) {
|
) {
|
||||||
let nonEmptyContent
|
let nonEmptyContent
|
||||||
if (!content) {
|
if (!content) {
|
||||||
nonEmptyContent = el => {
|
|
||||||
el.getAttribute('data-tooltip-content')
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
nonEmptyContent = content
|
|
||||||
}
|
|
||||||
|
|
||||||
return generateTooltipBase(list, function (tooltipTriggerEl) {
|
return generateTooltipBase(list, function (tooltipTriggerEl) {
|
||||||
return new bootstrap.Tooltip(tooltipTriggerEl, {
|
return new bootstrap.Tooltip(tooltipTriggerEl, {
|
||||||
html: true,
|
html: true,
|
||||||
title: nonEmptyContent,
|
title: tooltipTriggerEl.getAttribute('data-tooltip-content') || '',
|
||||||
customClass: styles
|
customClass: styles
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
nonEmptyContent = content
|
||||||
|
return generateTooltipBase(list, function (tooltipTriggerEl) {
|
||||||
|
return new bootstrap.Tooltip(tooltipTriggerEl, {
|
||||||
|
html: true,
|
||||||
|
title: nonEmptyContent || '',
|
||||||
|
customClass: styles
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateTooltipBase(list, fn) {
|
function generateTooltipBase(list, fn) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* global axios */
|
/* global axios, i18next */
|
||||||
|
|
||||||
function autoGrow(element) {
|
function autoGrow(element) {
|
||||||
element.style.height = '5px'
|
element.style.height = '5px'
|
||||||
@@ -156,14 +156,14 @@ function editRowBodyForConsultancy(fieldInfo) {
|
|||||||
fieldInfo.newButtonGroup = newButtonGroup
|
fieldInfo.newButtonGroup = newButtonGroup
|
||||||
fieldInfo.cancelButton = cancelButton
|
fieldInfo.cancelButton = cancelButton
|
||||||
fieldInfo.saveButton = saveButton
|
fieldInfo.saveButton = saveButton
|
||||||
cancelButton.textContent = 'Prekliči'
|
cancelButton.textContent = i18next.t('Prekliči')
|
||||||
cancelButton.className = 'btn btn-secondary me-2'
|
cancelButton.className = 'btn btn-secondary me-2'
|
||||||
cancelButton.style.height = '33px'
|
cancelButton.style.height = '33px'
|
||||||
cancelButton.style.width = '105px'
|
cancelButton.style.width = '105px'
|
||||||
cancelButton.addEventListener('click', () =>
|
cancelButton.addEventListener('click', () =>
|
||||||
abortConsultancyUserEditing(fieldInfo)
|
abortConsultancyUserEditing(fieldInfo)
|
||||||
)
|
)
|
||||||
saveButton.textContent = 'POTRDI'
|
saveButton.textContent = i18next.t('POTRDI')
|
||||||
saveButton.className = 'btn btn-primary'
|
saveButton.className = 'btn btn-primary'
|
||||||
saveButton.style.height = '33px'
|
saveButton.style.height = '33px'
|
||||||
saveButton.style.width = '105px'
|
saveButton.style.width = '105px'
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
@@ -0,0 +1,710 @@
|
|||||||
|
{
|
||||||
|
" Terminološke odgovore pripravljajo sodelavci Terminološke sekcije Inštituta za slovenski jezik Frana Ramovša ZRC SAZU (in so objavljeni tudi na spletišču <a href='https://isjfr.zrc-sazu.si/sl/terminologisce/svetovanje'>Terminologišče</a>), ki pri delu upoštevajo osnovna terminološka načela.": " Answers to terminology questions are prepared by the employees of the Fran Ramovš Institute of the Slovenian Language at the Research Centre of the Slovenian Academy of Sciences and Arts (the answers are also published on their <a href='https://isjfr.zrc-sazu.si/sl/terminologisce/svetovanje'>Terminology</a> website). When addressing the questions, the consultants take into account the basic terminology principles.",
|
||||||
|
"(STOP) Termini": "(STOP) Terms",
|
||||||
|
"[ni termina]": "[no term]",
|
||||||
|
"#": "#",
|
||||||
|
"<b>Odgovor:</b>": "<b>Answer:</b>",
|
||||||
|
"Administracija": "Administration",
|
||||||
|
"Administrator": "Administrator",
|
||||||
|
"Administratorska konzola": "Admin console",
|
||||||
|
"Administratorski del portala je namenjen skupini oseb, ki vsebinsko in tehnično ureja portal, odpira slovarske vire, daje pooblastila posameznim uporabnikom in skrbi za vsebinsko in tehnično urejenost portala. Izberite module, ki jih boste ponudili na terminološkem portalu in na kratko opišite, kaj ponuja vaš portal.": "The administrative functions of the portal is intended for the group of people who edit the content and the technical aspects of the portal, publish dictionaries, give authorizations to other individual users and are responsible for the content and the technical aspects of the portal.",
|
||||||
|
"Aktivacija računa": "Account activation",
|
||||||
|
"Ali res želite izbrisati vnos? Dejanja ni mogoče razveljaviti.": "Are you sure you want to delete the entry? This action is cannot be undone.",
|
||||||
|
"Ali želite indeksirati?": "Do you want to index?",
|
||||||
|
"Ali želite izbrisati slovarski sestavek?": "Do you want to delete the dictionary entry?",
|
||||||
|
"Ali želite objaviti vsa gesla?": "Do you want to publish all entries?",
|
||||||
|
"Ali želite zbrisati ta profil. S tem bodo izbrisani vsi podatki, ki ste jih ustvarili": "Do you want to delete this profile? This action will also delete all data you created.",
|
||||||
|
"Ali želite zbrisati ta vnos?": "Do you want to delete this entry?",
|
||||||
|
"angleščina": "English",
|
||||||
|
"ANGLEŠKI NASLOV SLOVARJA *": "DICTIONARY TITLE IN ENGLISH *",
|
||||||
|
"ANGLEŠKI OPIS PORTALA": "PORTAL DESCRIPTION IN ENGLISH",
|
||||||
|
"ANGLEŠKO IME PORTALA": "PORTAL NAME IN ENGLISH",
|
||||||
|
"Angleško ime terminološkega portala.": "English name of the terminology portal.",
|
||||||
|
"Avtor": "Author",
|
||||||
|
"AVTOR": "AUTHOR",
|
||||||
|
"AVTOR SLOVARJA": "DICTIONARY AUTHOR",
|
||||||
|
"Avtor:": "Author:",
|
||||||
|
"Avtorja": "Authors",
|
||||||
|
"Avtorji": "Authors",
|
||||||
|
"Avtorji mnenja:": "Opinion authors:",
|
||||||
|
"Basic": "Basic",
|
||||||
|
"Besedila": "Texts",
|
||||||
|
"Besedilo gre tukaj": "The text goes here",
|
||||||
|
"Brisanje je onemogočeno, doker se nalagajo nove datoteke.": "Deletion is disabled while new files are being uploaded.",
|
||||||
|
"Brisanje je onemogočeno, saj je eno še v procesu.": "Deletion is disabled because previous deletion has not yet been completed.",
|
||||||
|
"Brisanje slovarja": "Delete the dictionary",
|
||||||
|
"Brisanje slovarskih sestavkov": "Delete dictionary entries",
|
||||||
|
"Briši": "Delete",
|
||||||
|
"BRIŠI": "DELETE",
|
||||||
|
"Briši uporabnika": "Delete user",
|
||||||
|
"Če imate pripravljen slovar v enem od formatov, ki so navedeni spodaj, lahko svoje podatke uvozite.": "If you have a dictionary in one of the formats listed below, you can import your data.",
|
||||||
|
"Če izberete to možnost, se bodo vsi doslejšnji slovarski sestavki ob uvozu nove datoteke izbrisali.": "If you select this option, all previous dictionary entries will be deleted when the new file is imported.",
|
||||||
|
"Če želite dodati novo luščenje, morate najprej pobrisati vsaj eno od obstoječih, saj je na posameznega uporabnika dovoljenih največ 5 luščenj.": "If you want to add a new extraction, you must first delete at least one of your current extraction, as a maximum of 5 extractions are allowed per user.",
|
||||||
|
"Celotni naslov slovarja v angleščini.": "Full title of the dictionary in English.",
|
||||||
|
"Celotni naslov slovarja, ki bo zabeležen v bibliografskih podatkih.": "The full dictionary title to be recorded in the bibliographic information.",
|
||||||
|
"Ciljni jeziki": "Target languages",
|
||||||
|
"Čim bolj natančno opišite terminološki problem, zlasti opišite vsebino pojma.": "Describe the terminological problem as precisely as you can, especially the concept the term should represent.",
|
||||||
|
"DATOTEKA": "FILE",
|
||||||
|
"Datoteka uspešno naložena": "File uploaded successfully",
|
||||||
|
"DATUM": "DATE",
|
||||||
|
"Datum objave/spremembe": "Date of publication/modification",
|
||||||
|
"Datum vprašanja:": "Question date:",
|
||||||
|
"Definicija": "Definition",
|
||||||
|
"DEFINICIJA": "DEFINITION",
|
||||||
|
"DEFINICIJA:": "DEFINITION:",
|
||||||
|
"Delete profile": "Delete profile",
|
||||||
|
"Deli": "Share",
|
||||||
|
"DELI": "SHARE",
|
||||||
|
"dni.": "days.",
|
||||||
|
"do številke": "and",
|
||||||
|
"Dodaj": "Add",
|
||||||
|
"DODAJ": "ADD",
|
||||||
|
"Dodaj besedilo za luščenje terminoloških kandidatov iz lastnega specializiranega korpusa. Ko boste dodali vsa besedila, ki ste jih izbrali, morate izbiro shraniti.": "Add text to extract term candidates from your own specialized corpus. After adding all selected texts, save your selection.",
|
||||||
|
"Dodaj datoteko": "Add file",
|
||||||
|
"Dodaj komentar...": "Add a comment...",
|
||||||
|
"Dodaj odgovor...": "Add a reply...",
|
||||||
|
"Dodaj opravilo": "Add a task",
|
||||||
|
"Dodaj povezani termin.": "Add a related term.",
|
||||||
|
"Dodaj povezavo": "Add link",
|
||||||
|
"Dodaj slovar": "Add a dictionary",
|
||||||
|
"Dodajte ime in priimek naslednjega avtorja slovarja.": "Add first and last name of the next dictionary author.",
|
||||||
|
"Dodajte ime in priimek naslednjega avtorja slovarja..": "Add first and last name of the next dictionary author..",
|
||||||
|
"Dodajte leto izida besedil, ki jih želite luščiti. Lahko dodate več posameznih let. Če boste polje pustili prazno, bodo vključena vsa leta. Če bo besedil preveč, boste morali omejiti izbiro.": "Add the year of publication of the texts to be used for extraction. You can add several years. If the field is left empty, all years will be included. If this results in too many results, you will need to limit your selection.",
|
||||||
|
"Dodajte podatek o zunajjezikovnih okoliščinah, ki niso povezane s pojmom, npr. letnico.": "Add information about extra-linguistic circumstances unrelated to the concept, e.g. ",
|
||||||
|
"Dodajte povezavo do slike.": "Link an image.",
|
||||||
|
"Dodajte povezavo do videa.": "Link a video.",
|
||||||
|
"Dodajte povezavo do zvočnega posnetka.": "Link an audio file.",
|
||||||
|
"Dodajte termin, ki je sicer definiran v samostojnem slovarskem sestavku, vendar je povezan s terminom, ki ga opisujete v tem slovarskem sestavku.": "Add a term that is defined in a separate dictionary entry but is related to the term you are describing in this dictionary entry.",
|
||||||
|
"Dodajte terminološki odgovor in ga utemeljite.": "Add a terminological answer and justify it.",
|
||||||
|
"Dodajte tujejezične ustreznike, ki se za opisani pojem tudi uporabljajo v tujem jeziku.": "Add foreign language equivalents that are also used in a foreign language for the described concept.",
|
||||||
|
"Dodajte tujejezični ustreznik.": "Add a foreign language equivalent.",
|
||||||
|
"Dodate lahko seznam besed, ki jih v seznam terminoloških kandidatov ne želite vključiti. Seznam naj bo shranjen v formatu .txt. <a class=\"smaller-gray-info\" href=\"/pomoc#help-stop-lists\">Več...</a>": "You can add a list of words that you do not want on the list of term candidates. The list should be saved in a .txt format. <a class=\"smaller-gray-info\" href=\"/pomoc#help-stop-lists\">Read more...</a>",
|
||||||
|
"Dodate lahko seznam besed, ki jih v seznam terminoloških kandidatov ne želite vključiti. Seznam naj bo shranjen v formatu .txt. Na koncu morate spremembe shraniti. <a class=\"smaller-gray-info\" href=\"/pomoc#help-stop-lists\" target=\"_blank\">Več ...</a>": "You can add a list of words that you do not want on the list of term candidates. The list should be saved in a .txt format. Save all changes after you are done. <a class=\"smaller-gray-info\" href=\"/pomoc#help-stop-lists\" target=\"_blank\">Read more...</a>",
|
||||||
|
"Dodate lahko seznam besed, ki jih v seznam terminoloških kandidatov ne želite vključiti. Seznam naj bo shranjen v formatu .txt. Vse spremembe morate shraniti. <a class=\"smaller-gray-info\" href=\"/pomoc#help-stop-lists\">Več...</a>": "You can add a list of words that you do not want on the list of term candidates. The list should be saved in a .txt format. <a class=\"smaller-gray-info\" href=\"/pomoc#help-stop-lists\">Read more...</a>",
|
||||||
|
"Dodaten avtor.": "Additional author.",
|
||||||
|
"DODATNI POVEZANI TERMIN": "ADDITIONAL RELATED TERM",
|
||||||
|
"Dodeli": "Assign",
|
||||||
|
"DODELI": "ASSIGN",
|
||||||
|
"Določite lastnosti terminoloških virov na portalu, zlasti minimalno število sestavkov, ki so pogoj za objavo, možnosti potrjevanja objave novih slovarjev, število različic slovarja, ki jih lahko hrani posamezni uporabnik. Te nastavitve veljajo za vse terminološke vire na portalu.": "Define the characteristics of the terminology resources on the portal, specifically the minimal number of entries required to publish a dictionary, the options for approving the publication of new terminology dictionaries, as well as the number of dictionary versions and exports that can be saved by individual users. These settings apply to all terminology resources on the portal.",
|
||||||
|
"Domači slovarji": "Local dictionaries",
|
||||||
|
"Drugo": "Other",
|
||||||
|
"DRUGO": "OTHER",
|
||||||
|
"DRUGO:": "OTHER:",
|
||||||
|
"Dvočrkovna oznaka terminološkega portala.": "Two-letter label of the terminology portal.",
|
||||||
|
"E - naslov": "E-mail address",
|
||||||
|
"E-naslov": "E-mail address",
|
||||||
|
"E-NASLOV": "E-MAIL ADDRESS",
|
||||||
|
"E-pošta": "E-mail",
|
||||||
|
"ELEKTRONSKI NASLOV": "E-MAIL ADDRESS",
|
||||||
|
"Elektronski naslov trenutno prijavljenega uporabnika.": "E-mail address of the currently logged-in user.",
|
||||||
|
"Elektronski naslov že obstaja": "The e-mail address already exists",
|
||||||
|
"EVROPSKI SKLAD ZA REGIONALNI RAZVOJ": "EUROPEAN REGIONAL DEVELOPMENT FUND",
|
||||||
|
"FAZA UREJANJA": "EDITING PHASE",
|
||||||
|
"Faze urejanja": "Editing phases",
|
||||||
|
"Filtri": "Filters",
|
||||||
|
"Filtriranje": "Filter",
|
||||||
|
"FORMAT ZAPISA": "FILE FORMAT",
|
||||||
|
"Gesli se ne ujemata": "Passwords do not match",
|
||||||
|
"Gesli se ujemata!": "Passwords match!",
|
||||||
|
"Geslo": "Password",
|
||||||
|
"GESLO": "PASSWORD",
|
||||||
|
"Geslo je prekratko": "The password is too short",
|
||||||
|
"Geslo je prekratko.": "The password is too short.",
|
||||||
|
"Geslo se ne ujema": "Incorrect password",
|
||||||
|
"Geslo se ne ujema.": "Incorrect password.",
|
||||||
|
"Hrvaščina": "Croatian",
|
||||||
|
"Hvala za poslano vprašanje, ki je bilo posredovano svetovalcem terminološkega portala. Odgovor boste prejeli na e-naslov, ki ste ga navedli ob registraciji.": "Thank you for submitting the question, which was forwarded to the terminology portal consultants. The answer will be sent to the e-mail address you used for registration.",
|
||||||
|
"Hvala za poslano vprašanje, ki je bilo posredovano v Terminološko svetovalnico ZRC SAZU. Odgovor boste prejeli na e-naslov, ki ste ga navedli ob registraciji.": "Thank you for submitting the question, which was forwarded to the Terminological Counselling of the ZRC SAZU. The answer will be sent to the e-mail address you used for registration.",
|
||||||
|
"ID Slovarja": "Dictionary ID",
|
||||||
|
"ID: Ni idja": "ID: No ID",
|
||||||
|
"IDEKSIRAJ": "INDEX",
|
||||||
|
"Imate neshranjene spremebe. Ali jih želite shraniti?": "Your changes have not been saved. Do you want to save them?",
|
||||||
|
"Imate neshranjene spremembe. Ali jih želite shraniti?": "Your changes have not been saved. Do you want to save them?",
|
||||||
|
"Ime": "First name",
|
||||||
|
"IME": "FIRST NAME",
|
||||||
|
"IME DATOTEKE": "FILE NAME",
|
||||||
|
"Ime in opis": "Name and description",
|
||||||
|
"IME IN PRIIMEK": "FIST AND LAST NAME",
|
||||||
|
"Ime in priimek trenutno prijavljenega uporabnika.": "First and last name of the currently logged-in user.",
|
||||||
|
"IME LUŠČENJA": "EXTRACTION NAME",
|
||||||
|
"IME PORTALA": "PORTAL NAME",
|
||||||
|
"IME PORTALA *": "PORTAL NAME *",
|
||||||
|
"Ime Priimek": "First Name Last Name",
|
||||||
|
"Ime registriranega uporabnika.": "Name of rhe registered user.",
|
||||||
|
"Ime slovarja": "Dictionary name",
|
||||||
|
"Ime, s katerim se uporabnik predstavlja na terminološkem portalu.": "The name with which the user presents himself on the terminology portal.",
|
||||||
|
"Indeksiranje slovarja": "Index dictionary",
|
||||||
|
"Institucija": "Institution",
|
||||||
|
"INSTITUCIJA": "INSTITUTION",
|
||||||
|
"Institucija, kjer trenutno prijavljeni uporabnik deluje.": "The institution where the currently logged-in user works.",
|
||||||
|
"Interni": "Internal",
|
||||||
|
"Išči": "Search",
|
||||||
|
"Išči po slovarju": "Search the dictionary",
|
||||||
|
"ISKANI NIZ JE BIL NAJDEN": "THE SEARCH STRING WAS FOUND",
|
||||||
|
"Iskanje": "Search",
|
||||||
|
"Iskanje po slovarjih": "Search the dictionaries",
|
||||||
|
"Iskanje po vseh": "Search all",
|
||||||
|
"ISSN": "ISSN",
|
||||||
|
"ISSN OZNAKA": "ISSN CODE",
|
||||||
|
"ISSN oznaka.": "ISSN code.",
|
||||||
|
"Italijanščina": "Italian",
|
||||||
|
"IZBERI": "SELECT",
|
||||||
|
"Izberi datoteko": "Select a file",
|
||||||
|
"IZBERI VEČ": "SELECT MORE",
|
||||||
|
"Izberite enega od rezultatov luščenja s seznama.": "Select one of the extraction results from the list.",
|
||||||
|
"Izberite format datoteke, v kateri je slovar shranjen na vašem računalniku.": "Select the file format of the dictionary saved on your computer.",
|
||||||
|
"Izberite format izpisa terminološkega slovarja.": "Select the file format to export the terminology dictionary.",
|
||||||
|
"Izberite luščenje": "Select extraction",
|
||||||
|
"Izberite področje": "Select an domain",
|
||||||
|
"Izberite področje iz seznama področij, da zmanjšate obseg besedil, iz katerih bo potekalo luščenje.": "Select a domain from the the list of domains to limit the scope of texts used for extraction.",
|
||||||
|
"Izberite področje svojega terminološkega slovarja na seznamu področij.": "Select the domain of your dictionary from the list of domains.",
|
||||||
|
"Izberite slovarske podatke, ki ste jih shranili na svojem računalniku.": "Select the dictionary data you saved on your computer.",
|
||||||
|
"Izberite takšno ime, ki vam bo pomagalo slediti rezultatom, če boste luščenje besedil opravili večkrat.": "Choose a name that will help you keep track of the results if you decide to run several extractions.",
|
||||||
|
"Izberite vrsto dokumenta iz seznama, npr. članek, diplomsko delo.": "Select the document type from the list, e.g. article, graduate thesis.",
|
||||||
|
"Izberite vse tuje jezike, ki jih bo terminološki slovar vseboval.": "Select all foreign languages you want to include into the terminology dictionary.",
|
||||||
|
"Izberite, ali želite izpisati samo jezikovno pregledane slovarske sestavke.": "Choose whether you want to list only linguistically reviewed dictionary entries.",
|
||||||
|
"Izberite, ali želite izpisati samo objavljene ali tudi neobjavljene slovarske sestavke.": "Choose whether you want to list only published or unpublished dictionary entries.",
|
||||||
|
"Izberite, ali želite izpisati samo strokovno pregledane slovarske sestavke.": "Choose whether you want to list only terminologically reviewed dictionary entries.",
|
||||||
|
"Izberite, ali želite izpisati vse slovarske sestavke ali samo tiste, ki so v določeni fazi urejanja.": "Choose whether you want to list all dictionary entries or only those in a certain editing phase.",
|
||||||
|
"Izberite, katere slovarske sestavke želite izpisati glede na njihovo veljavnost.": "Choose which dictionary entries you want to list based on their validity.",
|
||||||
|
"Izbrišete lahko celoten slovar z vsemi metapodatki. Dejanja ni mogoče preklicati.": "You can delete the entire dictionary with all metadata. The action is irreversible.",
|
||||||
|
"Izbrišete lahko vse doslej obdelane slovarske sestavke in ohranite vse metapodatke o slovarju. Dejanja ni mogoče preklicati.": "You can delete all dictionary entries you have edited until now and keep all dictionary metadata. The action is irreversible.",
|
||||||
|
"Izbriši": "Delete",
|
||||||
|
"IZBRIŠI": "DELETE",
|
||||||
|
"Izbriši obstoječe slovarske sestavke.": "Delete existing dictionary entries.",
|
||||||
|
"Izbriši polje": "Remove the field",
|
||||||
|
"Izbriši račun": "Delete account",
|
||||||
|
"Izgovor": "Pronunciation",
|
||||||
|
"Izpolnite polja in na kratko opišite vsebino terminološkega slovarja.": "Fill in the fields and briefly describe the contents of the terminology dictionary.",
|
||||||
|
"Izvoz": "Export",
|
||||||
|
"Izvozi": "Export",
|
||||||
|
"IZVOZI": "EXPORT",
|
||||||
|
"Izvozi termine od številke": "Export entries between the numbers",
|
||||||
|
"je že v tabeli.": "is already in the table.",
|
||||||
|
"Jeziki": "Languages",
|
||||||
|
"Jeziki iskanja": "Search languages",
|
||||||
|
"Jezikovni pregled": "Language review",
|
||||||
|
"Jezikovno pregledano": "Linguistically reviewed",
|
||||||
|
"KANONIČNA OBLIKA": "CANONICAL FORM",
|
||||||
|
"KLJUČNE BESEDE": "KEY WORDS",
|
||||||
|
"Ko bo luščenje uspešno zaključeno, boste na svoj elektronski naslov, ki ste ga navedli ob registraciji, prejeli obvestilo.": "After the extraction is successfully completed, you will receive a notification to the email address that you provided during registration.",
|
||||||
|
"Ko boste končali z urejanjem svojega terminološkega slovarja, lahko objavite vse slovarske sestavke, ki bodo postali vidni vsem uporabnikom. Vaše dejanje mora potrditi še administrator portala.": "After you finish editing your terminology dictionary, you can publish all the dictionary entries, which will become visible to all users. Your action must be approved by the portal administrator.",
|
||||||
|
"komentar": "comment",
|
||||||
|
"komentarja": "comments",
|
||||||
|
"komentarjev": "comments",
|
||||||
|
"komentarji": "comments",
|
||||||
|
"Komentarji": "Comments",
|
||||||
|
"Končan": "Finished",
|
||||||
|
"Konec": "Finish",
|
||||||
|
"Kontrola objave": "Publication control",
|
||||||
|
"KONTROLA OBJAVE": "PUBLICATION CONTROL",
|
||||||
|
"Korpus OSS": "OSS Corpus",
|
||||||
|
"Lastna svetovalnica": "Your own consultancy",
|
||||||
|
"Lastni dokumenti": "Your own documents",
|
||||||
|
"Lastnosti": "Properties",
|
||||||
|
"LETO": "YEAR",
|
||||||
|
"Luščenje": "Extraction",
|
||||||
|
"Luščenje | dodaj opravilo": "Extraction | add a task",
|
||||||
|
"Luščenje iz korpusa besedil OSS": "Extraction from the texts of the OSS corpus",
|
||||||
|
"Luščenje iz lastnih besedil": "Extraction from your own texts",
|
||||||
|
"Luščenje iz lastnih besedil | dodaj opravilo ": "Extraction from your own texts | add a task",
|
||||||
|
"Luščenje je bilo dano v fazo obdelave.": "The extraction was sent to the processing phase.",
|
||||||
|
"Luščenje končano": "Extraction completed",
|
||||||
|
"Luščenje terminoloških kandidatov iz besedil, ki jih ima uporabnik shranjena pri sebi. Za boljšo učinkovitost svetujemo format .txt.": "Extracting term candidates from texts that the user has stored on his own devices. We recommend using .txt file format for better results.",
|
||||||
|
"Luščenje terminoloških kandidatov iz besedil, ki jih uporabnik izbere med vsemi besedili, vključenimi v <a class=\"gray-info\" href=\"https://openscience.si/\" target=\"_blank\">Nacionalni portal odprte znanosti</a>.": "Extracting term candidates from texts selected by the user among all texts included in the <a class=\"gray-info\" href=\"https://openscience.si/\" target=\"_blank\">National Open Science Portal</a>.",
|
||||||
|
"Luščenje terminoloških kandidatov iz besedil, ki jih uporabnik namensko izbere, je najučinkovitejše. Primerno izbrano ime opravila, vam omogoča, da lahko sledite, katera luščenja ste že opravili. Ko vnesete podatke, morate vse spremembe shraniti. Zaradi omejenega prostora za shranjevanje lahko shranite največ pet zadnjih luščenj. ": "Extracting term candidates from texts purposefully selected by the user is more effective. Using a well-chosen name for the task helps you track which extractions have already been performed. After entering the data, make sure to save all changes. Due to limited storage space, only the last five extractions will be saved.",
|
||||||
|
"Luščenje terminoloških kandidatov iz besedil, ki so že predhodno oblikoslovno označena, nudi dobre rezultate, vendar je treba nabor besedil omejiti. Svetujemo vam, da zoožite področje in dodatno omejite izbiro s tipi besedil in časovnim razponom, v katerih so nastala. Ko vnesete podatke, morate vse spremembe shraniti. Po vnosu podatkov, s katerimi boste omejili nabor izbranih besedil, morate pritisniti gumb Najdi. Izbiro boste shranili lahko le v primeru, da ne bo število najdenih dokumentov preveliko.": "Extracting term candidates from texts annotated with linguistic metadate provides good results, but the selection of texts must be limited. We recommend narrowing down the domain and selecting the text types and year of publication to further limit the scope. Make sure to save all changes after entering the data. After filling in the data to limit the text selection, click the Search button. You will only be able to save the selection if the number of found texts does not exceed the limit.",
|
||||||
|
"MAKSIMALNO ŠTEVILO KOPIJ SLOVARSKIH SESTAVKOV": "MAXIMUM NUMBER OF DICTIONARY ENTRY VERSIONS",
|
||||||
|
"Meni": "Menu",
|
||||||
|
"MENI": "MENU",
|
||||||
|
"MINIMALNO ŠTEVILO SLOVARSKIH SESTAVKOV": "MINIMUM NUMBER OF DICTIONARY ENTRIES",
|
||||||
|
"Modul za luščenje terminoloških kandidatov iz besedil.": "Module for extracting term candidates from texts.",
|
||||||
|
"Modul za svetovanje pri terminoloških zagatah.": "Terminology consulting module.",
|
||||||
|
"Modul za urejanje terminoloških slovarjev.": "Module for editing terminology dictionaries.",
|
||||||
|
"MODULI": "MODULES",
|
||||||
|
"Moj Profil": "My Profile",
|
||||||
|
"Moji slovarji": "My dictionaries",
|
||||||
|
"Morebitne že obstoječe poimenovalne rešitve, če obstajajo.": "Any already existing naming solutions.",
|
||||||
|
"Morebitni primeri rabe termina v besedilih ali povezave do njih, če obstajajo.": "Any examples of term use in texts or links to text where the term is used.",
|
||||||
|
"MOŽNOST OBJAVE SLOVARSKEGA SESTAVKA MED UREJANJEM": "OPTION OF PUBLISHING THE DICTIONARY ENTRY WHILE EDITING",
|
||||||
|
"MULTIMEDIJA": "MULTIMEDIA",
|
||||||
|
"Na kratko opišite zasnovo in namen slovarja.": "Briefly describe the composition and purpose of the dictionary.",
|
||||||
|
"Na seznamu uporabnikov lahko določite posameznemu uporabniku dodatne vloge ali urejate njihove podatke.": "You can assign additional roles or edit user data from all users on the list.",
|
||||||
|
"Na tem mestu lahko dodajate svetovalce, ki so registrirani uporabniki terminološkega portala. Če boste pripisali področje, boste lahko svetovalcu dodeljevali samo vprašanja, ki sodijo na izbrano področje, vsem drugim pa vsa.": "Here you can add consultants who are registered users of the terminology portal. If you assign them a domain, you will only be able to assign them questions related to this domain, otherwise you can assign them all questions.",
|
||||||
|
"Na tem mestu lahko določite področne oznake, če želite posamezne termine v svojem terminološkem slovarju razvrstiti še podrobneje.": "Here you can define domain labels, if you want to classify individual terms in your terminology dictionary in more detail.",
|
||||||
|
"Na tem mestu so zbrani vsi komentarji, povezani s terminološkim portalom.": "All comments related to the terminology portal can be found here.",
|
||||||
|
"Na tem mestu so zbrani vsi komentarji, povezani z izbranim terminološkim slovarjem.": "All comments related to the selected terminology dictionary can be found here.",
|
||||||
|
"na vaš elektronski naslov smo vam poslali sporočilo s povezavo, s katero boste potrdili svoj uporabniški račun na Terminološkem portalu. Povezava za potrditev je veljavna ": "we a message with a link to verify your Termonology Portal user account to your e-mail address. The verification link is valid.",
|
||||||
|
"Na vaš elektronski naslov smo vam posredovali povezavo za ponastavitev gesla. Prosimo preverite svoj elektronski predal.": "We sent a password reset link to your e-mail address. Please, check your inbox.",
|
||||||
|
"Nabor vseh luščenj, s katerimi lahko uporabnik iz izbranih besedil pridobi sezname terminoloških kandidatov. Posamezne terminološke kandidate lahko preveri tudi v konkordančniku.": "All extractions the users can use to obtain a list of term candidates from the selected texts. The user can also check individual term candidates using the concordance tool.",
|
||||||
|
"Nabor vseh luščenj, s pomočjo katerih uporabnik iz vhodnih besedil pridobi sezname terminoloških kandidatov in dostop do konkordančnika po teh besedilih.": "All extractions the users can use to obtain a list of term candidates from the selected texts and access to the concordance tools for these texts.",
|
||||||
|
"Nabor vseh slovarjev, ki jih uporabnik lahko ureja kot glavni urednik ali pa ima dodeljeno pravico urejanja, pregledovanja, popravljanja ipd.": "All dictionaries where the user is the main editor or has the editing, reviewing, correcting, etc. rights.",
|
||||||
|
"NAČIN UVOZA": "IMPORT METHOD",
|
||||||
|
"Naglas": "Accent",
|
||||||
|
"Najdi": "Search",
|
||||||
|
"Naložbo sofinancirata Republika Slovenija in Evropska unija iz Evropskega sklada za regionalni razvoj": "The investment is co-financed by the Republic of Slovenia and the EU via the European Regional Development Fund",
|
||||||
|
"Napačno geslo.": "Wrong password.",
|
||||||
|
"Napaka": "Error",
|
||||||
|
"Napaka na strežniku": "Server error",
|
||||||
|
"NAPAKA pri iskanju": "Search ERROR",
|
||||||
|
"Napaka pri nalaganju datoteke": "Error loading file",
|
||||||
|
"NAPAKA V UVOZU": "IMPORT ERROR",
|
||||||
|
"Napredno": "Advanced",
|
||||||
|
"Napredno urejanje omogoča spreminjanje večjega števila podatkov v terminološkem slovarju.": "Advanced editing allows the user to change more data in the terminology dictionary.",
|
||||||
|
"Naprej": "Next",
|
||||||
|
"Naslednje datoteke niso bile naložene zaradi navedenih razlogov:": "The following files were not uploaded due to the following reasons:",
|
||||||
|
"Naslov": "Address",
|
||||||
|
"NASLOV": "ADDRESS",
|
||||||
|
"NASLOV SLOVARJA *": "DICTIONARY TITLE *",
|
||||||
|
"Nastavitve": "Settings",
|
||||||
|
"Nastavitve portala": "Portal settings",
|
||||||
|
"Nastavitve računa": "Account settings",
|
||||||
|
"Nastavitve slovarjev": "Dictionaries settings",
|
||||||
|
"Nastavitve svetovalnice": "Consultancy settings",
|
||||||
|
"Nazaj": "Back",
|
||||||
|
"Nazaj na prijavo": "Back to login",
|
||||||
|
"Nazaj na zadetke": "Back to results",
|
||||||
|
"Ne": "No",
|
||||||
|
"Ne izbriši": "Do not delete",
|
||||||
|
"Ne morete imeti več kot 5 luščenj!": "You cannot have more than 5 extractions!",
|
||||||
|
"Ne shrani": "Do not save",
|
||||||
|
"nedefinirano": "undefined",
|
||||||
|
"Nemško-slovenski slovar tehnike in naravoslovja": "German-Slovenian dictionary of technology and natural sciences",
|
||||||
|
"Neobjavljeni": "Unpublished",
|
||||||
|
"Nepravilna vrednost strani": "Invalid page value",
|
||||||
|
"Nepravilno uporabniško ime, elektronski naslov ali geslo.": "Incorrect username, e-mail address or password.",
|
||||||
|
"Nepregledani": "Not reviewed",
|
||||||
|
"Neveljavna e-pošta": "Invalid e-mail",
|
||||||
|
"Neveljavni": "Invalid",
|
||||||
|
"Neveljavni elektronski naslov.": "Invalid e-mail address",
|
||||||
|
"Ni področja": "No domain",
|
||||||
|
"Ni še dodanih uvozov.": "No imports have been added.",
|
||||||
|
"Ni še povezanih portalov": "No portals have been connected",
|
||||||
|
"Ni slovarskih sestavkov za urejanje.": "There are no dictionary entries to edit.",
|
||||||
|
"Ni vprašanj.": "No questions.",
|
||||||
|
"Ni zadetkov": "No results",
|
||||||
|
"Nimate izbranega področja CERIF.": "No CERIF domain selected.",
|
||||||
|
"Nimate luščenj za urejanje.": "No extractions to edit.",
|
||||||
|
"Nimate povezav za urejanje.": "No linked portals to edit.",
|
||||||
|
"Nimate slovarjev za urejanje.": "No dictionaries to edit.",
|
||||||
|
"Nimate ustreznih pravic": "You do not have the required rights",
|
||||||
|
"Niste izbrali glavnega področja.": "Primary domain missing.",
|
||||||
|
"Niste vnesli področnih oznak.": "Domain labels missing.",
|
||||||
|
"Niste vpisali angleškega naslova slovarja.": "Dictionary title in English missing.",
|
||||||
|
"Niste vpisali imena": "First name missing.",
|
||||||
|
"Niste vpisali imena slovarja.": "Dictionary name missing.",
|
||||||
|
"Niste vpisali naslova slovarja.": "Dictionary title missing.",
|
||||||
|
"Niste vpisali opisa terminološkega problema.": "Terminology problem description missing.",
|
||||||
|
"Niste vpisali priimka.": "Last name missing.",
|
||||||
|
"Niste vpisali tujih jezikov.": "Foreign languages missing.",
|
||||||
|
"Nov": "New",
|
||||||
|
"Nov avtor": "New author",
|
||||||
|
"NOV AVTOR SLOVARJA": "NEW DICTIONARY AUTHOR",
|
||||||
|
"Nov povezan termin.": "New related term.",
|
||||||
|
"Nov slovar": "New dictionary",
|
||||||
|
"NOV UPORABNIK": "NEW USER",
|
||||||
|
"Nov video": "New video",
|
||||||
|
"NOV VIDEO": "NEW VIDEO",
|
||||||
|
"Nov video.": "New video.",
|
||||||
|
"Nov zvok": "New audio file",
|
||||||
|
"NOV ZVOK": "NEW AUDIO FILE",
|
||||||
|
"Nov zvok.": "New audio file.",
|
||||||
|
"Nova povezava": "New link",
|
||||||
|
"Nova slika": "New image",
|
||||||
|
"NOVA SLIKA": "NEW IMAGE",
|
||||||
|
"Nova slika.": "New image.",
|
||||||
|
"Novo": "New",
|
||||||
|
"NOVO GESLO": "NEW PASSWORD",
|
||||||
|
"Novo podpodročje": "New secondary domain",
|
||||||
|
"NOVO PODPODROČJE": "NEW SECONDARY DOMAIN",
|
||||||
|
"NOVO PODPODROČJE (angleško)": "NEW SECONDARY DOMAIN (English)",
|
||||||
|
"Novo podpodročje (angleško).": "New secondary domain (English).",
|
||||||
|
"NOVO PODPODROČJE (slovensko)": "NEW SECONDARY DOMAIN (Slovenian)",
|
||||||
|
"Novo terminološko vprašanje": "New terminology question",
|
||||||
|
"Novo vprašanje": "New question",
|
||||||
|
"Novo vprašanje za Terminološko svetovalnico": "New question for the Terminological Consulting",
|
||||||
|
"O slovarju": "About the dictionary",
|
||||||
|
"O terminu": "About the term",
|
||||||
|
"Objava med urejanjem": "Publish while editing",
|
||||||
|
"Objava terminološkega vprašanja": "Publish a terminology question",
|
||||||
|
"Objava vseh slovarskih sestavkov": "Publish all dictionary entries",
|
||||||
|
"Objavi": "Publish",
|
||||||
|
"OBJAVI": "PUBLISH",
|
||||||
|
"Objavljeni": "Published",
|
||||||
|
"Objavljeno": "Published",
|
||||||
|
"Oblike": "Forms",
|
||||||
|
"Oblike, naglasi, izgovor": "Forms, accents, pronunciation",
|
||||||
|
"Obljavljeno": "Published",
|
||||||
|
"Obnovi": "Restore",
|
||||||
|
"Obstoječe poimenovalne rešitve": "Existing naming solutions",
|
||||||
|
"OBSTOJEČE POIMENOVALNE REŠITVE": "EXISTING NAMING SOLUTIONS",
|
||||||
|
"Obvestilo": "Notification",
|
||||||
|
"Obvestilo o številu gesel": "Notification about the number of entries",
|
||||||
|
"Odgovor": "Answer",
|
||||||
|
"Odgovori": "Answer",
|
||||||
|
"Odgovori na vprašanja:": "Answers to the questions:",
|
||||||
|
"Odjava": "Log out",
|
||||||
|
"odprt": "published",
|
||||||
|
"Odprt": "Published",
|
||||||
|
"Odstrani": "Remove",
|
||||||
|
"Omogočeno": "Enabled",
|
||||||
|
"OPIS PORTALA": "PORTAL DESCRIPTION",
|
||||||
|
"OPIS SLOVARJA": "DICTIONARY DESCRIPTION",
|
||||||
|
"Opis terminološkega problema": "Description of the terminology problem",
|
||||||
|
"OPIS TERMINOLOŠKEGA PROBLEMA": "DESCRIPTION OF THE TERMINOLOGY PROBLEM",
|
||||||
|
"Opis terminološkega problema:": "Description of the terminology problem:",
|
||||||
|
"Opis terminološkega problema: ": "Description of the terminology problem: ",
|
||||||
|
"Osnovne nastavitve": "Basic settings",
|
||||||
|
"Osnovni podatki": "Basic data",
|
||||||
|
"Ožji": "Narrow",
|
||||||
|
"Oznaka portala": "Portal label",
|
||||||
|
"OZNAKA PORTALA *": "PORTAL LABEL *",
|
||||||
|
"Po koncu urejanja vseh slovarskih sestavkov lahko slovar objavite in ga tako prikažete na javnem delu terminološkega portala.": "After all dictionary entires are edited, you can publish the dictionary to make it available on the public part of the terminology portal.",
|
||||||
|
"Počisti filtre": "Remove filters",
|
||||||
|
"POČISTI FILTRE": "REMOVE FILTERS",
|
||||||
|
"Podnaslov": "Subtitle",
|
||||||
|
"Podpodročja": "Secondary domains",
|
||||||
|
"Podpodročja so namenjena podrobnejšemu razvrščanju terminoloških slovarjev. Administrator mora vsa novo predlagana podpodročja potrditi, preden jih lahko na seznamu vidijo tudi drugi uporabniki portala.": "Secondary domains are used for more detailed classification of terminology dictionaries. All new suggested secondary domains must be approved by the Portal Administrator before other portal users can see them on the list.",
|
||||||
|
"Podpodročje": "Secondary domain",
|
||||||
|
"PODPODROČJE": "SECONDARY DOMAIN",
|
||||||
|
"Podpodročje.": "Secondary domain.",
|
||||||
|
"Področja": "Domains",
|
||||||
|
"Področje": "Domain",
|
||||||
|
"PODROČJE": "DOMAIN",
|
||||||
|
"PODROČJE *": "DOMAIN *",
|
||||||
|
"Področje, v katero sodi opisani terminološki problem.": "Domain of the described terminology problem.",
|
||||||
|
"Področje.": "Domain.",
|
||||||
|
"Področna oznaka": "Domain label",
|
||||||
|
"PODROČNA OZNAKA": "DOMAIN LABEL",
|
||||||
|
"PODROČNA OZNAKA:": "DOMAIN LABEL:",
|
||||||
|
"Področne oznake": "Domain labels",
|
||||||
|
"PODROČNE OZNAKE": "DOMAIN LABELS",
|
||||||
|
"Podvoji": "Duplicate",
|
||||||
|
"PODVOJI": "DUPLICATE",
|
||||||
|
"Poiščite naslove terminoloških slovarjev, s katerimi želite dopolniti iskalne zadetke na svojem portalu. Lahko izberete vse slovarje ali le nekatere. Svoje izbire morate shraniti. Vse vaše izbire mora potrditi administrator povezanega portala.": "Find the titles of terminology dictionaries you want to add to the search results on your portal. You can select all the dictionaries or only a few. You have to save your selection. Your selection must be approved by the administrator of the linked portal.",
|
||||||
|
"Pojasnilo": "Qualifier",
|
||||||
|
"POJASNILO": "QUALIFIER",
|
||||||
|
"POJASNILO:": "QUALIFIER:",
|
||||||
|
"POJAVITVE": "APPEARANCES",
|
||||||
|
"Poleg objave slovarskega sestavka v fazi \"Urejeno\" je omogočena tudi objava v fazi \"V urejanju\".": "In addition to the publication of dictionary entries in the \"Edited\" phase, they can also be published while \"In editing\" phase.",
|
||||||
|
"Polja naslov, vprašanje in mnenje so obvezna!": "The Title, Question and Opinion fields are mandatory!",
|
||||||
|
"Polno ime terminološkega portala.": "Full name of the terminology portal.",
|
||||||
|
"Pomoč": "Help",
|
||||||
|
"Ponastavi geslo": "Reset password",
|
||||||
|
"Ponovi Geslo": "Repeat password",
|
||||||
|
"PONOVI GESLO": "REPEAT PASSWORD",
|
||||||
|
"PONOVI NOVO GESLO": "REPEAT NEW PASSWORD",
|
||||||
|
"Portal": "Portal",
|
||||||
|
"Portal lahko povežete s Terminološko svetovalnico ZRC SAZU in tako prikazujete terminološke odgovore na svojem portalu, lahko pa vklopite lastno svetovalnico. V tem primeru morate med registriranimi uporabniki izbrati svetovalce in urednika svetovalnice, ki bodo odgovarjali na terminološka vprašanja uporabnikov.": "You can connect your portal to the ZRC SAZU Terminological counselling and show their terminology answers on your portal, or you can activate your own consultancy service. In the latter case, you have to select the consultants and the consultancy service editor who will answer terminology questions of your portal users. The consultants and consultancy service editor must be registered users.",
|
||||||
|
"Portali": "Portals",
|
||||||
|
"Pošlji": "Send",
|
||||||
|
"Potrdi": "Confirm",
|
||||||
|
"POTRDI": "CONFIRM",
|
||||||
|
"Potrditev objave": "Confirm publication",
|
||||||
|
"Potrjen": "Confirmed",
|
||||||
|
"Povezani slovarji": "Linked dictionaries",
|
||||||
|
"Povezani termin": "Related term",
|
||||||
|
"POVEZANI TERMIN": "RELATED TERM",
|
||||||
|
"POVEZANI TERMIN:": "RELATED TERM:",
|
||||||
|
"POVEZAVA": "LINK",
|
||||||
|
"Povezava ni (več) veljavna. Prosimo, da ponovno zahtevate ponastavitev gesla.": "The link is (no longer) valid. Please, request new password reset link.",
|
||||||
|
"Povezave": "Links",
|
||||||
|
"Povezave s portali": "Links to other portals",
|
||||||
|
"Pozabljeno geslo": "Forgoten password",
|
||||||
|
"Pozdravljeni ": "Hello ",
|
||||||
|
"Pozor": "Attention",
|
||||||
|
"Prazno obvezno polje": "Mandatory field is empty",
|
||||||
|
"Preden naložite besedila, jih shranite v besedilni obliki (končnica .txt).": "Before uploading texts, save them in text format (.txt file).",
|
||||||
|
"Predlog": "Suggestion",
|
||||||
|
"Predogled": "Preview",
|
||||||
|
"Pregledani": "Reviewed",
|
||||||
|
"Prekinjen": "Interrupted",
|
||||||
|
"Prekliči": "Cancel",
|
||||||
|
"Preverite, če ste pravilno vpisali uporabniško ime. Bodite pozorni na velike in male črke.": "Check if you entered the username correctly. Pay attention to capital letters.",
|
||||||
|
"Prevod": "Translation",
|
||||||
|
"Pri brisanju datoteke je prišlo do napake.": "An error occurred during file deletion.",
|
||||||
|
"Priimek": "Last name",
|
||||||
|
"PRIIMEK": "LAST NAME",
|
||||||
|
"Priimek registriranega uporabnika.": "Last name of the registered user.",
|
||||||
|
"Prijava": "Login",
|
||||||
|
"PRIJAVA": "LOGIN",
|
||||||
|
"Prijava uspešna": "Login successful",
|
||||||
|
"Prikaži datume": "Show dates",
|
||||||
|
"Prikaži filtre": "Show filters",
|
||||||
|
"PRIKAŽI VSE": "SHOW ALL",
|
||||||
|
"Primeri rabe": "Examples of use",
|
||||||
|
"PRIMERI RABE V BESEDILIH": "EXAMPLES OF USE IN TEXTS",
|
||||||
|
"Pripravljeno": "Ready",
|
||||||
|
"Prišlo je do napake pri pošiljanju sporočila na vaš elektronski naslov. Poskusite ponovno.": "An error occurred while sending the message to your e-mail address. Please, try again.",
|
||||||
|
"Prišlo je do napake.": "An error has occurred.",
|
||||||
|
"Prišlo je do strežniške napake. Poskusite kasneje.": "A server error has occurred. Please, try again later.",
|
||||||
|
"Prosim, ponovno vnesite geslo": "Please re-enter your password",
|
||||||
|
"Prosim, vnesite geslo": "Please enter your password",
|
||||||
|
"RAČUN": "ACCOUNT",
|
||||||
|
"Razumem": "I understand",
|
||||||
|
"registracija": "registration",
|
||||||
|
"Registracija": "Registration",
|
||||||
|
"Registracija uporabnika je potrjena.": "User registration confirmed.",
|
||||||
|
"Registracija uspešna": "Registration successful",
|
||||||
|
"Registrirani uporabniki lahko dobijo različne vloge na portalu. Kot administrator jim lahko dodelite tudi vlogo skrbnika slovarjev in/ali skrbnika svetovalnice.": "Registered users on the portal can have different roles. As Portal Administrator you can assign them the role of Dictionary Administrator and/or Consultancy Administrator.",
|
||||||
|
"REPUBLIKA SLOVENIJA MINISTRSTVO ZA KULTURO": "REPUBLIC OF SLOVENIA, MINISTRY OF CULTURE",
|
||||||
|
"rezultat": "result",
|
||||||
|
"Rezultat luščenja so terminološki kandidati. Z uvozom rezultatov posameznega luščenja lahko dopolnite geslovnik terminološkega slovarja, ki ga urejate.": "Extraction results in a list of term candidates. You can import the results of individual extractions to add the term candidates to the list of terms in the dictionary you are editing.",
|
||||||
|
"rezultata": "results",
|
||||||
|
"rezultati": "results",
|
||||||
|
"Rezultati: ": "Results: ",
|
||||||
|
"rezultatov": "results",
|
||||||
|
"S ključnimi besedami, ki so vključene v večino znanstvenih in strokovnih, lahko bolj natančno izberete besedila, ki jih želite uporabiti za luščenje terminoloških kandidatov.": "Most of the scientific and specialized texts have keywords you can use for a more precise selection of texts from which to extract term candidates.",
|
||||||
|
"S prijavo pridobite možnost uporabe vseh funkcij terminološkega portala.": "By registering, you get access to all functions of the terminology portal.",
|
||||||
|
"S tem dejanjem boste izbrisali vse slovarske sestavke v slovarju in vse metapodatke. Dejanja ni mogoče razveljaviti.": "This action will delete all dictionary entries in the dictionary and all metadata. The action cannot be undone.",
|
||||||
|
"S tem dejanjem boste izbrisali vse slovarske sestavke v slovarju. Metapodatki bodo ostali. Dejanja ni mogoče razveljaviti.": "This action will delete all dictionary entries in the dictionary. The metadata will not be deleted. The action cannot be undone.",
|
||||||
|
"Še niste registriran uporabnik, registrirajte se.": "You are not a registered user. Please, register.",
|
||||||
|
"Settings": "Settings",
|
||||||
|
"Seznam": "List",
|
||||||
|
"Seznam luščenj": "List of exctractions",
|
||||||
|
"Seznam neželenih terminov": "List of unwanted terms",
|
||||||
|
"Seznam objavljenih vprašanj.": "List of published questions.",
|
||||||
|
"Seznam povezav": "List of linked portals",
|
||||||
|
"Seznam slovarjev": "List of dictionaries",
|
||||||
|
"Seznam slovarjev s povezanega portala.": "List of dictionaries from the linked portal.",
|
||||||
|
"Seznam svetovalcev": "List of consultants",
|
||||||
|
"Seznam terminoloških kandidatov": "List of term candidates",
|
||||||
|
"Seznam terminoloških kandidatov bo natančnejši, če boste dodali tudi seznam neželenih besed, ki naj jih luščilnik izloči iz seznama. Seznam lahko vsebuje splošne termine, npr. tabela, kazalnik, ali pogoste slovnične besede, zlasti veznike, pomožne glagole.": "The list of term candidates will be more accurate if you also add a list of unwanted words that the extractor should exclude from the list of term candidates. This list of unwanted words should include general terms, such as table or indicator, and commonly used grammatical words, such as conjunctions and auxiliary vers.",
|
||||||
|
"Seznam terminoloških kandidatov, ki so rezultat izbranega luščenja.": "List of term candidates resulting from the selected extraction.",
|
||||||
|
"Seznam uporabnikov": "List of users",
|
||||||
|
"Seznam urejenih vprašanj, ki čakajo na potrditev dokončne objave.": "List of edited questions waiting for publication approval.",
|
||||||
|
"Seznam vprašanj, ki so jih moderatorji zavrnili in jih je treba dodeliti nekomu drugemu.": "List of questions rejected by moderators that should be assigned to someone else.",
|
||||||
|
"Seznam vprašanj, ki so jih poslali uporabniki, in še niso bila dodeljena moderatorjem.": "List of questions sent by the users waiting to be assigned to moderators.",
|
||||||
|
"Seznam vprašanj, ki so še v urejanju.": "List of questions that are still being edited.",
|
||||||
|
"Seznam vseh slovarjev, ki jih uredniki, sicer registrirani uporabniki, urejajo na tem portalu.": "List of all dictionaries being edited on this portal by editors, who are also registered users.",
|
||||||
|
"Seznam vseh slovarjev, ki so na tem portalu na voljo uporabnikom.": "List of all dictionaries available to users on this portal.",
|
||||||
|
"Seznam zadetkov": "List of results",
|
||||||
|
"Shemo za format xml si prenesete <a href=\"/slovarji/xml-schema\" target=\"_blank\">tukaj</a>.": "You can download the xml format scheme <a href=\"/slovarji/xml-schema\" target=\"_blank\">here</a>.",
|
||||||
|
"Shrani": "Save",
|
||||||
|
"Shranjeno": "Saved",
|
||||||
|
"Shranjujem ...": "Saving...",
|
||||||
|
"Sinhroniziraj": "Synchronize",
|
||||||
|
"Sinonim": "Synonym",
|
||||||
|
"SINONIM:": "SYNONYM:",
|
||||||
|
"Sinonimi": "Synonyms",
|
||||||
|
"SINONIMI": "SYNONYMS",
|
||||||
|
"Širši": "Broad",
|
||||||
|
"SKRAJŠANI NASLOV SLOVARJA": "SHORT DICTIONARY TITLE",
|
||||||
|
"Skrbnik portala": "Portal administrator",
|
||||||
|
"Skrbnik slovarjev": "Dictionary administrator",
|
||||||
|
"Skrbnik svetovalnice": "Consultancy administrator",
|
||||||
|
"Skrbniki portala": "Portal administrators",
|
||||||
|
"slika": "image",
|
||||||
|
"Slika": "Image",
|
||||||
|
"SLIKA": "IMAGE",
|
||||||
|
"SLIKA:": "IMAGE:",
|
||||||
|
"Slovar": "Dictionary",
|
||||||
|
"Slovar je bil izbrisan.": "The dictionary has been deleted.",
|
||||||
|
"Slovar je odprt.": "The dictionary is published.",
|
||||||
|
"Slovar je pregledal jezikoslovec.": "The dictionary was reviewed by a linguist.",
|
||||||
|
"Slovar je pregledal področni strokovnjak.": "The dictionary has been reviewed by a terminology expert.",
|
||||||
|
"Slovar je v odpiranju - čaka na potrditev skrbnika slovarjev": "The dictionary is being published - waiting for approval from the dictionary administrator",
|
||||||
|
"Slovar odprt": "Dictionary is published",
|
||||||
|
"SLOVAR UVOŽEN": "DICTIONARY IMPORTED",
|
||||||
|
"Slovarji": "Dictionaries",
|
||||||
|
"Slovarji portala": "Dictionaries on the portal",
|
||||||
|
"Slovarski sestavek je neveljaven": "The dictionary entry is invalid",
|
||||||
|
"Slovarski sestavek je pregledan in dokončan.": "The dictionary entry has been reviewed and completed.",
|
||||||
|
"Slovarski sestavek je v fazi, ko se dodajajo in oblikujejo elementi, ki ste jih izbrali v strukturi slovarskega sestavka.": "The dictionary entry is in the editing phase, in which the elements of the dictionary entry structure are being added and edited.",
|
||||||
|
"Slovarski sestavki so bili izbrisani.": "Dictionary entries have been deleted.",
|
||||||
|
"Slovarski sestavki so bili objavljeni.": "Dictionary entries have been published.",
|
||||||
|
"SLOVARSKIH SESTAVKOV": "DICTIONARY ENTRIES",
|
||||||
|
"Slovenščina": "Slovenian",
|
||||||
|
"Soglašam s <a target=\"_blank\" href=\"/politika-zasebnosti\">politiko zasebnosti</a>": "I agree with the <a target=\"_blank\" href=\"/politika-zasebnosti\">Privacy policy</a>",
|
||||||
|
"Sorodni": "Similar",
|
||||||
|
"Sprememba stanja slovarja": "Dictionary status change",
|
||||||
|
"Spremeni geslo": "Change password",
|
||||||
|
"SPREMENJEN": "CHANGED",
|
||||||
|
"ŠT. GESEL": "NO. OF ENTRIES",
|
||||||
|
"STARO GESLO": "OLD PASSWORD",
|
||||||
|
"Statistika": "Statistics",
|
||||||
|
"Status": "Status",
|
||||||
|
"STATUS": "STATUS",
|
||||||
|
"Ste pozabili geslo? Napišite svoje uporabniško ime ali elektronski naslov, ki ste ga uporabili ob registraciji. Na ta naslov vam bomo poslali sporočilo, s pomočjo katerega boste lahko vnesli novo geslo.": "Forgot your password? Enter you username or the e-mail address you used for registration. We send you a message with instructions for password reset to this e-mail address.",
|
||||||
|
"Število dokumentov": "Number of documents",
|
||||||
|
"Število slovarskih sestavkov je manjše od zahtevanega števila - povečajte število slovarskih sestavkov, sicer ga skrbnik slovarjev lahko zapre": "The dictionary has less entries than the required minimum - add more entries to the dictionary, otherwise it may be unpublished by the Dictionary administrator",
|
||||||
|
"Število slovarskih sestavkov, ki jih mora vsebovati slovar, da je omogočena objava slovarja na portalu.": "Minimal number of dictionary entries required for a dictionary to be published on the portal.",
|
||||||
|
"Število terminov": "Number of terms",
|
||||||
|
"Število vprašanj:": "Number of questions:",
|
||||||
|
"ŠTEVILO ZADETKOV NA STRANI": "NUMBER OF RESULTS ON THE PAGE",
|
||||||
|
"Število zadnjih kopij spremenjenih slovarskih sestavkov, ki se hranijo. Če vrednost ni določena, omejitve ni.": "The largest allowed number of saved of recent dictionary entry versions. If no value is entered, there is not limit.",
|
||||||
|
"Stop termini": "Stop terms",
|
||||||
|
"Stran ne obstaja": "Page does not exist",
|
||||||
|
"Strežnik ni dosegljiv. Poskusite kasneje.": "Unable to connect to server. Try again later.",
|
||||||
|
"Strinjam se s <a target=\"_blank\" href=\"/pogoji-uporabe\">pogoji uporabe</a>": "I agree to the <a target=\"_blank\" href=\"/pogoji-uporabe\">Terms of use</a>",
|
||||||
|
"Strokovni pregled": "Terminology review",
|
||||||
|
"Strokovno pregledano": "Terminologically reviewed",
|
||||||
|
"Struktura": "Structure",
|
||||||
|
"STRUKTURA - ELEMENTI SLOVARSKEGA SESTAVKA:": "STRUCTURE - ELEMENTS OF THA DICTIONARY ENTRY:",
|
||||||
|
"Struktura slovarskega sestavka": "The structure of the dictionary entry",
|
||||||
|
"Svetovalci": "Consultants",
|
||||||
|
"Svetovalec": "Consultant",
|
||||||
|
"Svetovalnica": "Consultancy",
|
||||||
|
"Svetovanje": "Consulting",
|
||||||
|
"Svoj terminološki portal lahko povežete še z drugimi terminološkimi portali in med iskalnimi prikazujete tudi njihove zadetke.": "You can link your terminology portal to other terminology portals and show their terminology resources in your results.",
|
||||||
|
"Ta slovar nima opisa": "This dictionary has no description",
|
||||||
|
"Termin": "Term",
|
||||||
|
"TERMIN": "TERM",
|
||||||
|
"TERMIN:": "TERM:",
|
||||||
|
"TERMINI": "TERMS",
|
||||||
|
"Terminološka svetovalnica je namenjena širši strokovni javnosti, ki se sooča s konkretnimi poimenovalnimi problemi, pa naj gre za popolnoma nove pojme, ki jih je v slovenščini šele treba poimenovati, ali že znane pojme, za katere obstaja več poimenovanj.": "The Terminological consulting is aimed at wider expert public when faced with the problem of naming specific concepts, whether they are completely new concept that do not yet have a term, or already known concepts for which several different terms are used.",
|
||||||
|
"Terminološka svetovalnica ZRC SAZU": "ZRC SAZU Terminological counselling",
|
||||||
|
"Terminološke slovarje lahko v celoti ali po izbranih kriterijih izvozite v različnih formatih in shranite na svojem računalniku.": "Terminological dictionaries in their entirety or according to selected criteria can be exported formats in various and saved to your computer.",
|
||||||
|
"Terminološkemu slovarju določite status. Izbirate lahko med zaprt, v urejanju in odprt.": "Set the status of the terminology dictionary. Possible statuses are unpublished, in editing, and published.",
|
||||||
|
"Terminološki kandidati": "Term candidates",
|
||||||
|
"Terminološko svetovanje": "Terminology counseling",
|
||||||
|
"Tip napake 1 (ni slovenskega termina)": "Error type 1 (no Slovenian term)",
|
||||||
|
"Tip napake 2 (ni definicije ali tujejezičnega termina)": "Error type 2 (no definition or term in foreign language)",
|
||||||
|
"Tu so zbrani vsi komentarji, povezani z vašim slovarjem.": "All comments related to the your dictionary can be found here.",
|
||||||
|
"Tuj sinonim": "Synonym in a foreign language",
|
||||||
|
"Tuj termin": "Term in a foreign language",
|
||||||
|
"Tuja definicija": "Definition in a foreign language",
|
||||||
|
"Tuji jezik": "Foreign language",
|
||||||
|
"TUJI JEZIKI": "FOREIGN LANGUAGES",
|
||||||
|
"Tuji slovarji": "Dictionaries in a foreign language",
|
||||||
|
"TUJI TERMIN": "TERMS IN A FOREIGN LANGUAGE",
|
||||||
|
"Tukaj lahko izbrišete svoj račun.": "Here you can delete your account.",
|
||||||
|
"Tukaj lahko ponovno ideksirate slovar.": "Here you can re-index the dictionary.",
|
||||||
|
"Tukaj lahko spremenite geslo za prijavo.": "Here you can change your login password.",
|
||||||
|
"Tukaj lahko spremenite ime, priimek in elektronski naslov uporabnika.": "Here you can change the user's first name, last name and e-mail address.",
|
||||||
|
"Tukaj lahko spremenite nekatere nastavitve, vezane na posameznega uporabnika.": "Here you can change certain settings related to individual users.",
|
||||||
|
"Unikatna oznaka povezanega terminološkega portala za prikaz na vašem portalu, sestavljena iz dveh črk ali črke in številke.": "A unique identifier of the linked terminology portal to be display on your portal, consisting of two letters or a letter and a number.",
|
||||||
|
"Uporabi": "Use",
|
||||||
|
"Uporabnik": "User",
|
||||||
|
"UPORABNIK": "USER",
|
||||||
|
"Uporabnik na tem mestu izdela specializirani korpus iz besedil, ki jih je zbral in shranil sam. Besedila naj bodo izbrana po načelih tvorjenja specializiranih korpusov. Za uspešno luščenje priporočamo najmanj 10 besedil. Vsa besedila naj bodo shranjena v besedilnem formatu (.txt). Luščenje podpira tudi formate .docx in .pdf, vendar so rezultati slabši.": "Here the user can create a specialized corpus from texts selected and saved by the user. The texts should be selected in accordance with the principles for creation of specialized corpora. For successful extraction, at least 10 texts should be used. All texts should be saved in a .txt format. While the extractor also supports the .docx and .pdf formats, the results will not be as good.",
|
||||||
|
"Uporabniki": "Users",
|
||||||
|
"Uporabnikovi dokumenti": "User documents",
|
||||||
|
"Uporabniške pravice/vloge": "User rights/roles",
|
||||||
|
"Uporabniške vloge": "User roles",
|
||||||
|
"Uporabniški korpus": "User's corpus",
|
||||||
|
"Uporabniški račun še ni aktiviran. Kliknite aktivacijsko povezavo, katero smo vam poslali po elektronski pošti.": "The user account has not been activated. Click the link sent to your e-mail to activate your account.",
|
||||||
|
"Uporabniško ime": "Username",
|
||||||
|
"UPORABNIŠKO IME": "USERNAME",
|
||||||
|
"Uporabniško ime ali elektronski naslov": "Username or e-mail address",
|
||||||
|
"Uporabniško ime že obstaja": "Username already exists",
|
||||||
|
"Uredi": "Edit",
|
||||||
|
"UREDI": "EDIT",
|
||||||
|
"Uredi lastnosti": "Edit properties",
|
||||||
|
"Uredi povezavo": "Edit the link",
|
||||||
|
"Uredi vsebino": "Edit content",
|
||||||
|
"Urednik slovarja": "Dictionary editor",
|
||||||
|
"Urejanje": "Edit",
|
||||||
|
"Urejanje terminološkega odgovora": "Edit terminological answer",
|
||||||
|
"Urejeni": "Edited",
|
||||||
|
"Urejeno": "Edited",
|
||||||
|
"URL naslov terminološkega portala, s katerim boste sinhronizirali podatke iz terminoloških virov.": "URL address of the terminology portal hosting terminology resources with data you want to show on your portal.",
|
||||||
|
"URL naslov terminološkega portala, s katerim želimo povezati svoj portal.": "URL address of the terminology portal you want to link to your portal.",
|
||||||
|
"URL naslov terminološkega portala, s katerim želimo povezati svoj portal. (API klic)": "URL address of the terminology portal with which we want to connect our portal. (API call)",
|
||||||
|
"URL naslov terminološkega portala, s katerim želite povezati svoj portal. (API klic)": "URL address of the terminology portal you want to link to your portal. (API call)",
|
||||||
|
"URL povezava do mnenja:": "URL link to opinion:",
|
||||||
|
"URL za sinhronizacijo slovarjev *": "URL for synchronization of dictionaries *",
|
||||||
|
"URL za sinhronizacijo slovarskih sestavkov *": "URL for synchronization of dictionary entries *",
|
||||||
|
"Uspešno ste ponastavili svoje geslo.": "You have successfully reset your password.",
|
||||||
|
"Ustvari": "Create",
|
||||||
|
"USTVARJEN": "CREATED",
|
||||||
|
"Ustvarjeno novo vprašanje v svetovalnici": "A new question has been created in the Consultancy service",
|
||||||
|
"UTEŽ": "WEIGHT",
|
||||||
|
"Uvoz": "Import",
|
||||||
|
"Uvoz iz datoteke": "Import from file",
|
||||||
|
"Uvoz iz luščilnika": "Import from the extractor",
|
||||||
|
"UVOZI": "IMPORTS",
|
||||||
|
"Uvozi termine od številke": "Import appointments between the numbers",
|
||||||
|
"V delu": "In progress",
|
||||||
|
"V DRUGI VSEBINI": "IN OTHER PARTS OF THE ENTRY",
|
||||||
|
"V IZTOČNICAH": "IN THE TERMS",
|
||||||
|
"V obdelavi": "In progress",
|
||||||
|
"V odpiranju": "In the process of publication",
|
||||||
|
"v predogledu": "in preview",
|
||||||
|
"V slovarju so samo terminološki kandidati. Slovarja ne morete objaviti.": "The dictionary contains only term candidates. The dictionary cannot be published.",
|
||||||
|
"V tem razdelku lahko določite elemente slovarskega sestavka. Z izbiro elementov se vam prikazuje podoba slovarskega sestavka. Izbiro lahko tudi med urejanjem vsebine kadarkoli spremenite.": "In this section, you can select the elements of the dictionary entry. When you select the elements, you can see what the entry will look like. You can change your selection any time during the editing process.",
|
||||||
|
"V tem razdelku lahko določite glavne administratorske pravice na terminološkem portalu.": "In this section, you can assign the main administrative rights on the terminology portal.",
|
||||||
|
"V tem razdelku lahko določite uporabniške vloge posameznega uporabnika in urejate njegove podatke.": "In this section, you can define the user roles of individual users and edit their data.",
|
||||||
|
"V TUJEJEZIČNIH USTREZNIKIH": "IN FOREIGN LANGUAGE EQUIVALENTS",
|
||||||
|
"V urejanju": "In editing",
|
||||||
|
"Vaš komentar je brez vsebine.": "Your comment is empty.",
|
||||||
|
"Vaše iskanje ni bilo uspešno. Vpišite novo iskalno poizvedbo in poizkusite znova.": "Your search was not successful. Enter a new search string and try again.",
|
||||||
|
"Več ...": "More...",
|
||||||
|
"Več …": "More...",
|
||||||
|
"Več...": "More...",
|
||||||
|
"VELIKOST": "SIZE",
|
||||||
|
"Veljavni": "Valid",
|
||||||
|
"Veljavni elektronski naslov uporabnika, na katerega uporabnik prejema sporočila, povezana s portalom.": "A valid e-mail address of the user to which the user receives messages related to the portal.",
|
||||||
|
"Veljavno": "Valid",
|
||||||
|
"Veljavnost": "Validity",
|
||||||
|
"Verzija": "Version",
|
||||||
|
"Verzija 1": "Version 1",
|
||||||
|
"Verzija:": "Version:",
|
||||||
|
"video": "video",
|
||||||
|
"Video": "Video",
|
||||||
|
"VIDEO": "VIDEO",
|
||||||
|
"VIDEO:": "VIDEO:",
|
||||||
|
"Vidno": "Visible",
|
||||||
|
"Vir": "Source",
|
||||||
|
"Viri": "Sources",
|
||||||
|
"Vnesite e-naslov, ki ga za obveščanje o novih terminoloških vprašanjih uporabljajo terminološki svetovalci.": "Enter the email address used to notify Terminology Consultants about new terminology questions.",
|
||||||
|
"Vnesite naslov spletnega mesta, kjer so zbrani vsi odgovori terminološke svetovalnice.": "Enter the URL of the website where all the answers of your consultancy service are collected.",
|
||||||
|
"Vnesite novo geslo. Ko ga potrdite, se boste v vaš uporabniški račun lahko spet prijavili z novim geslom.": "Enter a new password. After confirming it, you will be able to log into your account with the new password.",
|
||||||
|
"Vnesite podatke terminološkega portala, s katerim želite povezati svoj portal.": "Enter the information of the terminology portal with which you want to link your portal.",
|
||||||
|
"Vnesite podatke, ki niso sistemsko vključeni v druga polja, npr. vir, zgled rabe.": "Enter data that is not systemically included in other fields, e.g. examples of use.",
|
||||||
|
"Vnesite polno ime terminološkega portala, s katerim se povezujete.": "Enter the full name of the terminology portal you are linking with.",
|
||||||
|
"Vnesite termin.": "Enter the term.",
|
||||||
|
"Vnesti morate termin": "You must enter the term",
|
||||||
|
"Vpišite avtorja slovarja, če ste glavni avtor, na prvo mesto zapišite svoje ime.": "Enter the author of the dictionary. If you are the main author, write your name first.",
|
||||||
|
"Vpišite definicijo v tujem jeziku.": "Enter the definition in a foreign language.",
|
||||||
|
"Vpišite iskalni niz": "Enter a search string",
|
||||||
|
"Vpišite novo podpodročje. Na seznamu podpodročij bo vidno takoj po potrditvi administratorja portala.": "Enter a new secondary domain. It will be visible on the list of secondary domain immediately after Portal Administrator's approval.",
|
||||||
|
"Vpišite podpodročje glavnega področja, ki ste ga izbrali. Če podpodročja ni med naborom, izberite polje Novo podpodročje.": "Enter the secondary domain of the domain you selected. If you cannot find the secondary domain on the list, select New secondary domain.",
|
||||||
|
"Vprašanje poslano:": "Question sent:",
|
||||||
|
"VRSTA": "TYPE",
|
||||||
|
"VRSTA DOKUMENTA": "TYPE OF DOCUMENT",
|
||||||
|
"Vsebina": "Content",
|
||||||
|
"Vsebina slovarja": "Contents of the dictionary",
|
||||||
|
"Vsebina slovarskega sestavka je bila duplicirana.": "The content of the dictionary entry has been duplicated.",
|
||||||
|
"Vsi povezani slovarji": "All linked dictionaries",
|
||||||
|
"Vsi slovarji": "All dictionaries",
|
||||||
|
"Vsi slovarski sestavki": "All dictionary entries",
|
||||||
|
"Vstavite definicijo pojma.": "Enter the definition of the term.",
|
||||||
|
"Vstavite termine, ki se za definirani pojem tudi uporabljajo.": "Enter terms which are also used for the defined concept.",
|
||||||
|
"Vstavite ustrezno področno oznako, ki jo želite določiti za posamezni termin.": "Enter the domain label that you want to assign to the individual term.",
|
||||||
|
"Z izbiro te možnosti boste pobrisali samo slovarske sestavke, ki so v določeni fazi urejanja.": "By selecting this option, you will only delete dictionary entries that are in a certain editing phase.",
|
||||||
|
"Z registracijo pridobite možnost uporabe vseh funkcij terminološkega portala.": "By registering, you get access to all functions of the terminology portal.",
|
||||||
|
"Za luščenje terminoloških kandidatov morate biti prijavljeni.": "You must be logged in to extract term candidates.",
|
||||||
|
"Za prvo objavo slovarja je potrebno dovoljenje skrbnika slovarjev.": "Dictionary administrator's approval is required before the first publication of the dictionary.",
|
||||||
|
"Za to besedo še ni podatkov.": "There is no data for this word.",
|
||||||
|
"Za urejanje slovarjev morate biti prijavljeni.": "You must be logged in to edit dictionaries.",
|
||||||
|
"Za zastavljanje terminoloških vprašanj morate biti prijavljeni.": "You must be logged in to ask terminology questions.",
|
||||||
|
"Začetek": "Start",
|
||||||
|
"Začni": "Start",
|
||||||
|
"ZAČNI": "START",
|
||||||
|
"Zadnja sprememba": "Last change",
|
||||||
|
"Zadnji izvozi": "Recent exports",
|
||||||
|
"Zadnji objavljeni slovarji": "Recent published dictionaries",
|
||||||
|
"Zadnji odgovori na vprašanja": "Recent answers to questions",
|
||||||
|
"Zadnji uvozi": "Recent imports",
|
||||||
|
"Zapomni si prijavo": "Remember my login",
|
||||||
|
"Zaporedje:": "Sequence:",
|
||||||
|
"Zapri": "Close",
|
||||||
|
"ZAPRI": "CLOSE",
|
||||||
|
"zaprt": "unpublished",
|
||||||
|
"Zaprt": "Unpublished",
|
||||||
|
"Zaradi organizacije podatkov na portalu za skrajšani naslov slovarja predlagamo poenobesede\tnje, ki se bo izpisovalo ob slovarju, npr. Davčni terminološki slovar → Davki.": "Due to the organization of the data on the portal, we suggest using only one word for the short dictionary title, which will show next to the dictionary, e.g. Tax Terminology Dictionary→ Taxes",
|
||||||
|
"Zaradi organizacije podatkov na portalu za skrajšani naslov slovarja predlagamo poenobesedenje, ki se bo izpisovalo ob slovarju, npr. Davčni terminološki slovar → Davki": "Due to the organization of the data on the portal, we suggest using only one word for the short dictionary title, which will show next to the dictionary, e.g. Tax Terminology Dictionary→ Taxes",
|
||||||
|
"Zastavi novo vprašanje": "Ask a new question",
|
||||||
|
"Zastavi terminološko vprašanje": "Ask a terminology question",
|
||||||
|
"ZAVRJEN": "REJECTED",
|
||||||
|
"ZAVRNI": "REJECT",
|
||||||
|
"Zavrnjeno": "Rejected",
|
||||||
|
"Zunanji": "External",
|
||||||
|
"zvok": "audio",
|
||||||
|
"Zvok": "Audio",
|
||||||
|
"ZVOK": "AUDIO",
|
||||||
|
"ZVOK:": "AUDIO:",
|
||||||
|
"titleTermsOfUse": "Terms of Use",
|
||||||
|
"titlePrivacyPolicy": "Privacy Policy"
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"helpPageTitleHelp": "Help",
|
||||||
|
"helpPageTitleAbout": "General information about the Portal",
|
||||||
|
"helpPageTitleRegistration": "Registration",
|
||||||
|
"helpPageTitleSearchIndex": "Search",
|
||||||
|
"helpPageTitleSearchBasic": "Basic search",
|
||||||
|
"helpPageTitleSearchAdvanced": "Advanced search",
|
||||||
|
"helpPageTitleExtractionIndex": "Extraction",
|
||||||
|
"helpPageTitleExtractionSpecializedCorpora": "Specialized corpora",
|
||||||
|
"helpPageTitleExtractionPersonalCorpus": "User’s personal corpus",
|
||||||
|
"helpPageTitleExtractionOssCorpus": "OSS Corpus",
|
||||||
|
"helpPageTitleExtractionStopTerms": "Lists of unwanted words or \"Stop terms\"",
|
||||||
|
"helpPageTitleExtractionDomains": "Domains",
|
||||||
|
"helpPageTitleExtractionTermCandidates": "Term candidates",
|
||||||
|
"helpPageTitleEditingIndex": "Editing",
|
||||||
|
"helpPageTitleEditingNewDictionary": "New dictionary",
|
||||||
|
"helpPageTitleEditingDictionaryProperties": "Editing dictionary properties",
|
||||||
|
"helpPageTitleEditingUsers": "Users",
|
||||||
|
"helpPageTitleEditingStructure": "Structure of dictionary entries",
|
||||||
|
"helpPageTitleEditingComments": "Comments",
|
||||||
|
"helpPageTitleEditingDictionaryContent": "The content of the dictionary",
|
||||||
|
"helpPageTitleEditingImportingData": "Importing data",
|
||||||
|
"helpPageTitleEditingEntriesIndex": "Adding new dictionary entries",
|
||||||
|
"helpPageTitleEditingEntriesDomainLabels": "Domain labels",
|
||||||
|
"helpPageTitleEditingEntriesLabel": "Qualifiers",
|
||||||
|
"helpPageTitleEditingEntriesDefinition": "Definition",
|
||||||
|
"helpPageTitleEditingEntriesSynonyms": "Synonyms",
|
||||||
|
"helpPageTitleEditingEntriesRelatedTerms": "Related terms",
|
||||||
|
"helpPageTitleEditingEntriesOtherLanguages": "Other languages",
|
||||||
|
"helpPageTitleEditingEntriesOther": "Other",
|
||||||
|
"helpPageTitleEditingEntriesEditingHistory": "Editing history",
|
||||||
|
"helpPageTitleEditingEntriesComments": "Comments",
|
||||||
|
"helpPageTitleEditingEntriesDataExport": "Data export",
|
||||||
|
"helpPageTitleConsultancyIndex": "Consultancy",
|
||||||
|
"helpPageTitleConsultancyQuestions": "How to ask a question",
|
||||||
|
"helpPageTitleConsultancyTerminologyPrinciples": "Terminology principles",
|
||||||
|
"helpPageTitleConsultancyLink": "Link",
|
||||||
|
"helpPageTitleConsultancyAnswers": "Published answers",
|
||||||
|
"helpPageTitleAdministrationIndex": "Administration",
|
||||||
|
"helpPageTitleAdministrationBasicSettingsIndex": "Basic settings",
|
||||||
|
"helpPageTitleAdministrationBasicSettingsPortal": "Portal",
|
||||||
|
"helpPageTitleAdministrationBasicSettingsDictionaries": "Dictionaries",
|
||||||
|
"helpPageTitleAdministrationBasicSettingsConsultancy": "Consultancy",
|
||||||
|
"helpPageTitleAdministrationLinksToOtherPortalsIndex": "Links to other portals",
|
||||||
|
"helpPageTitleAdministrationLinksToOtherPortalsList": "List",
|
||||||
|
"helpPageTitleAdministrationLinksToOtherPortalsDictionaries": "Dictionaries",
|
||||||
|
"helpPageTitleAdministrationDictionaries": "Dictionaries",
|
||||||
|
"helpPageTitleAdministrationUsers": "Users",
|
||||||
|
"helpPageTitleAdministrationSecondaryDomains": "Secondary domains"
|
||||||
|
}
|
||||||
@@ -0,0 +1,710 @@
|
|||||||
|
{
|
||||||
|
" Terminološke odgovore pripravljajo sodelavci Terminološke sekcije Inštituta za slovenski jezik Frana Ramovša ZRC SAZU (in so objavljeni tudi na spletišču <a href='https://isjfr.zrc-sazu.si/sl/terminologisce/svetovanje'>Terminologišče</a>), ki pri delu upoštevajo osnovna terminološka načela.": " Terminološke odgovore pripravljajo sodelavci Terminološke sekcije Inštituta za slovenski jezik Frana Ramovša ZRC SAZU (in so objavljeni tudi na spletišču <a href='https://isjfr.zrc-sazu.si/sl/terminologisce/svetovanje'>Terminologišče</a>), ki pri delu upoštevajo osnovna terminološka načela.",
|
||||||
|
"(STOP) Termini": "(STOP) Termini",
|
||||||
|
"[ni termina]": "[ni termina]",
|
||||||
|
"#": "#",
|
||||||
|
"<b>Odgovor:</b>": "<b>Odgovor:</b>",
|
||||||
|
"Administracija": "Administracija",
|
||||||
|
"Administrator": "Administrator",
|
||||||
|
"Administratorska konzola": "Administratorska konzola",
|
||||||
|
"Administratorski del portala je namenjen skupini oseb, ki vsebinsko in tehnično ureja portal, odpira slovarske vire, daje pooblastila posameznim uporabnikom in skrbi za vsebinsko in tehnično urejenost portala. Izberite module, ki jih boste ponudili na terminološkem portalu in na kratko opišite, kaj ponuja vaš portal.": "Administratorski del portala je namenjen skupini oseb, ki vsebinsko in tehnično ureja portal, odpira slovarske vire, daje pooblastila posameznim uporabnikom in skrbi za vsebinsko in tehnično urejenost portala. Izberite module, ki jih boste ponudili na terminološkem portalu in na kratko opišite, kaj ponuja vaš portal.",
|
||||||
|
"Aktivacija računa": "Aktivacija računa",
|
||||||
|
"Ali res želite izbrisati vnos? Dejanja ni mogoče razveljaviti.": "Ali res želite izbrisati vnos? Dejanja ni mogoče razveljaviti.",
|
||||||
|
"Ali želite indeksirati?": "Ali želite indeksirati?",
|
||||||
|
"Ali želite izbrisati slovarski sestavek?": "Ali želite izbrisati slovarski sestavek?",
|
||||||
|
"Ali želite objaviti vsa gesla?": "Ali želite objaviti vsa gesla?",
|
||||||
|
"Ali želite zbrisati ta profil. S tem bodo izbrisani vsi podatki, ki ste jih ustvarili": "Ali želite zbrisati ta profil. S tem bodo izbrisani vsi podatki, ki ste jih ustvarili",
|
||||||
|
"Ali želite zbrisati ta vnos?": "Ali želite zbrisati ta vnos?",
|
||||||
|
"angleščina": "angleščina",
|
||||||
|
"ANGLEŠKI NASLOV SLOVARJA *": "ANGLEŠKI NASLOV SLOVARJA *",
|
||||||
|
"ANGLEŠKI OPIS PORTALA": "ANGLEŠKI OPIS PORTALA",
|
||||||
|
"ANGLEŠKO IME PORTALA": "ANGLEŠKO IME PORTALA",
|
||||||
|
"Angleško ime terminološkega portala.": "Angleško ime terminološkega portala.",
|
||||||
|
"Avtor": "Avtor",
|
||||||
|
"AVTOR": "AVTOR",
|
||||||
|
"AVTOR SLOVARJA": "AVTOR SLOVARJA",
|
||||||
|
"Avtor:": "Avtor:",
|
||||||
|
"Avtorja": "Avtorja",
|
||||||
|
"Avtorji": "Avtorji",
|
||||||
|
"Avtorji mnenja:": "Avtorji mnenja:",
|
||||||
|
"Basic": "Basic",
|
||||||
|
"Besedila": "Besedila",
|
||||||
|
"Besedilo gre tukaj": "Besedilo gre tukaj",
|
||||||
|
"Brisanje je onemogočeno, doker se nalagajo nove datoteke.": "Brisanje je onemogočeno, doker se nalagajo nove datoteke.",
|
||||||
|
"Brisanje je onemogočeno, saj je eno še v procesu.": "Brisanje je onemogočeno, saj je eno še v procesu.",
|
||||||
|
"Brisanje slovarja": "Brisanje slovarja",
|
||||||
|
"Brisanje slovarskih sestavkov": "Brisanje slovarskih sestavkov",
|
||||||
|
"Briši": "Briši",
|
||||||
|
"BRIŠI": "BRIŠI",
|
||||||
|
"Briši uporabnika": "Briši uporabnika",
|
||||||
|
"Če imate pripravljen slovar v enem od formatov, ki so navedeni spodaj, lahko svoje podatke uvozite.": "Če imate pripravljen slovar v enem od formatov, ki so navedeni spodaj, lahko svoje podatke uvozite.",
|
||||||
|
"Če izberete to možnost, se bodo vsi doslejšnji slovarski sestavki ob uvozu nove datoteke izbrisali.": "Če izberete to možnost, se bodo vsi doslejšnji slovarski sestavki ob uvozu nove datoteke izbrisali.",
|
||||||
|
"Če želite dodati novo luščenje, morate najprej pobrisati vsaj eno od obstoječih, saj je na posameznega uporabnika dovoljenih največ 5 luščenj.": "Če želite dodati novo luščenje, morate najprej pobrisati vsaj eno od obstoječih, saj je na posameznega uporabnika dovoljenih največ 5 luščenj.",
|
||||||
|
"Celotni naslov slovarja v angleščini.": "Celotni naslov slovarja v angleščini.",
|
||||||
|
"Celotni naslov slovarja, ki bo zabeležen v bibliografskih podatkih.": "Celotni naslov slovarja, ki bo zabeležen v bibliografskih podatkih.",
|
||||||
|
"Ciljni jeziki": "Ciljni jeziki",
|
||||||
|
"Čim bolj natančno opišite terminološki problem, zlasti opišite vsebino pojma.": "Čim bolj natančno opišite terminološki problem, zlasti opišite vsebino pojma.",
|
||||||
|
"DATOTEKA": "DATOTEKA",
|
||||||
|
"Datoteka uspešno naložena": "Datoteka uspešno naložena",
|
||||||
|
"DATUM": "DATUM",
|
||||||
|
"Datum objave/spremembe": "Datum objave/spremembe",
|
||||||
|
"Datum vprašanja:": "Datum vprašanja:",
|
||||||
|
"Definicija": "Definicija",
|
||||||
|
"DEFINICIJA": "DEFINICIJA",
|
||||||
|
"DEFINICIJA:": "DEFINICIJA:",
|
||||||
|
"Delete profile": "Delete profile",
|
||||||
|
"Deli": "Deli",
|
||||||
|
"DELI": "DELI",
|
||||||
|
"dni.": "dni.",
|
||||||
|
"do številke": "do številke",
|
||||||
|
"Dodaj": "Dodaj",
|
||||||
|
"DODAJ": "DODAJ",
|
||||||
|
"Dodaj besedilo za luščenje terminoloških kandidatov iz lastnega specializiranega korpusa. Ko boste dodali vsa besedila, ki ste jih izbrali, morate izbiro shraniti.": "Dodaj besedilo za luščenje terminoloških kandidatov iz lastnega specializiranega korpusa. Ko boste dodali vsa besedila, ki ste jih izbrali, morate izbiro shraniti.",
|
||||||
|
"Dodaj datoteko": "Dodaj datoteko",
|
||||||
|
"Dodaj komentar...": "Dodaj komentar...",
|
||||||
|
"Dodaj odgovor...": "Dodaj odgovor...",
|
||||||
|
"Dodaj opravilo": "Dodaj opravilo",
|
||||||
|
"Dodaj povezani termin.": "Dodaj povezani termin.",
|
||||||
|
"Dodaj povezavo": "Dodaj povezavo",
|
||||||
|
"Dodaj slovar": "Dodaj slovar",
|
||||||
|
"Dodajte ime in priimek naslednjega avtorja slovarja.": "Dodajte ime in priimek naslednjega avtorja slovarja.",
|
||||||
|
"Dodajte ime in priimek naslednjega avtorja slovarja..": "Dodajte ime in priimek naslednjega avtorja slovarja..",
|
||||||
|
"Dodajte leto izida besedil, ki jih želite luščiti. Lahko dodate več posameznih let. Če boste polje pustili prazno, bodo vključena vsa leta. Če bo besedil preveč, boste morali omejiti izbiro.": "Dodajte leto izida besedil, ki jih želite luščiti. Lahko dodate več posameznih let. Če boste polje pustili prazno, bodo vključena vsa leta. Če bo besedil preveč, boste morali omejiti izbiro.",
|
||||||
|
"Dodajte podatek o zunajjezikovnih okoliščinah, ki niso povezane s pojmom, npr. letnico.": "Dodajte podatek o zunajjezikovnih okoliščinah, ki niso povezane s pojmom, npr. letnico.",
|
||||||
|
"Dodajte povezavo do slike.": "Dodajte povezavo do slike.",
|
||||||
|
"Dodajte povezavo do videa.": "Dodajte povezavo do videa.",
|
||||||
|
"Dodajte povezavo do zvočnega posnetka.": "Dodajte povezavo do zvočnega posnetka.",
|
||||||
|
"Dodajte termin, ki je sicer definiran v samostojnem slovarskem sestavku, vendar je povezan s terminom, ki ga opisujete v tem slovarskem sestavku.": "Dodajte termin, ki je sicer definiran v samostojnem slovarskem sestavku, vendar je povezan s terminom, ki ga opisujete v tem slovarskem sestavku.",
|
||||||
|
"Dodajte terminološki odgovor in ga utemeljite.": "Dodajte terminološki odgovor in ga utemeljite.",
|
||||||
|
"Dodajte tujejezične ustreznike, ki se za opisani pojem tudi uporabljajo v tujem jeziku.": "Dodajte tujejezične ustreznike, ki se za opisani pojem tudi uporabljajo v tujem jeziku.",
|
||||||
|
"Dodajte tujejezični ustreznik.": "Dodajte tujejezični ustreznik.",
|
||||||
|
"Dodate lahko seznam besed, ki jih v seznam terminoloških kandidatov ne želite vključiti. Seznam naj bo shranjen v formatu .txt. <a class=\"smaller-gray-info\" href=\"/pomoc#help-stop-lists\">Več...</a>": "Dodate lahko seznam besed, ki jih v seznam terminoloških kandidatov ne želite vključiti. Seznam naj bo shranjen v formatu .txt. <a class=\"smaller-gray-info\" href=\"/pomoc#help-stop-lists\">Več...</a>",
|
||||||
|
"Dodate lahko seznam besed, ki jih v seznam terminoloških kandidatov ne želite vključiti. Seznam naj bo shranjen v formatu .txt. Na koncu morate spremembe shraniti. <a class=\"smaller-gray-info\" href=\"/pomoc#help-stop-lists\" target=\"_blank\">Več ...</a>": "Dodate lahko seznam besed, ki jih v seznam terminoloških kandidatov ne želite vključiti. Seznam naj bo shranjen v formatu .txt. Na koncu morate spremembe shraniti. <a class=\"smaller-gray-info\" href=\"/pomoc#help-stop-lists\" target=\"_blank\">Več ...</a>",
|
||||||
|
"Dodate lahko seznam besed, ki jih v seznam terminoloških kandidatov ne želite vključiti. Seznam naj bo shranjen v formatu .txt. Vse spremembe morate shraniti. <a class=\"smaller-gray-info\" href=\"/pomoc#help-stop-lists\">Več...</a>": "Dodate lahko seznam besed, ki jih v seznam terminoloških kandidatov ne želite vključiti. Seznam naj bo shranjen v formatu .txt. Vse spremembe morate shraniti. <a class=\"smaller-gray-info\" href=\"/pomoc#help-stop-lists\">Več...</a>",
|
||||||
|
"Dodaten avtor.": "Dodaten avtor.",
|
||||||
|
"DODATNI POVEZANI TERMIN": "DODATNI POVEZANI TERMIN",
|
||||||
|
"Dodeli": "Dodeli",
|
||||||
|
"DODELI": "DODELI",
|
||||||
|
"Določite lastnosti terminoloških virov na portalu, zlasti minimalno število sestavkov, ki so pogoj za objavo, možnosti potrjevanja objave novih slovarjev, število različic slovarja, ki jih lahko hrani posamezni uporabnik. Te nastavitve veljajo za vse terminološke vire na portalu.": "Določite lastnosti terminoloških virov na portalu, zlasti minimalno število sestavkov, ki so pogoj za objavo, možnosti potrjevanja objave novih slovarjev, število različic slovarja, ki jih lahko hrani posamezni uporabnik. Te nastavitve veljajo za vse terminološke vire na portalu.",
|
||||||
|
"Domači slovarji": "Domači slovarji",
|
||||||
|
"Drugo": "Drugo",
|
||||||
|
"DRUGO": "DRUGO",
|
||||||
|
"DRUGO:": "DRUGO:",
|
||||||
|
"Dvočrkovna oznaka terminološkega portala.": "Dvočrkovna oznaka terminološkega portala.",
|
||||||
|
"E - naslov": "E - naslov",
|
||||||
|
"E-naslov": "E-naslov",
|
||||||
|
"E-NASLOV": "E-NASLOV",
|
||||||
|
"E-pošta": "E-pošta",
|
||||||
|
"ELEKTRONSKI NASLOV": "ELEKTRONSKI NASLOV",
|
||||||
|
"Elektronski naslov trenutno prijavljenega uporabnika.": "Elektronski naslov trenutno prijavljenega uporabnika.",
|
||||||
|
"Elektronski naslov že obstaja": "Elektronski naslov že obstaja",
|
||||||
|
"EVROPSKI SKLAD ZA REGIONALNI RAZVOJ": "EVROPSKI SKLAD ZA REGIONALNI RAZVOJ",
|
||||||
|
"FAZA UREJANJA": "FAZA UREJANJA",
|
||||||
|
"Faze urejanja": "Faze urejanja",
|
||||||
|
"Filtri": "Filtri",
|
||||||
|
"Filtriranje": "Filtriranje",
|
||||||
|
"FORMAT ZAPISA": "FORMAT ZAPISA",
|
||||||
|
"Gesli se ne ujemata": "Gesli se ne ujemata",
|
||||||
|
"Gesli se ujemata!": "Gesli se ujemata!",
|
||||||
|
"Geslo": "Geslo",
|
||||||
|
"GESLO": "GESLO",
|
||||||
|
"Geslo je prekratko": "Geslo je prekratko",
|
||||||
|
"Geslo je prekratko.": "Geslo je prekratko.",
|
||||||
|
"Geslo se ne ujema": "Geslo se ne ujema",
|
||||||
|
"Geslo se ne ujema.": "Geslo se ne ujema.",
|
||||||
|
"Hrvaščina": "Hrvaščina",
|
||||||
|
"Hvala za poslano vprašanje, ki je bilo posredovano svetovalcem terminološkega portala. Odgovor boste prejeli na e-naslov, ki ste ga navedli ob registraciji.": "Hvala za poslano vprašanje, ki je bilo posredovano svetovalcem terminološkega portala. Odgovor boste prejeli na e-naslov, ki ste ga navedli ob registraciji.",
|
||||||
|
"Hvala za poslano vprašanje, ki je bilo posredovano v Terminološko svetovalnico ZRC SAZU. Odgovor boste prejeli na e-naslov, ki ste ga navedli ob registraciji.": "Hvala za poslano vprašanje, ki je bilo posredovano v Terminološko svetovalnico ZRC SAZU. Odgovor boste prejeli na e-naslov, ki ste ga navedli ob registraciji.",
|
||||||
|
"ID Slovarja": "ID Slovarja",
|
||||||
|
"ID: Ni idja": "ID: Ni idja",
|
||||||
|
"IDEKSIRAJ": "IDEKSIRAJ",
|
||||||
|
"Imate neshranjene spremebe. Ali jih želite shraniti?": "Imate neshranjene spremebe. Ali jih želite shraniti?",
|
||||||
|
"Imate neshranjene spremembe. Ali jih želite shraniti?": "Imate neshranjene spremembe. Ali jih želite shraniti?",
|
||||||
|
"Ime": "Ime",
|
||||||
|
"IME": "IME",
|
||||||
|
"IME DATOTEKE": "IME DATOTEKE",
|
||||||
|
"Ime in opis": "Ime in opis",
|
||||||
|
"IME IN PRIIMEK": "IME IN PRIIMEK",
|
||||||
|
"Ime in priimek trenutno prijavljenega uporabnika.": "Ime in priimek trenutno prijavljenega uporabnika.",
|
||||||
|
"IME LUŠČENJA": "IME LUŠČENJA",
|
||||||
|
"IME PORTALA": "IME PORTALA",
|
||||||
|
"IME PORTALA *": "IME PORTALA *",
|
||||||
|
"Ime Priimek": "Ime Priimek",
|
||||||
|
"Ime registriranega uporabnika.": "Ime registriranega uporabnika.",
|
||||||
|
"Ime slovarja": "Ime slovarja",
|
||||||
|
"Ime, s katerim se uporabnik predstavlja na terminološkem portalu.": "Ime, s katerim se uporabnik predstavlja na terminološkem portalu.",
|
||||||
|
"Indeksiranje slovarja": "Indeksiranje slovarja",
|
||||||
|
"Institucija": "Institucija",
|
||||||
|
"INSTITUCIJA": "INSTITUCIJA",
|
||||||
|
"Institucija, kjer trenutno prijavljeni uporabnik deluje.": "Institucija, kjer trenutno prijavljeni uporabnik deluje.",
|
||||||
|
"Interni": "Interni",
|
||||||
|
"Išči": "Išči",
|
||||||
|
"Išči po slovarju": "Išči po slovarju",
|
||||||
|
"ISKANI NIZ JE BIL NAJDEN": "ISKANI NIZ JE BIL NAJDEN",
|
||||||
|
"Iskanje": "Iskanje",
|
||||||
|
"Iskanje po slovarjih": "Iskanje po slovarjih",
|
||||||
|
"Iskanje po vseh": "Iskanje po vseh",
|
||||||
|
"ISSN": "ISSN",
|
||||||
|
"ISSN OZNAKA": "ISSN OZNAKA",
|
||||||
|
"ISSN oznaka.": "ISSN oznaka.",
|
||||||
|
"Italijanščina": "Italijanščina",
|
||||||
|
"IZBERI": "IZBERI",
|
||||||
|
"Izberi datoteko": "Izberi datoteko",
|
||||||
|
"IZBERI VEČ": "IZBERI VEČ",
|
||||||
|
"Izberite enega od rezultatov luščenja s seznama.": "Izberite enega od rezultatov luščenja s seznama.",
|
||||||
|
"Izberite format datoteke, v kateri je slovar shranjen na vašem računalniku.": "Izberite format datoteke, v kateri je slovar shranjen na vašem računalniku.",
|
||||||
|
"Izberite format izpisa terminološkega slovarja.": "Izberite format izpisa terminološkega slovarja.",
|
||||||
|
"Izberite luščenje": "Izberite luščenje",
|
||||||
|
"Izberite področje": "Izberite področje",
|
||||||
|
"Izberite področje iz seznama področij, da zmanjšate obseg besedil, iz katerih bo potekalo luščenje.": "Izberite področje iz seznama področij, da zmanjšate obseg besedil, iz katerih bo potekalo luščenje.",
|
||||||
|
"Izberite področje svojega terminološkega slovarja na seznamu področij.": "Izberite področje svojega terminološkega slovarja na seznamu področij.",
|
||||||
|
"Izberite slovarske podatke, ki ste jih shranili na svojem računalniku.": "Izberite slovarske podatke, ki ste jih shranili na svojem računalniku.",
|
||||||
|
"Izberite takšno ime, ki vam bo pomagalo slediti rezultatom, če boste luščenje besedil opravili večkrat.": "Izberite takšno ime, ki vam bo pomagalo slediti rezultatom, če boste luščenje besedil opravili večkrat.",
|
||||||
|
"Izberite vrsto dokumenta iz seznama, npr. članek, diplomsko delo.": "Izberite vrsto dokumenta iz seznama, npr. članek, diplomsko delo.",
|
||||||
|
"Izberite vse tuje jezike, ki jih bo terminološki slovar vseboval.": "Izberite vse tuje jezike, ki jih bo terminološki slovar vseboval.",
|
||||||
|
"Izberite, ali želite izpisati samo jezikovno pregledane slovarske sestavke.": "Izberite, ali želite izpisati samo jezikovno pregledane slovarske sestavke.",
|
||||||
|
"Izberite, ali želite izpisati samo objavljene ali tudi neobjavljene slovarske sestavke.": "Izberite, ali želite izpisati samo objavljene ali tudi neobjavljene slovarske sestavke.",
|
||||||
|
"Izberite, ali želite izpisati samo strokovno pregledane slovarske sestavke.": "Izberite, ali želite izpisati samo strokovno pregledane slovarske sestavke.",
|
||||||
|
"Izberite, ali želite izpisati vse slovarske sestavke ali samo tiste, ki so v določeni fazi urejanja.": "Izberite, ali želite izpisati vse slovarske sestavke ali samo tiste, ki so v določeni fazi urejanja.",
|
||||||
|
"Izberite, katere slovarske sestavke želite izpisati glede na njihovo veljavnost.": "Izberite, katere slovarske sestavke želite izpisati glede na njihovo veljavnost.",
|
||||||
|
"Izbrišete lahko celoten slovar z vsemi metapodatki. Dejanja ni mogoče preklicati.": "Izbrišete lahko celoten slovar z vsemi metapodatki. Dejanja ni mogoče preklicati.",
|
||||||
|
"Izbrišete lahko vse doslej obdelane slovarske sestavke in ohranite vse metapodatke o slovarju. Dejanja ni mogoče preklicati.": "Izbrišete lahko vse doslej obdelane slovarske sestavke in ohranite vse metapodatke o slovarju. Dejanja ni mogoče preklicati.",
|
||||||
|
"Izbriši": "Izbriši",
|
||||||
|
"IZBRIŠI": "IZBRIŠI",
|
||||||
|
"Izbriši obstoječe slovarske sestavke.": "Izbriši obstoječe slovarske sestavke.",
|
||||||
|
"Izbriši polje": "Izbriši polje",
|
||||||
|
"Izbriši račun": "Izbriši račun",
|
||||||
|
"Izgovor": "Izgovor",
|
||||||
|
"Izpolnite polja in na kratko opišite vsebino terminološkega slovarja.": "Izpolnite polja in na kratko opišite vsebino terminološkega slovarja.",
|
||||||
|
"Izvoz": "Izvoz",
|
||||||
|
"Izvozi": "Izvozi",
|
||||||
|
"IZVOZI": "IZVOZI",
|
||||||
|
"Izvozi termine od številke": "Izvozi termine od številke",
|
||||||
|
"je že v tabeli.": "je že v tabeli.",
|
||||||
|
"Jeziki": "Jeziki",
|
||||||
|
"Jeziki iskanja": "Jeziki iskanja",
|
||||||
|
"Jezikovni pregled": "Jezikovni pregled",
|
||||||
|
"Jezikovno pregledano": "Jezikovno pregledano",
|
||||||
|
"KANONIČNA OBLIKA": "KANONIČNA OBLIKA",
|
||||||
|
"KLJUČNE BESEDE": "KLJUČNE BESEDE",
|
||||||
|
"Ko bo luščenje uspešno zaključeno, boste na svoj elektronski naslov, ki ste ga navedli ob registraciji, prejeli obvestilo.": "Ko bo luščenje uspešno zaključeno, boste na svoj elektronski naslov, ki ste ga navedli ob registraciji, prejeli obvestilo.",
|
||||||
|
"Ko boste končali z urejanjem svojega terminološkega slovarja, lahko objavite vse slovarske sestavke, ki bodo postali vidni vsem uporabnikom. Vaše dejanje mora potrditi še administrator portala.": "Ko boste končali z urejanjem svojega terminološkega slovarja, lahko objavite vse slovarske sestavke, ki bodo postali vidni vsem uporabnikom. Vaše dejanje mora potrditi še administrator portala.",
|
||||||
|
"komentar": "komentar",
|
||||||
|
"komentarja": "komentarja",
|
||||||
|
"komentarjev": "komentarjev",
|
||||||
|
"komentarji": "komentarji",
|
||||||
|
"Komentarji": "Komentarji",
|
||||||
|
"Končan": "Končan",
|
||||||
|
"Konec": "Konec",
|
||||||
|
"Kontrola objave": "Kontrola objave",
|
||||||
|
"KONTROLA OBJAVE": "KONTROLA OBJAVE",
|
||||||
|
"Korpus OSS": "Korpus OSS",
|
||||||
|
"Lastna svetovalnica": "Lastna svetovalnica",
|
||||||
|
"Lastni dokumenti": "Lastni dokumenti",
|
||||||
|
"Lastnosti": "Lastnosti",
|
||||||
|
"LETO": "LETO",
|
||||||
|
"Luščenje": "Luščenje",
|
||||||
|
"Luščenje | dodaj opravilo": "Luščenje | dodaj opravilo",
|
||||||
|
"Luščenje iz korpusa besedil OSS": "Luščenje iz korpusa besedil OSS",
|
||||||
|
"Luščenje iz lastnih besedil": "Luščenje iz lastnih besedil",
|
||||||
|
"Luščenje iz lastnih besedil | dodaj opravilo ": "Luščenje iz lastnih besedil | dodaj opravilo ",
|
||||||
|
"Luščenje je bilo dano v fazo obdelave.": "Luščenje je bilo dano v fazo obdelave.",
|
||||||
|
"Luščenje končano": "Luščenje končano",
|
||||||
|
"Luščenje terminoloških kandidatov iz besedil, ki jih ima uporabnik shranjena pri sebi. Za boljšo učinkovitost svetujemo format .txt.": "Luščenje terminoloških kandidatov iz besedil, ki jih ima uporabnik shranjena pri sebi. Za boljšo učinkovitost svetujemo format .txt.",
|
||||||
|
"Luščenje terminoloških kandidatov iz besedil, ki jih uporabnik izbere med vsemi besedili, vključenimi v <a class=\"gray-info\" href=\"https://openscience.si/\" target=\"_blank\">Nacionalni portal odprte znanosti</a>.": "Luščenje terminoloških kandidatov iz besedil, ki jih uporabnik izbere med vsemi besedili, vključenimi v <a class=\"gray-info\" href=\"https://openscience.si/\" target=\"_blank\">Nacionalni portal odprte znanosti</a>.",
|
||||||
|
"Luščenje terminoloških kandidatov iz besedil, ki jih uporabnik namensko izbere, je najučinkovitejše. Primerno izbrano ime opravila, vam omogoča, da lahko sledite, katera luščenja ste že opravili. Ko vnesete podatke, morate vse spremembe shraniti. Zaradi omejenega prostora za shranjevanje lahko shranite največ pet zadnjih luščenj. ": "Luščenje terminoloških kandidatov iz besedil, ki jih uporabnik namensko izbere, je najučinkovitejše. Primerno izbrano ime opravila, vam omogoča, da lahko sledite, katera luščenja ste že opravili. Ko vnesete podatke, morate vse spremembe shraniti. Zaradi omejenega prostora za shranjevanje lahko shranite največ pet zadnjih luščenj. ",
|
||||||
|
"Luščenje terminoloških kandidatov iz besedil, ki so že predhodno oblikoslovno označena, nudi dobre rezultate, vendar je treba nabor besedil omejiti. Svetujemo vam, da zoožite področje in dodatno omejite izbiro s tipi besedil in časovnim razponom, v katerih so nastala. Ko vnesete podatke, morate vse spremembe shraniti. Po vnosu podatkov, s katerimi boste omejili nabor izbranih besedil, morate pritisniti gumb Najdi. Izbiro boste shranili lahko le v primeru, da ne bo število najdenih dokumentov preveliko.": "Luščenje terminoloških kandidatov iz besedil, ki so že predhodno oblikoslovno označena, nudi dobre rezultate, vendar je treba nabor besedil omejiti. Svetujemo vam, da zoožite področje in dodatno omejite izbiro s tipi besedil in časovnim razponom, v katerih so nastala. Ko vnesete podatke, morate vse spremembe shraniti. Po vnosu podatkov, s katerimi boste omejili nabor izbranih besedil, morate pritisniti gumb Najdi. Izbiro boste shranili lahko le v primeru, da ne bo število najdenih dokumentov preveliko.",
|
||||||
|
"MAKSIMALNO ŠTEVILO KOPIJ SLOVARSKIH SESTAVKOV": "MAKSIMALNO ŠTEVILO KOPIJ SLOVARSKIH SESTAVKOV",
|
||||||
|
"Meni": "Meni",
|
||||||
|
"MENI": "MENI",
|
||||||
|
"MINIMALNO ŠTEVILO SLOVARSKIH SESTAVKOV": "MINIMALNO ŠTEVILO SLOVARSKIH SESTAVKOV",
|
||||||
|
"Modul za luščenje terminoloških kandidatov iz besedil.": "Modul za luščenje terminoloških kandidatov iz besedil.",
|
||||||
|
"Modul za svetovanje pri terminoloških zagatah.": "Modul za svetovanje pri terminoloških zagatah.",
|
||||||
|
"Modul za urejanje terminoloških slovarjev.": "Modul za urejanje terminoloških slovarjev.",
|
||||||
|
"MODULI": "MODULI",
|
||||||
|
"Moj Profil": "Moj Profil",
|
||||||
|
"Moji slovarji": "Moji slovarji",
|
||||||
|
"Morebitne že obstoječe poimenovalne rešitve, če obstajajo.": "Morebitne že obstoječe poimenovalne rešitve, če obstajajo.",
|
||||||
|
"Morebitni primeri rabe termina v besedilih ali povezave do njih, če obstajajo.": "Morebitni primeri rabe termina v besedilih ali povezave do njih, če obstajajo.",
|
||||||
|
"MOŽNOST OBJAVE SLOVARSKEGA SESTAVKA MED UREJANJEM": "MOŽNOST OBJAVE SLOVARSKEGA SESTAVKA MED UREJANJEM",
|
||||||
|
"MULTIMEDIJA": "MULTIMEDIJA",
|
||||||
|
"Na kratko opišite zasnovo in namen slovarja.": "Na kratko opišite zasnovo in namen slovarja.",
|
||||||
|
"Na seznamu uporabnikov lahko določite posameznemu uporabniku dodatne vloge ali urejate njihove podatke.": "Na seznamu uporabnikov lahko določite posameznemu uporabniku dodatne vloge ali urejate njihove podatke.",
|
||||||
|
"Na tem mestu lahko dodajate svetovalce, ki so registrirani uporabniki terminološkega portala. Če boste pripisali področje, boste lahko svetovalcu dodeljevali samo vprašanja, ki sodijo na izbrano področje, vsem drugim pa vsa.": "Na tem mestu lahko dodajate svetovalce, ki so registrirani uporabniki terminološkega portala. Če boste pripisali področje, boste lahko svetovalcu dodeljevali samo vprašanja, ki sodijo na izbrano področje, vsem drugim pa vsa.",
|
||||||
|
"Na tem mestu lahko določite področne oznake, če želite posamezne termine v svojem terminološkem slovarju razvrstiti še podrobneje.": "Na tem mestu lahko določite področne oznake, če želite posamezne termine v svojem terminološkem slovarju razvrstiti še podrobneje.",
|
||||||
|
"Na tem mestu so zbrani vsi komentarji, povezani s terminološkim portalom.": "Na tem mestu so zbrani vsi komentarji, povezani s terminološkim portalom.",
|
||||||
|
"Na tem mestu so zbrani vsi komentarji, povezani z izbranim terminološkim slovarjem.": "Na tem mestu so zbrani vsi komentarji, povezani z izbranim terminološkim slovarjem.",
|
||||||
|
"na vaš elektronski naslov smo vam poslali sporočilo s povezavo, s katero boste potrdili svoj uporabniški račun na Terminološkem portalu. Povezava za potrditev je veljavna ": "na vaš elektronski naslov smo vam poslali sporočilo s povezavo, s katero boste potrdili svoj uporabniški račun na Terminološkem portalu. Povezava za potrditev je veljavna ",
|
||||||
|
"Na vaš elektronski naslov smo vam posredovali povezavo za ponastavitev gesla. Prosimo preverite svoj elektronski predal.": "Na vaš elektronski naslov smo vam posredovali povezavo za ponastavitev gesla. Prosimo preverite svoj elektronski predal.",
|
||||||
|
"Nabor vseh luščenj, s katerimi lahko uporabnik iz izbranih besedil pridobi sezname terminoloških kandidatov. Posamezne terminološke kandidate lahko preveri tudi v konkordančniku.": "Nabor vseh luščenj, s katerimi lahko uporabnik iz izbranih besedil pridobi sezname terminoloških kandidatov. Posamezne terminološke kandidate lahko preveri tudi v konkordančniku.",
|
||||||
|
"Nabor vseh luščenj, s pomočjo katerih uporabnik iz vhodnih besedil pridobi sezname terminoloških kandidatov in dostop do konkordančnika po teh besedilih.": "Nabor vseh luščenj, s pomočjo katerih uporabnik iz vhodnih besedil pridobi sezname terminoloških kandidatov in dostop do konkordančnika po teh besedilih.",
|
||||||
|
"Nabor vseh slovarjev, ki jih uporabnik lahko ureja kot glavni urednik ali pa ima dodeljeno pravico urejanja, pregledovanja, popravljanja ipd.": "Nabor vseh slovarjev, ki jih uporabnik lahko ureja kot glavni urednik ali pa ima dodeljeno pravico urejanja, pregledovanja, popravljanja ipd.",
|
||||||
|
"NAČIN UVOZA": "NAČIN UVOZA",
|
||||||
|
"Naglas": "Naglas",
|
||||||
|
"Najdi": "Najdi",
|
||||||
|
"Naložbo sofinancirata Republika Slovenija in Evropska unija iz Evropskega sklada za regionalni razvoj": "Naložbo sofinancirata Republika Slovenija in Evropska unija iz Evropskega sklada za regionalni razvoj",
|
||||||
|
"Napačno geslo.": "Napačno geslo.",
|
||||||
|
"Napaka": "Napaka",
|
||||||
|
"Napaka na strežniku": "Napaka na strežniku",
|
||||||
|
"NAPAKA pri iskanju": "NAPAKA pri iskanju",
|
||||||
|
"Napaka pri nalaganju datoteke": "Napaka pri nalaganju datoteke",
|
||||||
|
"NAPAKA V UVOZU": "NAPAKA V UVOZU",
|
||||||
|
"Napredno": "Napredno",
|
||||||
|
"Napredno urejanje omogoča spreminjanje večjega števila podatkov v terminološkem slovarju.": "Napredno urejanje omogoča spreminjanje večjega števila podatkov v terminološkem slovarju.",
|
||||||
|
"Naprej": "Naprej",
|
||||||
|
"Naslednje datoteke niso bile naložene zaradi navedenih razlogov:": "Naslednje datoteke niso bile naložene zaradi navedenih razlogov:",
|
||||||
|
"Naslov": "Naslov",
|
||||||
|
"NASLOV": "NASLOV",
|
||||||
|
"NASLOV SLOVARJA *": "NASLOV SLOVARJA *",
|
||||||
|
"Nastavitve": "Nastavitve",
|
||||||
|
"Nastavitve portala": "Nastavitve portala",
|
||||||
|
"Nastavitve računa": "Nastavitve računa",
|
||||||
|
"Nastavitve slovarjev": "Nastavitve slovarjev",
|
||||||
|
"Nastavitve svetovalnice": "Nastavitve svetovalnice",
|
||||||
|
"Nazaj": "Nazaj",
|
||||||
|
"Nazaj na prijavo": "Nazaj na prijavo",
|
||||||
|
"Nazaj na zadetke": "Nazaj na zadetke",
|
||||||
|
"Ne": "Ne",
|
||||||
|
"Ne izbriši": "Ne izbriši",
|
||||||
|
"Ne morete imeti več kot 5 luščenj!": "Ne morete imeti več kot 5 luščenj!",
|
||||||
|
"Ne shrani": "Ne shrani",
|
||||||
|
"nedefinirano": "nedefinirano",
|
||||||
|
"Nemško-slovenski slovar tehnike in naravoslovja": "Nemško-slovenski slovar tehnike in naravoslovja",
|
||||||
|
"Neobjavljeni": "Neobjavljeni",
|
||||||
|
"Nepravilna vrednost strani": "Nepravilna vrednost strani",
|
||||||
|
"Nepravilno uporabniško ime, elektronski naslov ali geslo.": "Nepravilno uporabniško ime, elektronski naslov ali geslo.",
|
||||||
|
"Nepregledani": "Nepregledani",
|
||||||
|
"Neveljavna e-pošta": "Neveljavna e-pošta",
|
||||||
|
"Neveljavni": "Neveljavni",
|
||||||
|
"Neveljavni elektronski naslov.": "Neveljavni elektronski naslov.",
|
||||||
|
"Ni področja": "Ni področja",
|
||||||
|
"Ni še dodanih uvozov.": "Ni še dodanih uvozov.",
|
||||||
|
"Ni še povezanih portalov": "Ni še povezanih portalov",
|
||||||
|
"Ni slovarskih sestavkov za urejanje.": "Ni slovarskih sestavkov za urejanje.",
|
||||||
|
"Ni vprašanj.": "Ni vprašanj.",
|
||||||
|
"Ni zadetkov": "Ni zadetkov",
|
||||||
|
"Nimate izbranega področja CERIF.": "Nimate izbranega področja CERIF.",
|
||||||
|
"Nimate luščenj za urejanje.": "Nimate luščenj za urejanje.",
|
||||||
|
"Nimate povezav za urejanje.": "Nimate povezav za urejanje.",
|
||||||
|
"Nimate slovarjev za urejanje.": "Nimate slovarjev za urejanje.",
|
||||||
|
"Nimate ustreznih pravic": "Nimate ustreznih pravic",
|
||||||
|
"Niste izbrali glavnega področja.": "Niste izbrali glavnega področja.",
|
||||||
|
"Niste vnesli področnih oznak.": "Niste vnesli področnih oznak.",
|
||||||
|
"Niste vpisali angleškega naslova slovarja.": "Niste vpisali angleškega naslova slovarja.",
|
||||||
|
"Niste vpisali imena": "Niste vpisali imena",
|
||||||
|
"Niste vpisali imena slovarja.": "Niste vpisali imena slovarja.",
|
||||||
|
"Niste vpisali naslova slovarja.": "Niste vpisali naslova slovarja.",
|
||||||
|
"Niste vpisali opisa terminološkega problema.": "Niste vpisali opisa terminološkega problema.",
|
||||||
|
"Niste vpisali priimka.": "Niste vpisali priimka.",
|
||||||
|
"Niste vpisali tujih jezikov.": "Niste vpisali tujih jezikov.",
|
||||||
|
"Nov": "Nov",
|
||||||
|
"Nov avtor": "Nov avtor",
|
||||||
|
"NOV AVTOR SLOVARJA": "NOV AVTOR SLOVARJA",
|
||||||
|
"Nov povezan termin.": "Nov povezan termin.",
|
||||||
|
"Nov slovar": "Nov slovar",
|
||||||
|
"NOV UPORABNIK": "NOV UPORABNIK",
|
||||||
|
"Nov video": "Nov video",
|
||||||
|
"NOV VIDEO": "NOV VIDEO",
|
||||||
|
"Nov video.": "Nov video.",
|
||||||
|
"Nov zvok": "Nov zvok",
|
||||||
|
"NOV ZVOK": "NOV ZVOK",
|
||||||
|
"Nov zvok.": "Nov zvok.",
|
||||||
|
"Nova povezava": "Nova povezava",
|
||||||
|
"Nova slika": "Nova slika",
|
||||||
|
"NOVA SLIKA": "NOVA SLIKA",
|
||||||
|
"Nova slika.": "Nova slika.",
|
||||||
|
"Novo": "Novo",
|
||||||
|
"NOVO GESLO": "NOVO GESLO",
|
||||||
|
"Novo podpodročje": "Novo podpodročje",
|
||||||
|
"NOVO PODPODROČJE": "NOVO PODPODROČJE",
|
||||||
|
"NOVO PODPODROČJE (angleško)": "NOVO PODPODROČJE (angleško)",
|
||||||
|
"Novo podpodročje (angleško).": "Novo podpodročje (angleško).",
|
||||||
|
"NOVO PODPODROČJE (slovensko)": "NOVO PODPODROČJE (slovensko)",
|
||||||
|
"Novo terminološko vprašanje": "Novo terminološko vprašanje",
|
||||||
|
"Novo vprašanje": "Novo vprašanje",
|
||||||
|
"Novo vprašanje za Terminološko svetovalnico": "Novo vprašanje za Terminološko svetovalnico",
|
||||||
|
"O slovarju": "O slovarju",
|
||||||
|
"O terminu": "O terminu",
|
||||||
|
"Objava med urejanjem": "Objava med urejanjem",
|
||||||
|
"Objava terminološkega vprašanja": "Objava terminološkega vprašanja",
|
||||||
|
"Objava vseh slovarskih sestavkov": "Objava vseh slovarskih sestavkov",
|
||||||
|
"Objavi": "Objavi",
|
||||||
|
"OBJAVI": "OBJAVI",
|
||||||
|
"Objavljeni": "Objavljeni",
|
||||||
|
"Objavljeno": "Objavljeno",
|
||||||
|
"Oblike": "Oblike",
|
||||||
|
"Oblike, naglasi, izgovor": "Oblike, naglasi, izgovor",
|
||||||
|
"Obljavljeno": "Obljavljeno",
|
||||||
|
"Obnovi": "Obnovi",
|
||||||
|
"Obstoječe poimenovalne rešitve": "Obstoječe poimenovalne rešitve",
|
||||||
|
"OBSTOJEČE POIMENOVALNE REŠITVE": "OBSTOJEČE POIMENOVALNE REŠITVE",
|
||||||
|
"Obvestilo": "Obvestilo",
|
||||||
|
"Obvestilo o številu gesel": "Obvestilo o številu gesel",
|
||||||
|
"Odgovor": "Odgovor",
|
||||||
|
"Odgovori": "Odgovori",
|
||||||
|
"Odgovori na vprašanja:": "Odgovori na vprašanja:",
|
||||||
|
"Odjava": "Odjava",
|
||||||
|
"odprt": "odprt",
|
||||||
|
"Odprt": "Odprt",
|
||||||
|
"Odstrani": "Odstrani",
|
||||||
|
"Omogočeno": "Omogočeno",
|
||||||
|
"OPIS PORTALA": "OPIS PORTALA",
|
||||||
|
"OPIS SLOVARJA": "OPIS SLOVARJA",
|
||||||
|
"Opis terminološkega problema": "Opis terminološkega problema",
|
||||||
|
"OPIS TERMINOLOŠKEGA PROBLEMA": "OPIS TERMINOLOŠKEGA PROBLEMA",
|
||||||
|
"Opis terminološkega problema:": "Opis terminološkega problema:",
|
||||||
|
"Opis terminološkega problema: ": "Opis terminološkega problema: ",
|
||||||
|
"Osnovne nastavitve": "Osnovne nastavitve",
|
||||||
|
"Osnovni podatki": "Osnovni podatki",
|
||||||
|
"Ožji": "Ožji",
|
||||||
|
"Oznaka portala": "Oznaka portala",
|
||||||
|
"OZNAKA PORTALA *": "OZNAKA PORTALA *",
|
||||||
|
"Po koncu urejanja vseh slovarskih sestavkov lahko slovar objavite in ga tako prikažete na javnem delu terminološkega portala.": "Po koncu urejanja vseh slovarskih sestavkov lahko slovar objavite in ga tako prikažete na javnem delu terminološkega portala.",
|
||||||
|
"Počisti filtre": "Počisti filtre",
|
||||||
|
"POČISTI FILTRE": "POČISTI FILTRE",
|
||||||
|
"Podnaslov": "Podnaslov",
|
||||||
|
"Podpodročja": "Podpodročja",
|
||||||
|
"Podpodročja so namenjena podrobnejšemu razvrščanju terminoloških slovarjev. Administrator mora vsa novo predlagana podpodročja potrditi, preden jih lahko na seznamu vidijo tudi drugi uporabniki portala.": "Podpodročja so namenjena podrobnejšemu razvrščanju terminoloških slovarjev. Administrator mora vsa novo predlagana podpodročja potrditi, preden jih lahko na seznamu vidijo tudi drugi uporabniki portala.",
|
||||||
|
"Podpodročje": "Podpodročje",
|
||||||
|
"PODPODROČJE": "PODPODROČJE",
|
||||||
|
"Podpodročje.": "Podpodročje.",
|
||||||
|
"Področja": "Področja",
|
||||||
|
"Področje": "Področje",
|
||||||
|
"PODROČJE": "PODROČJE",
|
||||||
|
"PODROČJE *": "PODROČJE *",
|
||||||
|
"Področje, v katero sodi opisani terminološki problem.": "Področje, v katero sodi opisani terminološki problem.",
|
||||||
|
"Področje.": "Področje.",
|
||||||
|
"Področna oznaka": "Področna oznaka",
|
||||||
|
"PODROČNA OZNAKA": "PODROČNA OZNAKA",
|
||||||
|
"PODROČNA OZNAKA:": "PODROČNA OZNAKA:",
|
||||||
|
"Področne oznake": "Področne oznake",
|
||||||
|
"PODROČNE OZNAKE": "PODROČNE OZNAKE",
|
||||||
|
"Podvoji": "Podvoji",
|
||||||
|
"PODVOJI": "PODVOJI",
|
||||||
|
"Poiščite naslove terminoloških slovarjev, s katerimi želite dopolniti iskalne zadetke na svojem portalu. Lahko izberete vse slovarje ali le nekatere. Svoje izbire morate shraniti. Vse vaše izbire mora potrditi administrator povezanega portala.": "Poiščite naslove terminoloških slovarjev, s katerimi želite dopolniti iskalne zadetke na svojem portalu. Lahko izberete vse slovarje ali le nekatere. Svoje izbire morate shraniti. Vse vaše izbire mora potrditi administrator povezanega portala.",
|
||||||
|
"Pojasnilo": "Pojasnilo",
|
||||||
|
"POJASNILO": "POJASNILO",
|
||||||
|
"POJASNILO:": "POJASNILO:",
|
||||||
|
"POJAVITVE": "POJAVITVE",
|
||||||
|
"Poleg objave slovarskega sestavka v fazi \"Urejeno\" je omogočena tudi objava v fazi \"V urejanju\".": "Poleg objave slovarskega sestavka v fazi \"Urejeno\" je omogočena tudi objava v fazi \"V urejanju\".",
|
||||||
|
"Polja naslov, vprašanje in mnenje so obvezna!": "Polja naslov, vprašanje in mnenje so obvezna!",
|
||||||
|
"Polno ime terminološkega portala.": "Polno ime terminološkega portala.",
|
||||||
|
"Pomoč": "Pomoč",
|
||||||
|
"Ponastavi geslo": "Ponastavi geslo",
|
||||||
|
"Ponovi Geslo": "Ponovi Geslo",
|
||||||
|
"PONOVI GESLO": "PONOVI GESLO",
|
||||||
|
"PONOVI NOVO GESLO": "PONOVI NOVO GESLO",
|
||||||
|
"Portal": "Portal",
|
||||||
|
"Portal lahko povežete s Terminološko svetovalnico ZRC SAZU in tako prikazujete terminološke odgovore na svojem portalu, lahko pa vklopite lastno svetovalnico. V tem primeru morate med registriranimi uporabniki izbrati svetovalce in urednika svetovalnice, ki bodo odgovarjali na terminološka vprašanja uporabnikov.": "Portal lahko povežete s Terminološko svetovalnico ZRC SAZU in tako prikazujete terminološke odgovore na svojem portalu, lahko pa vklopite lastno svetovalnico. V tem primeru morate med registriranimi uporabniki izbrati svetovalce in urednika svetovalnice, ki bodo odgovarjali na terminološka vprašanja uporabnikov.",
|
||||||
|
"Portali": "Portali",
|
||||||
|
"Pošlji": "Pošlji",
|
||||||
|
"Potrdi": "Potrdi",
|
||||||
|
"POTRDI": "POTRDI",
|
||||||
|
"Potrditev objave": "Potrditev objave",
|
||||||
|
"Potrjen": "Potrjen",
|
||||||
|
"Povezani slovarji": "Povezani slovarji",
|
||||||
|
"Povezani termin": "Povezani termin",
|
||||||
|
"POVEZANI TERMIN": "POVEZANI TERMIN",
|
||||||
|
"POVEZANI TERMIN:": "POVEZANI TERMIN:",
|
||||||
|
"POVEZAVA": "POVEZAVA",
|
||||||
|
"Povezava ni (več) veljavna. Prosimo, da ponovno zahtevate ponastavitev gesla.": "Povezava ni (več) veljavna. Prosimo, da ponovno zahtevate ponastavitev gesla.",
|
||||||
|
"Povezave": "Povezave",
|
||||||
|
"Povezave s portali": "Povezave s portali",
|
||||||
|
"Pozabljeno geslo": "Pozabljeno geslo",
|
||||||
|
"Pozdravljeni ": "Pozdravljeni ",
|
||||||
|
"Pozor": "Pozor",
|
||||||
|
"Prazno obvezno polje": "Prazno obvezno polje",
|
||||||
|
"Preden naložite besedila, jih shranite v besedilni obliki (končnica .txt).": "Preden naložite besedila, jih shranite v besedilni obliki (končnica .txt).",
|
||||||
|
"Predlog": "Predlog",
|
||||||
|
"Predogled": "Predogled",
|
||||||
|
"Pregledani": "Pregledani",
|
||||||
|
"Prekinjen": "Prekinjen",
|
||||||
|
"Prekliči": "Prekliči",
|
||||||
|
"Preverite, če ste pravilno vpisali uporabniško ime. Bodite pozorni na velike in male črke.": "Preverite, če ste pravilno vpisali uporabniško ime. Bodite pozorni na velike in male črke.",
|
||||||
|
"Prevod": "Prevod",
|
||||||
|
"Pri brisanju datoteke je prišlo do napake.": "Pri brisanju datoteke je prišlo do napake.",
|
||||||
|
"Priimek": "Priimek",
|
||||||
|
"PRIIMEK": "PRIIMEK",
|
||||||
|
"Priimek registriranega uporabnika.": "Priimek registriranega uporabnika.",
|
||||||
|
"Prijava": "Prijava",
|
||||||
|
"PRIJAVA": "PRIJAVA",
|
||||||
|
"Prijava uspešna": "Prijava uspešna",
|
||||||
|
"Prikaži datume": "Prikaži datume",
|
||||||
|
"Prikaži filtre": "Prikaži filtre",
|
||||||
|
"PRIKAŽI VSE": "PRIKAŽI VSE",
|
||||||
|
"Primeri rabe": "Primeri rabe",
|
||||||
|
"PRIMERI RABE V BESEDILIH": "PRIMERI RABE V BESEDILIH",
|
||||||
|
"Pripravljeno": "Pripravljeno",
|
||||||
|
"Prišlo je do napake pri pošiljanju sporočila na vaš elektronski naslov. Poskusite ponovno.": "Prišlo je do napake pri pošiljanju sporočila na vaš elektronski naslov. Poskusite ponovno.",
|
||||||
|
"Prišlo je do napake.": "Prišlo je do napake.",
|
||||||
|
"Prišlo je do strežniške napake. Poskusite kasneje.": "Prišlo je do strežniške napake. Poskusite kasneje.",
|
||||||
|
"Prosim, ponovno vnesite geslo": "Prosim, ponovno vnesite geslo",
|
||||||
|
"Prosim, vnesite geslo": "Prosim, vnesite geslo",
|
||||||
|
"RAČUN": "RAČUN",
|
||||||
|
"Razumem": "Razumem",
|
||||||
|
"registracija": "registracija",
|
||||||
|
"Registracija": "Registracija",
|
||||||
|
"Registracija uporabnika je potrjena.": "Registracija uporabnika je potrjena.",
|
||||||
|
"Registracija uspešna": "Registracija uspešna",
|
||||||
|
"Registrirani uporabniki lahko dobijo različne vloge na portalu. Kot administrator jim lahko dodelite tudi vlogo skrbnika slovarjev in/ali skrbnika svetovalnice.": "Registrirani uporabniki lahko dobijo različne vloge na portalu. Kot administrator jim lahko dodelite tudi vlogo skrbnika slovarjev in/ali skrbnika svetovalnice.",
|
||||||
|
"REPUBLIKA SLOVENIJA MINISTRSTVO ZA KULTURO": "REPUBLIKA SLOVENIJA MINISTRSTVO ZA KULTURO",
|
||||||
|
"rezultat": "rezultat",
|
||||||
|
"Rezultat luščenja so terminološki kandidati. Z uvozom rezultatov posameznega luščenja lahko dopolnite geslovnik terminološkega slovarja, ki ga urejate.": "Rezultat luščenja so terminološki kandidati. Z uvozom rezultatov posameznega luščenja lahko dopolnite geslovnik terminološkega slovarja, ki ga urejate.",
|
||||||
|
"rezultata": "rezultata",
|
||||||
|
"rezultati": "rezultati",
|
||||||
|
"Rezultati: ": "Rezultati: ",
|
||||||
|
"rezultatov": "rezultatov",
|
||||||
|
"S ključnimi besedami, ki so vključene v večino znanstvenih in strokovnih, lahko bolj natančno izberete besedila, ki jih želite uporabiti za luščenje terminoloških kandidatov.": "S ključnimi besedami, ki so vključene v večino znanstvenih in strokovnih, lahko bolj natančno izberete besedila, ki jih želite uporabiti za luščenje terminoloških kandidatov.",
|
||||||
|
"S prijavo pridobite možnost uporabe vseh funkcij terminološkega portala.": "S prijavo pridobite možnost uporabe vseh funkcij terminološkega portala.",
|
||||||
|
"S tem dejanjem boste izbrisali vse slovarske sestavke v slovarju in vse metapodatke. Dejanja ni mogoče razveljaviti.": "S tem dejanjem boste izbrisali vse slovarske sestavke v slovarju in vse metapodatke. Dejanja ni mogoče razveljaviti.",
|
||||||
|
"S tem dejanjem boste izbrisali vse slovarske sestavke v slovarju. Metapodatki bodo ostali. Dejanja ni mogoče razveljaviti.": "S tem dejanjem boste izbrisali vse slovarske sestavke v slovarju. Metapodatki bodo ostali. Dejanja ni mogoče razveljaviti.",
|
||||||
|
"Še niste registriran uporabnik, registrirajte se.": "Še niste registriran uporabnik, registrirajte se.",
|
||||||
|
"Settings": "Settings",
|
||||||
|
"Seznam": "Seznam",
|
||||||
|
"Seznam luščenj": "Seznam luščenj",
|
||||||
|
"Seznam neželenih terminov": "Seznam neželenih terminov",
|
||||||
|
"Seznam objavljenih vprašanj.": "Seznam objavljenih vprašanj.",
|
||||||
|
"Seznam povezav": "Seznam povezav",
|
||||||
|
"Seznam slovarjev": "Seznam slovarjev",
|
||||||
|
"Seznam slovarjev s povezanega portala.": "Seznam slovarjev s povezanega portala.",
|
||||||
|
"Seznam svetovalcev": "Seznam svetovalcev",
|
||||||
|
"Seznam terminoloških kandidatov": "Seznam terminoloških kandidatov",
|
||||||
|
"Seznam terminoloških kandidatov bo natančnejši, če boste dodali tudi seznam neželenih besed, ki naj jih luščilnik izloči iz seznama. Seznam lahko vsebuje splošne termine, npr. tabela, kazalnik, ali pogoste slovnične besede, zlasti veznike, pomožne glagole.": "Seznam terminoloških kandidatov bo natančnejši, če boste dodali tudi seznam neželenih besed, ki naj jih luščilnik izloči iz seznama. Seznam lahko vsebuje splošne termine, npr. tabela, kazalnik, ali pogoste slovnične besede, zlasti veznike, pomožne glagole.",
|
||||||
|
"Seznam terminoloških kandidatov, ki so rezultat izbranega luščenja.": "Seznam terminoloških kandidatov, ki so rezultat izbranega luščenja.",
|
||||||
|
"Seznam uporabnikov": "Seznam uporabnikov",
|
||||||
|
"Seznam urejenih vprašanj, ki čakajo na potrditev dokončne objave.": "Seznam urejenih vprašanj, ki čakajo na potrditev dokončne objave.",
|
||||||
|
"Seznam vprašanj, ki so jih moderatorji zavrnili in jih je treba dodeliti nekomu drugemu.": "Seznam vprašanj, ki so jih moderatorji zavrnili in jih je treba dodeliti nekomu drugemu.",
|
||||||
|
"Seznam vprašanj, ki so jih poslali uporabniki, in še niso bila dodeljena moderatorjem.": "Seznam vprašanj, ki so jih poslali uporabniki, in še niso bila dodeljena moderatorjem.",
|
||||||
|
"Seznam vprašanj, ki so še v urejanju.": "Seznam vprašanj, ki so še v urejanju.",
|
||||||
|
"Seznam vseh slovarjev, ki jih uredniki, sicer registrirani uporabniki, urejajo na tem portalu.": "Seznam vseh slovarjev, ki jih uredniki, sicer registrirani uporabniki, urejajo na tem portalu.",
|
||||||
|
"Seznam vseh slovarjev, ki so na tem portalu na voljo uporabnikom.": "Seznam vseh slovarjev, ki so na tem portalu na voljo uporabnikom.",
|
||||||
|
"Seznam zadetkov": "Seznam zadetkov",
|
||||||
|
"Shemo za format xml si prenesete <a href=\"/slovarji/xml-schema\" target=\"_blank\">tukaj</a>.": "Shemo za format xml si prenesete <a href=\"/slovarji/xml-schema\" target=\"_blank\">tukaj</a>.",
|
||||||
|
"Shrani": "Shrani",
|
||||||
|
"Shranjeno": "Shranjeno",
|
||||||
|
"Shranjujem ...": "Shranjujem ...",
|
||||||
|
"Sinhroniziraj": "Sinhroniziraj",
|
||||||
|
"Sinonim": "Sinonim",
|
||||||
|
"SINONIM:": "SINONIM:",
|
||||||
|
"Sinonimi": "Sinonimi",
|
||||||
|
"SINONIMI": "SINONIMI",
|
||||||
|
"Širši": "Širši",
|
||||||
|
"SKRAJŠANI NASLOV SLOVARJA": "SKRAJŠANI NASLOV SLOVARJA",
|
||||||
|
"Skrbnik portala": "Skrbnik portala",
|
||||||
|
"Skrbnik slovarjev": "Skrbnik slovarjev",
|
||||||
|
"Skrbnik svetovalnice": "Skrbnik svetovalnice",
|
||||||
|
"Skrbniki portala": "Skrbniki portala",
|
||||||
|
"slika": "slika",
|
||||||
|
"Slika": "Slika",
|
||||||
|
"SLIKA": "SLIKA",
|
||||||
|
"SLIKA:": "SLIKA:",
|
||||||
|
"Slovar": "Slovar",
|
||||||
|
"Slovar je bil izbrisan.": "Slovar je bil izbrisan.",
|
||||||
|
"Slovar je odprt.": "Slovar je odprt.",
|
||||||
|
"Slovar je pregledal jezikoslovec.": "Slovar je pregledal jezikoslovec.",
|
||||||
|
"Slovar je pregledal področni strokovnjak.": "Slovar je pregledal področni strokovnjak.",
|
||||||
|
"Slovar je v odpiranju - čaka na potrditev skrbnika slovarjev": "Slovar je v odpiranju - čaka na potrditev skrbnika slovarjev",
|
||||||
|
"Slovar odprt": "Slovar odprt",
|
||||||
|
"SLOVAR UVOŽEN": "SLOVAR UVOŽEN",
|
||||||
|
"Slovarji": "Slovarji",
|
||||||
|
"Slovarji portala": "Slovarji portala",
|
||||||
|
"Slovarski sestavek je neveljaven": "Slovarski sestavek je neveljaven",
|
||||||
|
"Slovarski sestavek je pregledan in dokončan.": "Slovarski sestavek je pregledan in dokončan.",
|
||||||
|
"Slovarski sestavek je v fazi, ko se dodajajo in oblikujejo elementi, ki ste jih izbrali v strukturi slovarskega sestavka.": "Slovarski sestavek je v fazi, ko se dodajajo in oblikujejo elementi, ki ste jih izbrali v strukturi slovarskega sestavka.",
|
||||||
|
"Slovarski sestavki so bili izbrisani.": "Slovarski sestavki so bili izbrisani.",
|
||||||
|
"Slovarski sestavki so bili objavljeni.": "Slovarski sestavki so bili objavljeni.",
|
||||||
|
"SLOVARSKIH SESTAVKOV": "SLOVARSKIH SESTAVKOV",
|
||||||
|
"Slovenščina": "Slovenščina",
|
||||||
|
"Soglašam s <a target=\"_blank\" href=\"/politika-zasebnosti\">politiko zasebnosti</a>": "Soglašam s <a target=\"_blank\" href=\"/politika-zasebnosti\">politiko zasebnosti</a>",
|
||||||
|
"Sorodni": "Sorodni",
|
||||||
|
"Sprememba stanja slovarja": "Sprememba stanja slovarja",
|
||||||
|
"Spremeni geslo": "Spremeni geslo",
|
||||||
|
"SPREMENJEN": "SPREMENJEN",
|
||||||
|
"ŠT. GESEL": "ŠT. GESEL",
|
||||||
|
"STARO GESLO": "STARO GESLO",
|
||||||
|
"Statistika": "Statistika",
|
||||||
|
"Status": "Status",
|
||||||
|
"STATUS": "STATUS",
|
||||||
|
"Ste pozabili geslo? Napišite svoje uporabniško ime ali elektronski naslov, ki ste ga uporabili ob registraciji. Na ta naslov vam bomo poslali sporočilo, s pomočjo katerega boste lahko vnesli novo geslo.": "Ste pozabili geslo? Napišite svoje uporabniško ime ali elektronski naslov, ki ste ga uporabili ob registraciji. Na ta naslov vam bomo poslali sporočilo, s pomočjo katerega boste lahko vnesli novo geslo.",
|
||||||
|
"Število dokumentov": "Število dokumentov",
|
||||||
|
"Število slovarskih sestavkov je manjše od zahtevanega števila - povečajte število slovarskih sestavkov, sicer ga skrbnik slovarjev lahko zapre": "Število slovarskih sestavkov je manjše od zahtevanega števila - povečajte število slovarskih sestavkov, sicer ga skrbnik slovarjev lahko zapre",
|
||||||
|
"Število slovarskih sestavkov, ki jih mora vsebovati slovar, da je omogočena objava slovarja na portalu.": "Število slovarskih sestavkov, ki jih mora vsebovati slovar, da je omogočena objava slovarja na portalu.",
|
||||||
|
"Število terminov": "Število terminov",
|
||||||
|
"Število vprašanj:": "Število vprašanj:",
|
||||||
|
"ŠTEVILO ZADETKOV NA STRANI": "ŠTEVILO ZADETKOV NA STRANI",
|
||||||
|
"Število zadnjih kopij spremenjenih slovarskih sestavkov, ki se hranijo. Če vrednost ni določena, omejitve ni.": "Število zadnjih kopij spremenjenih slovarskih sestavkov, ki se hranijo. Če vrednost ni določena, omejitve ni.",
|
||||||
|
"Stop termini": "Stop termini",
|
||||||
|
"Stran ne obstaja": "Stran ne obstaja",
|
||||||
|
"Strežnik ni dosegljiv. Poskusite kasneje.": "Strežnik ni dosegljiv. Poskusite kasneje.",
|
||||||
|
"Strinjam se s <a target=\"_blank\" href=\"/pogoji-uporabe\">pogoji uporabe</a>": "Strinjam se s <a target=\"_blank\" href=\"/pogoji-uporabe\">pogoji uporabe</a>",
|
||||||
|
"Strokovni pregled": "Strokovni pregled",
|
||||||
|
"Strokovno pregledano": "Strokovno pregledano",
|
||||||
|
"Struktura": "Struktura",
|
||||||
|
"STRUKTURA - ELEMENTI SLOVARSKEGA SESTAVKA:": "STRUKTURA - ELEMENTI SLOVARSKEGA SESTAVKA:",
|
||||||
|
"Struktura slovarskega sestavka": "Struktura slovarskega sestavka",
|
||||||
|
"Svetovalci": "Svetovalci",
|
||||||
|
"Svetovalec": "Svetovalec",
|
||||||
|
"Svetovalnica": "Svetovalnica",
|
||||||
|
"Svetovanje": "Svetovanje",
|
||||||
|
"Svoj terminološki portal lahko povežete še z drugimi terminološkimi portali in med iskalnimi prikazujete tudi njihove zadetke.": "Svoj terminološki portal lahko povežete še z drugimi terminološkimi portali in med iskalnimi prikazujete tudi njihove zadetke.",
|
||||||
|
"Ta slovar nima opisa": "Ta slovar nima opisa",
|
||||||
|
"Termin": "Termin",
|
||||||
|
"TERMIN": "TERMIN",
|
||||||
|
"TERMIN:": "TERMIN:",
|
||||||
|
"TERMINI": "TERMINI",
|
||||||
|
"Terminološka svetovalnica je namenjena širši strokovni javnosti, ki se sooča s konkretnimi poimenovalnimi problemi, pa naj gre za popolnoma nove pojme, ki jih je v slovenščini šele treba poimenovati, ali že znane pojme, za katere obstaja več poimenovanj.": "Terminološka svetovalnica je namenjena širši strokovni javnosti, ki se sooča s konkretnimi poimenovalnimi problemi, pa naj gre za popolnoma nove pojme, ki jih je v slovenščini šele treba poimenovati, ali že znane pojme, za katere obstaja več poimenovanj.",
|
||||||
|
"Terminološka svetovalnica ZRC SAZU": "Terminološka svetovalnica ZRC SAZU",
|
||||||
|
"Terminološke slovarje lahko v celoti ali po izbranih kriterijih izvozite v različnih formatih in shranite na svojem računalniku.": "Terminološke slovarje lahko v celoti ali po izbranih kriterijih izvozite v različnih formatih in shranite na svojem računalniku.",
|
||||||
|
"Terminološkemu slovarju določite status. Izbirate lahko med zaprt, v urejanju in odprt.": "Terminološkemu slovarju določite status. Izbirate lahko med zaprt, v urejanju in odprt.",
|
||||||
|
"Terminološki kandidati": "Terminološki kandidati",
|
||||||
|
"Terminološko svetovanje": "Terminološko svetovanje",
|
||||||
|
"Tip napake 1 (ni slovenskega termina)": "Tip napake 1 (ni slovenskega termina)",
|
||||||
|
"Tip napake 2 (ni definicije ali tujejezičnega termina)": "Tip napake 2 (ni definicije ali tujejezičnega termina)",
|
||||||
|
"Tu so zbrani vsi komentarji, povezani z vašim slovarjem.": "Tu so zbrani vsi komentarji, povezani z vašim slovarjem.",
|
||||||
|
"Tuj sinonim": "Tuj sinonim",
|
||||||
|
"Tuj termin": "Tuj termin",
|
||||||
|
"Tuja definicija": "Tuja definicija",
|
||||||
|
"Tuji jezik": "Tuji jezik",
|
||||||
|
"TUJI JEZIKI": "TUJI JEZIKI",
|
||||||
|
"Tuji slovarji": "Tuji slovarji",
|
||||||
|
"TUJI TERMIN": "TUJI TERMIN",
|
||||||
|
"Tukaj lahko izbrišete svoj račun.": "Tukaj lahko izbrišete svoj račun.",
|
||||||
|
"Tukaj lahko ponovno ideksirate slovar.": "Tukaj lahko ponovno ideksirate slovar.",
|
||||||
|
"Tukaj lahko spremenite geslo za prijavo.": "Tukaj lahko spremenite geslo za prijavo.",
|
||||||
|
"Tukaj lahko spremenite ime, priimek in elektronski naslov uporabnika.": "Tukaj lahko spremenite ime, priimek in elektronski naslov uporabnika.",
|
||||||
|
"Tukaj lahko spremenite nekatere nastavitve, vezane na posameznega uporabnika.": "Tukaj lahko spremenite nekatere nastavitve, vezane na posameznega uporabnika.",
|
||||||
|
"Unikatna oznaka povezanega terminološkega portala za prikaz na vašem portalu, sestavljena iz dveh črk ali črke in številke.": "Unikatna oznaka povezanega terminološkega portala za prikaz na vašem portalu, sestavljena iz dveh črk ali črke in številke.",
|
||||||
|
"Uporabi": "Uporabi",
|
||||||
|
"Uporabnik": "Uporabnik",
|
||||||
|
"UPORABNIK": "UPORABNIK",
|
||||||
|
"Uporabnik na tem mestu izdela specializirani korpus iz besedil, ki jih je zbral in shranil sam. Besedila naj bodo izbrana po načelih tvorjenja specializiranih korpusov. Za uspešno luščenje priporočamo najmanj 10 besedil. Vsa besedila naj bodo shranjena v besedilnem formatu (.txt). Luščenje podpira tudi formate .docx in .pdf, vendar so rezultati slabši.": "Uporabnik na tem mestu izdela specializirani korpus iz besedil, ki jih je zbral in shranil sam. Besedila naj bodo izbrana po načelih tvorjenja specializiranih korpusov. Za uspešno luščenje priporočamo najmanj 10 besedil. Vsa besedila naj bodo shranjena v besedilnem formatu (.txt). Luščenje podpira tudi formate .docx in .pdf, vendar so rezultati slabši.",
|
||||||
|
"Uporabniki": "Uporabniki",
|
||||||
|
"Uporabnikovi dokumenti": "Uporabnikovi dokumenti",
|
||||||
|
"Uporabniške pravice/vloge": "Uporabniške pravice/vloge",
|
||||||
|
"Uporabniške vloge": "Uporabniške vloge",
|
||||||
|
"Uporabniški korpus": "Uporabniški korpus",
|
||||||
|
"Uporabniški račun še ni aktiviran. Kliknite aktivacijsko povezavo, katero smo vam poslali po elektronski pošti.": "Uporabniški račun še ni aktiviran. Kliknite aktivacijsko povezavo, katero smo vam poslali po elektronski pošti.",
|
||||||
|
"Uporabniško ime": "Uporabniško ime",
|
||||||
|
"UPORABNIŠKO IME": "UPORABNIŠKO IME",
|
||||||
|
"Uporabniško ime ali elektronski naslov": "Uporabniško ime ali elektronski naslov",
|
||||||
|
"Uporabniško ime že obstaja": "Uporabniško ime že obstaja",
|
||||||
|
"Uredi": "Uredi",
|
||||||
|
"UREDI": "UREDI",
|
||||||
|
"Uredi lastnosti": "Uredi lastnosti",
|
||||||
|
"Uredi povezavo": "Uredi povezavo",
|
||||||
|
"Uredi vsebino": "Uredi vsebino",
|
||||||
|
"Urednik slovarja": "Urednik slovarja",
|
||||||
|
"Urejanje": "Urejanje",
|
||||||
|
"Urejanje terminološkega odgovora": "Urejanje terminološkega odgovora",
|
||||||
|
"Urejeni": "Urejeni",
|
||||||
|
"Urejeno": "Urejeno",
|
||||||
|
"URL naslov terminološkega portala, s katerim boste sinhronizirali podatke iz terminoloških virov.": "URL naslov terminološkega portala, s katerim boste sinhronizirali podatke iz terminoloških virov.",
|
||||||
|
"URL naslov terminološkega portala, s katerim želimo povezati svoj portal.": "URL naslov terminološkega portala, s katerim želimo povezati svoj portal.",
|
||||||
|
"URL naslov terminološkega portala, s katerim želimo povezati svoj portal. (API klic)": "URL naslov terminološkega portala, s katerim želimo povezati svoj portal. (API klic)",
|
||||||
|
"URL naslov terminološkega portala, s katerim želite povezati svoj portal. (API klic)": "URL naslov terminološkega portala, s katerim želite povezati svoj portal. (API klic)",
|
||||||
|
"URL povezava do mnenja:": "URL povezava do mnenja:",
|
||||||
|
"URL za sinhronizacijo slovarjev *": "URL za sinhronizacijo slovarjev *",
|
||||||
|
"URL za sinhronizacijo slovarskih sestavkov *": "URL za sinhronizacijo slovarskih sestavkov *",
|
||||||
|
"Uspešno ste ponastavili svoje geslo.": "Uspešno ste ponastavili svoje geslo.",
|
||||||
|
"Ustvari": "Ustvari",
|
||||||
|
"USTVARJEN": "USTVARJEN",
|
||||||
|
"Ustvarjeno novo vprašanje v svetovalnici": "Ustvarjeno novo vprašanje v svetovalnici",
|
||||||
|
"UTEŽ": "UTEŽ",
|
||||||
|
"Uvoz": "Uvoz",
|
||||||
|
"Uvoz iz datoteke": "Uvoz iz datoteke",
|
||||||
|
"Uvoz iz luščilnika": "Uvoz iz luščilnika",
|
||||||
|
"UVOZI": "UVOZI",
|
||||||
|
"Uvozi termine od številke": "Uvozi termine od številke",
|
||||||
|
"V delu": "V delu",
|
||||||
|
"V DRUGI VSEBINI": "V DRUGI VSEBINI",
|
||||||
|
"V IZTOČNICAH": "V IZTOČNICAH",
|
||||||
|
"V obdelavi": "V obdelavi",
|
||||||
|
"V odpiranju": "V odpiranju",
|
||||||
|
"v predogledu": "v predogledu",
|
||||||
|
"V slovarju so samo terminološki kandidati. Slovarja ne morete objaviti.": "V slovarju so samo terminološki kandidati. Slovarja ne morete objaviti.",
|
||||||
|
"V tem razdelku lahko določite elemente slovarskega sestavka. Z izbiro elementov se vam prikazuje podoba slovarskega sestavka. Izbiro lahko tudi med urejanjem vsebine kadarkoli spremenite.": "V tem razdelku lahko določite elemente slovarskega sestavka. Z izbiro elementov se vam prikazuje podoba slovarskega sestavka. Izbiro lahko tudi med urejanjem vsebine kadarkoli spremenite.",
|
||||||
|
"V tem razdelku lahko določite glavne administratorske pravice na terminološkem portalu.": "V tem razdelku lahko določite glavne administratorske pravice na terminološkem portalu.",
|
||||||
|
"V tem razdelku lahko določite uporabniške vloge posameznega uporabnika in urejate njegove podatke.": "V tem razdelku lahko določite uporabniške vloge posameznega uporabnika in urejate njegove podatke.",
|
||||||
|
"V TUJEJEZIČNIH USTREZNIKIH": "V TUJEJEZIČNIH USTREZNIKIH",
|
||||||
|
"V urejanju": "V urejanju",
|
||||||
|
"Vaš komentar je brez vsebine.": "Vaš komentar je brez vsebine.",
|
||||||
|
"Vaše iskanje ni bilo uspešno. Vpišite novo iskalno poizvedbo in poizkusite znova.": "Vaše iskanje ni bilo uspešno. Vpišite novo iskalno poizvedbo in poizkusite znova.",
|
||||||
|
"Več ...": "Več ...",
|
||||||
|
"Več …": "Več …",
|
||||||
|
"Več...": "Več...",
|
||||||
|
"VELIKOST": "VELIKOST",
|
||||||
|
"Veljavni": "Veljavni",
|
||||||
|
"Veljavni elektronski naslov uporabnika, na katerega uporabnik prejema sporočila, povezana s portalom.": "Veljavni elektronski naslov uporabnika, na katerega uporabnik prejema sporočila, povezana s portalom.",
|
||||||
|
"Veljavno": "Veljavno",
|
||||||
|
"Veljavnost": "Veljavnost",
|
||||||
|
"Verzija": "Verzija",
|
||||||
|
"Verzija 1": "Verzija 1",
|
||||||
|
"Verzija:": "Verzija:",
|
||||||
|
"video": "video",
|
||||||
|
"Video": "Video",
|
||||||
|
"VIDEO": "VIDEO",
|
||||||
|
"VIDEO:": "VIDEO:",
|
||||||
|
"Vidno": "Vidno",
|
||||||
|
"Vir": "Vir",
|
||||||
|
"Viri": "Viri",
|
||||||
|
"Vnesite e-naslov, ki ga za obveščanje o novih terminoloških vprašanjih uporabljajo terminološki svetovalci.": "Vnesite e-naslov, ki ga za obveščanje o novih terminoloških vprašanjih uporabljajo terminološki svetovalci.",
|
||||||
|
"Vnesite naslov spletnega mesta, kjer so zbrani vsi odgovori terminološke svetovalnice.": "Vnesite naslov spletnega mesta, kjer so zbrani vsi odgovori terminološke svetovalnice.",
|
||||||
|
"Vnesite novo geslo. Ko ga potrdite, se boste v vaš uporabniški račun lahko spet prijavili z novim geslom.": "Vnesite novo geslo. Ko ga potrdite, se boste v vaš uporabniški račun lahko spet prijavili z novim geslom.",
|
||||||
|
"Vnesite podatke terminološkega portala, s katerim želite povezati svoj portal.": "Vnesite podatke terminološkega portala, s katerim želite povezati svoj portal.",
|
||||||
|
"Vnesite podatke, ki niso sistemsko vključeni v druga polja, npr. vir, zgled rabe.": "Vnesite podatke, ki niso sistemsko vključeni v druga polja, npr. vir, zgled rabe.",
|
||||||
|
"Vnesite polno ime terminološkega portala, s katerim se povezujete.": "Vnesite polno ime terminološkega portala, s katerim se povezujete.",
|
||||||
|
"Vnesite termin.": "Vnesite termin.",
|
||||||
|
"Vnesti morate termin": "Vnesti morate termin",
|
||||||
|
"Vpišite avtorja slovarja, če ste glavni avtor, na prvo mesto zapišite svoje ime.": "Vpišite avtorja slovarja, če ste glavni avtor, na prvo mesto zapišite svoje ime.",
|
||||||
|
"Vpišite definicijo v tujem jeziku.": "Vpišite definicijo v tujem jeziku.",
|
||||||
|
"Vpišite iskalni niz": "Vpišite iskalni niz",
|
||||||
|
"Vpišite novo podpodročje. Na seznamu podpodročij bo vidno takoj po potrditvi administratorja portala.": "Vpišite novo podpodročje. Na seznamu podpodročij bo vidno takoj po potrditvi administratorja portala.",
|
||||||
|
"Vpišite podpodročje glavnega področja, ki ste ga izbrali. Če podpodročja ni med naborom, izberite polje Novo podpodročje.": "Vpišite podpodročje glavnega področja, ki ste ga izbrali. Če podpodročja ni med naborom, izberite polje Novo podpodročje.",
|
||||||
|
"Vprašanje poslano:": "Vprašanje poslano:",
|
||||||
|
"VRSTA": "VRSTA",
|
||||||
|
"VRSTA DOKUMENTA": "VRSTA DOKUMENTA",
|
||||||
|
"Vsebina": "Vsebina",
|
||||||
|
"Vsebina slovarja": "Vsebina slovarja",
|
||||||
|
"Vsebina slovarskega sestavka je bila duplicirana.": "Vsebina slovarskega sestavka je bila duplicirana.",
|
||||||
|
"Vsi povezani slovarji": "Vsi povezani slovarji",
|
||||||
|
"Vsi slovarji": "Vsi slovarji",
|
||||||
|
"Vsi slovarski sestavki": "Vsi slovarski sestavki",
|
||||||
|
"Vstavite definicijo pojma.": "Vstavite definicijo pojma.",
|
||||||
|
"Vstavite termine, ki se za definirani pojem tudi uporabljajo.": "Vstavite termine, ki se za definirani pojem tudi uporabljajo.",
|
||||||
|
"Vstavite ustrezno področno oznako, ki jo želite določiti za posamezni termin.": "Vstavite ustrezno področno oznako, ki jo želite določiti za posamezni termin.",
|
||||||
|
"Z izbiro te možnosti boste pobrisali samo slovarske sestavke, ki so v določeni fazi urejanja.": "Z izbiro te možnosti boste pobrisali samo slovarske sestavke, ki so v določeni fazi urejanja.",
|
||||||
|
"Z registracijo pridobite možnost uporabe vseh funkcij terminološkega portala.": "Z registracijo pridobite možnost uporabe vseh funkcij terminološkega portala.",
|
||||||
|
"Za luščenje terminoloških kandidatov morate biti prijavljeni.": "Za luščenje terminoloških kandidatov morate biti prijavljeni.",
|
||||||
|
"Za prvo objavo slovarja je potrebno dovoljenje skrbnika slovarjev.": "Za prvo objavo slovarja je potrebno dovoljenje skrbnika slovarjev.",
|
||||||
|
"Za to besedo še ni podatkov.": "Za to besedo še ni podatkov.",
|
||||||
|
"Za urejanje slovarjev morate biti prijavljeni.": "Za urejanje slovarjev morate biti prijavljeni.",
|
||||||
|
"Za zastavljanje terminoloških vprašanj morate biti prijavljeni.": "Za zastavljanje terminoloških vprašanj morate biti prijavljeni.",
|
||||||
|
"Začetek": "Začetek",
|
||||||
|
"Začni": "Začni",
|
||||||
|
"ZAČNI": "ZAČNI",
|
||||||
|
"Zadnja sprememba": "Zadnja sprememba",
|
||||||
|
"Zadnji izvozi": "Zadnji izvozi",
|
||||||
|
"Zadnji objavljeni slovarji": "Zadnji objavljeni slovarji",
|
||||||
|
"Zadnji odgovori na vprašanja": "Zadnji odgovori na vprašanja",
|
||||||
|
"Zadnji uvozi": "Zadnji uvozi",
|
||||||
|
"Zapomni si prijavo": "Zapomni si prijavo",
|
||||||
|
"Zaporedje:": "Zaporedje:",
|
||||||
|
"Zapri": "Zapri",
|
||||||
|
"ZAPRI": "ZAPRI",
|
||||||
|
"zaprt": "zaprt",
|
||||||
|
"Zaprt": "Zaprt",
|
||||||
|
"Zaradi organizacije podatkov na portalu za skrajšani naslov slovarja predlagamo poenobesede\tnje, ki se bo izpisovalo ob slovarju, npr. Davčni terminološki slovar → Davki.": "Zaradi organizacije podatkov na portalu za skrajšani naslov slovarja predlagamo poenobesede\tnje, ki se bo izpisovalo ob slovarju, npr. Davčni terminološki slovar → Davki.",
|
||||||
|
"Zaradi organizacije podatkov na portalu za skrajšani naslov slovarja predlagamo poenobesedenje, ki se bo izpisovalo ob slovarju, npr. Davčni terminološki slovar → Davki": "Zaradi organizacije podatkov na portalu za skrajšani naslov slovarja predlagamo poenobesedenje, ki se bo izpisovalo ob slovarju, npr. Davčni terminološki slovar → Davki",
|
||||||
|
"Zastavi novo vprašanje": "Zastavi novo vprašanje",
|
||||||
|
"Zastavi terminološko vprašanje": "Zastavi terminološko vprašanje",
|
||||||
|
"ZAVRJEN": "ZAVRJEN",
|
||||||
|
"ZAVRNI": "ZAVRNI",
|
||||||
|
"Zavrnjeno": "Zavrnjeno",
|
||||||
|
"Zunanji": "Zunanji",
|
||||||
|
"zvok": "zvok",
|
||||||
|
"Zvok": "Zvok",
|
||||||
|
"ZVOK": "ZVOK",
|
||||||
|
"ZVOK:": "ZVOK:",
|
||||||
|
"titleTermsOfUse": "Pogoji uporabe",
|
||||||
|
"titlePrivacyPolicy": "Politika zasebnosti"
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"helpPageTitleHelp": "Pomoč",
|
||||||
|
"helpPageTitleAbout": "Splošno o portalu",
|
||||||
|
"helpPageTitleRegistration": "Registracija",
|
||||||
|
"helpPageTitleSearchIndex": "Iskanje",
|
||||||
|
"helpPageTitleSearchBasic": "Osnovno iskanje",
|
||||||
|
"helpPageTitleSearchAdvanced": "Napredno iskanje",
|
||||||
|
"helpPageTitleExtractionIndex": "Luščenje",
|
||||||
|
"helpPageTitleExtractionSpecializedCorpora": "Specializirani korpusi",
|
||||||
|
"helpPageTitleExtractionPersonalCorpus": "Lastni uporabniški korpus",
|
||||||
|
"helpPageTitleExtractionOssCorpus": "Korpus OSS",
|
||||||
|
"helpPageTitleExtractionStopTerms": "Seznami neželenih besed ali \"Stop termini\"",
|
||||||
|
"helpPageTitleExtractionDomains": "Področja",
|
||||||
|
"helpPageTitleExtractionTermCandidates": "Terminološki kandidati",
|
||||||
|
"helpPageTitleEditingIndex": "Urejanje",
|
||||||
|
"helpPageTitleEditingNewDictionary": "Nov slovar",
|
||||||
|
"helpPageTitleEditingDictionaryProperties": "Urejanje lastnosti slovarja",
|
||||||
|
"helpPageTitleEditingUsers": "Uporabniki",
|
||||||
|
"helpPageTitleEditingStructure": "Struktura slovarskega sestavka",
|
||||||
|
"helpPageTitleEditingComments": "Komentiranje",
|
||||||
|
"helpPageTitleEditingDictionaryContent": "Vsebina slovarja",
|
||||||
|
"helpPageTitleEditingImportingData": "Uvoz podatkov",
|
||||||
|
"helpPageTitleEditingEntriesIndex": "Dodajanje novih slovarskih sestavkov",
|
||||||
|
"helpPageTitleEditingEntriesDomainLabels": "Področne oznake",
|
||||||
|
"helpPageTitleEditingEntriesLabel": "Pojasnilo",
|
||||||
|
"helpPageTitleEditingEntriesDefinition": "Definicja",
|
||||||
|
"helpPageTitleEditingEntriesSynonyms": "Sinonimi",
|
||||||
|
"helpPageTitleEditingEntriesRelatedTerms": "Povezani termini",
|
||||||
|
"helpPageTitleEditingEntriesOtherLanguages": "Tuji jeziki",
|
||||||
|
"helpPageTitleEditingEntriesOther": "Drugo",
|
||||||
|
"helpPageTitleEditingEntriesEditingHistory": "Zgodovina urejanja",
|
||||||
|
"helpPageTitleEditingEntriesComments": "Komentarji",
|
||||||
|
"helpPageTitleEditingEntriesDataExport": "Izvoz podatkov",
|
||||||
|
"helpPageTitleConsultancyIndex": "Svetovanje",
|
||||||
|
"helpPageTitleConsultancyQuestions": "Pošiljanje vprašanj",
|
||||||
|
"helpPageTitleConsultancyTerminologyPrinciples": "Terminološka načela",
|
||||||
|
"helpPageTitleConsultancyLink": "Povezava",
|
||||||
|
"helpPageTitleConsultancyAnswers": "Objava odgovorov",
|
||||||
|
"helpPageTitleAdministrationIndex": "Administracija",
|
||||||
|
"helpPageTitleAdministrationBasicSettingsIndex": "Osnovne nastavitve",
|
||||||
|
"helpPageTitleAdministrationBasicSettingsPortal": "Portal",
|
||||||
|
"helpPageTitleAdministrationBasicSettingsDictionaries": "Slovarji",
|
||||||
|
"helpPageTitleAdministrationBasicSettingsConsultancy": "Svetovalnica",
|
||||||
|
"helpPageTitleAdministrationLinksToOtherPortalsIndex": "Povezave s portali",
|
||||||
|
"helpPageTitleAdministrationLinksToOtherPortalsList": "Seznam",
|
||||||
|
"helpPageTitleAdministrationLinksToOtherPortalsDictionaries": "Slovarji",
|
||||||
|
"helpPageTitleAdministrationDictionaries": "Slovarji",
|
||||||
|
"helpPageTitleAdministrationUsers": "Uporabniki",
|
||||||
|
"helpPageTitleAdministrationSecondaryDomains": "Podpodročja"
|
||||||
|
}
|
||||||
@@ -6,6 +6,67 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
color: v.$tp-black;
|
||||||
|
}
|
||||||
|
|
||||||
|
// .check {
|
||||||
|
// -webkit-appearance: none; /*hides the default checkbox*/
|
||||||
|
// height: 20px;
|
||||||
|
// width: 20px;
|
||||||
|
// position: relative;
|
||||||
|
// top: 20px;
|
||||||
|
// left: 20px;
|
||||||
|
// transition: 0.10s;
|
||||||
|
// background-color: #FE0006;
|
||||||
|
// text-align: center;
|
||||||
|
// font-weight: 600;
|
||||||
|
// color: white;
|
||||||
|
// border-radius: 3px;
|
||||||
|
// outline: none;
|
||||||
|
// }
|
||||||
|
|
||||||
|
/*
|
||||||
|
input[type="checkbox"]:checked {
|
||||||
|
appearance: none;
|
||||||
|
height: 14px;
|
||||||
|
width: 14px;
|
||||||
|
font-size: 10px !important;
|
||||||
|
transition: 0.1s;
|
||||||
|
background-color: v.$navigation-maincolor;
|
||||||
|
text-align: center;
|
||||||
|
// font-weight: 600;
|
||||||
|
color: white;
|
||||||
|
border-radius: 3px;
|
||||||
|
outline: none;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: "✔";
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
.check:checked {
|
||||||
|
background-color: #0E9700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.check:before {
|
||||||
|
content: "✖";
|
||||||
|
}
|
||||||
|
.check:checked:before {
|
||||||
|
content: "✔";
|
||||||
|
}
|
||||||
|
|
||||||
|
.check:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
.dropdown-item:active {
|
||||||
|
background-color: v.$navigation-maincolor;
|
||||||
|
}
|
||||||
|
|
||||||
// input:focus-visible,
|
// input:focus-visible,
|
||||||
// button:focus-visible,
|
// button:focus-visible,
|
||||||
// button:focus {
|
// button:focus {
|
||||||
@@ -17,6 +78,15 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.outline-enabled {
|
||||||
|
outline: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover-opacity-blue:hover {
|
||||||
|
color: v.$navigation-maincolor;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
.index-font-size {
|
.index-font-size {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
@@ -76,10 +146,32 @@
|
|||||||
margin-top: 0.25rem;
|
margin-top: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.h-41 {
|
.rpm-corr {
|
||||||
|
margin-top: -1.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h-40px {
|
||||||
|
height: 40px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Todo check if this is needed
|
||||||
|
.h-41px {
|
||||||
display: block;
|
display: block;
|
||||||
height: 41px !important;
|
height: 41px !important;
|
||||||
}
|
}
|
||||||
|
// end check
|
||||||
|
|
||||||
|
.w320px {
|
||||||
|
width: 320px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rm-outline:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.max-512px {
|
||||||
|
max-width: 512px !important;
|
||||||
|
}
|
||||||
|
|
||||||
.navigation-text-color {
|
.navigation-text-color {
|
||||||
color: v.$navigation-maincolor;
|
color: v.$navigation-maincolor;
|
||||||
@@ -123,6 +215,11 @@
|
|||||||
width: 32px !important;
|
width: 32px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.i40x40 {
|
||||||
|
height: 40px !important;
|
||||||
|
width: 40px !important;
|
||||||
|
}
|
||||||
|
|
||||||
.height60 {
|
.height60 {
|
||||||
height: 60px !important;
|
height: 60px !important;
|
||||||
}
|
}
|
||||||
@@ -147,6 +244,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.clipx {
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.strength500 {
|
.strength500 {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
@@ -278,6 +379,14 @@
|
|||||||
border: 1px solid v.$gray-2;
|
border: 1px solid v.$gray-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mt-minus-1 {
|
||||||
|
margin-top: -0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-minus-p3 {
|
||||||
|
margin-top: -0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
.ps-2rem {
|
.ps-2rem {
|
||||||
padding-left: 2rem !important;
|
padding-left: 2rem !important;
|
||||||
}
|
}
|
||||||
@@ -328,6 +437,7 @@
|
|||||||
|
|
||||||
.pager {
|
.pager {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
// min-width: 13.4rem;
|
||||||
|
|
||||||
* {
|
* {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@@ -337,6 +447,14 @@
|
|||||||
opacity: 25%;
|
opacity: 25%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
opacity: 75%;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:not([disabled]):hover {
|
||||||
|
opacity: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.pager-input {
|
.pager-input {
|
||||||
width: 54px;
|
width: 54px;
|
||||||
height: 38px;
|
height: 38px;
|
||||||
@@ -428,6 +546,9 @@
|
|||||||
|
|
||||||
.termin-offset-up-wide {
|
.termin-offset-up-wide {
|
||||||
top: 85px !important;
|
top: 85px !important;
|
||||||
|
max-height: calc(
|
||||||
|
100vh - 130px + 10px
|
||||||
|
) !important; // correction due to bigger panel size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -436,6 +557,10 @@
|
|||||||
margin-right: 2rem !important;
|
margin-right: 2rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.me-2rem {
|
||||||
|
margin-right: 2rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
.mx-max-md-2rem {
|
.mx-max-md-2rem {
|
||||||
@media (max-width: v.$layout-breakpoint-medium) {
|
@media (max-width: v.$layout-breakpoint-medium) {
|
||||||
margin-left: 2rem !important;
|
margin-left: 2rem !important;
|
||||||
@@ -475,7 +600,7 @@
|
|||||||
.txt {
|
.txt {
|
||||||
font-size: 0.75rem !important;
|
font-size: 0.75rem !important;
|
||||||
}
|
}
|
||||||
$padding-wide: 3rem;
|
$padding-wide: 2rem;
|
||||||
img.rsmzk {
|
img.rsmzk {
|
||||||
height: 30px;
|
height: 30px;
|
||||||
padding-left: $padding-wide;
|
padding-left: $padding-wide;
|
||||||
|
|||||||
@@ -241,13 +241,21 @@ html {
|
|||||||
|
|
||||||
border: none;
|
border: none;
|
||||||
background: none;
|
background: none;
|
||||||
|
|
||||||
|
@media screen and (min-width: v.$layout-breakpoint-medium) {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.pdf {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-button-keyboard {
|
.search-button-keyboard {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
margin: 0 20px;
|
margin: 0 20px;
|
||||||
width: 30px;
|
// width: 30px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
line-height: 10px;
|
line-height: 10px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -312,6 +320,7 @@ html {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.menutxt {
|
.menutxt {
|
||||||
|
margin-left: 0.5rem;
|
||||||
display: flex !important;
|
display: flex !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,7 +430,7 @@ html {
|
|||||||
|
|
||||||
.search-button-keyboard {
|
.search-button-keyboard {
|
||||||
margin: 0 20px;
|
margin: 0 20px;
|
||||||
width: 25px;
|
// width: 25px;
|
||||||
line-height: 10px;
|
line-height: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -429,7 +438,7 @@ html {
|
|||||||
|
|
||||||
.descriptionArea {
|
.descriptionArea {
|
||||||
width: 48rem;
|
width: 48rem;
|
||||||
padding: 5rem 2rem;
|
padding: 5rem 2rem 3.5rem; // add specific bottom padding to align with footer padding
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,7 @@
|
|||||||
|
|
||||||
/*responsive*/
|
/*responsive*/
|
||||||
|
|
||||||
@media (max-width: 1192px) {
|
@media (max-width: v.$layout-breakpoint-x-large) {
|
||||||
.table-users thead {
|
.table-users thead {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -109,6 +109,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#page-results {
|
||||||
|
td {
|
||||||
|
min-width: 32px;
|
||||||
|
&.tdata-area {
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.tdata-translation {
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.buttons-group {
|
||||||
|
min-width: 48px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#add-area {
|
#add-area {
|
||||||
height: 41px;
|
height: 41px;
|
||||||
padding: 10px 40px;
|
padding: 10px 40px;
|
||||||
|
|||||||
@@ -41,7 +41,15 @@ body {
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
color: #46535b;
|
color: #46535b;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
display: block;
|
// display: block;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
// gap: 0.5rem;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
a:visited {
|
a:visited {
|
||||||
|
|||||||
@@ -6,6 +6,11 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pager-container {
|
||||||
|
display: flex;
|
||||||
|
width: 130px;
|
||||||
|
}
|
||||||
|
|
||||||
.comments-container {
|
.comments-container {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -173,7 +178,7 @@ form.comment-form::after {
|
|||||||
|
|
||||||
.comments-top-hr {
|
.comments-top-hr {
|
||||||
color: #b6bec4;
|
color: #b6bec4;
|
||||||
opacity: 0.7;
|
opacity: 0.8;
|
||||||
margin-top: 0.063rem;
|
margin-top: 0.063rem;
|
||||||
margin-bottom: 0rem;
|
margin-bottom: 0rem;
|
||||||
height: 10%;
|
height: 10%;
|
||||||
@@ -354,7 +359,7 @@ form.comment-reply-form button {
|
|||||||
|
|
||||||
.comments-top-hr {
|
.comments-top-hr {
|
||||||
color: #b6bec4;
|
color: #b6bec4;
|
||||||
opacity: 0.7;
|
opacity: 0.8;
|
||||||
margin-top: 0.63rem;
|
margin-top: 0.63rem;
|
||||||
margin-bottom: 0rem;
|
margin-bottom: 0rem;
|
||||||
height: 10%;
|
height: 10%;
|
||||||
@@ -450,7 +455,7 @@ form.comment-reply-form button {
|
|||||||
.comments-holder {
|
.comments-holder {
|
||||||
padding-left: 16px;
|
padding-left: 16px;
|
||||||
}
|
}
|
||||||
@media only screen and (min-width: 1200px) {
|
@media only screen and (min-width: v.$layout-breakpoint-x-large) {
|
||||||
.comments-holder {
|
.comments-holder {
|
||||||
padding-left: 36px;
|
padding-left: 36px;
|
||||||
}
|
}
|
||||||
|
|||||||