summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.drone.yml48
-rw-r--r--.gitignore3
-rw-r--r--apps/dav/l10n/cs.js1
-rw-r--r--apps/dav/l10n/cs.json1
-rw-r--r--apps/dav/l10n/de.js1
-rw-r--r--apps/dav/l10n/de.json1
-rw-r--r--apps/dav/l10n/de_DE.js1
-rw-r--r--apps/dav/l10n/de_DE.json1
-rw-r--r--apps/dav/l10n/es.js1
-rw-r--r--apps/dav/l10n/es.json1
-rw-r--r--apps/dav/l10n/fi.js1
-rw-r--r--apps/dav/l10n/fi.json1
-rw-r--r--apps/dav/l10n/fr.js1
-rw-r--r--apps/dav/l10n/fr.json1
-rw-r--r--apps/dav/l10n/he.js1
-rw-r--r--apps/dav/l10n/he.json1
-rw-r--r--apps/dav/l10n/is.js10
-rw-r--r--apps/dav/l10n/is.json10
-rw-r--r--apps/dav/l10n/it.js1
-rw-r--r--apps/dav/l10n/it.json1
-rw-r--r--apps/dav/l10n/pt_BR.js1
-rw-r--r--apps/dav/l10n/pt_BR.json1
-rw-r--r--apps/dav/l10n/ru.js3
-rw-r--r--apps/dav/l10n/ru.json3
-rw-r--r--apps/dav/l10n/sr.js1
-rw-r--r--apps/dav/l10n/sr.json1
-rw-r--r--apps/dav/l10n/tr.js1
-rw-r--r--apps/dav/l10n/tr.json1
-rw-r--r--apps/dav/lib/CalDAV/Schedule/IMipPlugin.php2
-rw-r--r--apps/federatedfilesharing/l10n/is.js4
-rw-r--r--apps/federatedfilesharing/l10n/is.json4
-rw-r--r--apps/files/l10n/is.js3
-rw-r--r--apps/files/l10n/is.json3
-rw-r--r--apps/files_external/l10n/is.js3
-rw-r--r--apps/files_external/l10n/is.json3
-rw-r--r--apps/files_sharing/l10n/ru.js5
-rw-r--r--apps/files_sharing/l10n/ru.json5
-rw-r--r--apps/files_trashbin/l10n/is.js2
-rw-r--r--apps/files_trashbin/l10n/is.json2
-rw-r--r--apps/sharebymail/l10n/is.js4
-rw-r--r--apps/sharebymail/l10n/is.json4
-rw-r--r--apps/sharebymail/l10n/ru.js3
-rw-r--r--apps/sharebymail/l10n/ru.json3
-rw-r--r--apps/systemtags/l10n/is.js1
-rw-r--r--apps/systemtags/l10n/is.json1
-rw-r--r--apps/theming/css/theming.scss13
-rw-r--r--apps/theming/l10n/is.js2
-rw-r--r--apps/theming/l10n/is.json2
-rw-r--r--core/css/apps.scss36
-rw-r--r--core/css/guest.css7
-rw-r--r--core/css/mobile.scss37
-rw-r--r--core/css/styles.scss2
-rw-r--r--core/img/actions/caret-white.svg1
-rw-r--r--core/l10n/is.js9
-rw-r--r--core/l10n/is.json9
-rw-r--r--core/templates/installation.php2
-rw-r--r--core/templates/layout.base.php6
-rw-r--r--core/templates/layout.public.php8
-rw-r--r--core/templates/layout.user.php6
-rw-r--r--lib/l10n/ru.js3
-rw-r--r--lib/l10n/ru.json3
-rw-r--r--lib/private/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDao.php28
-rw-r--r--lib/private/DB/MigrationService.php72
-rw-r--r--lib/private/Log.php3
-rw-r--r--tests/lib/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDaoTest.php19
-rw-r--r--tests/lib/DB/MigrationsTest.php351
-rw-r--r--tests/ui-regression/config.js58
-rw-r--r--tests/ui-regression/helper.js256
-rw-r--r--tests/ui-regression/out/index.html219
-rw-r--r--tests/ui-regression/package.json21
-rw-r--r--tests/ui-regression/runTests.js129
-rw-r--r--tests/ui-regression/test/appsSpec.js60
-rw-r--r--tests/ui-regression/test/filesSpec.js102
-rw-r--r--tests/ui-regression/test/installSpec.js76
-rw-r--r--tests/ui-regression/test/loginSpec.js81
-rw-r--r--tests/ui-regression/test/publicSpec.js102
-rw-r--r--tests/ui-regression/test/settingsSpec.js76
77 files changed, 1843 insertions, 107 deletions
diff --git a/.drone.yml b/.drone.yml
index 709079bac01..da877a95d50 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -703,6 +703,32 @@ pipeline:
when:
matrix:
TEST: memcache-redis-cluster
+ ui-regression:
+ image: nextcloudci/ui-regression:ui-regression-1
+ commands:
+ - cd tests/ui-regression
+ - npm install
+ - chown -R pptruser out node_modules
+ - bash -c "until curl -s http://ui-regression-php-master > /dev/null && curl -s http://ui-regression-php > /dev/null; do sleep 2; done"
+ - sudo -u pptruser node runTests.js || true
+ - echo "The result can be found at https://s3.bitgrid.net/nextcloud-ui-regression/nextcloud/server/${DRONE_PULL_REQUEST}/index.html"
+ shm_size: '1gb'
+ when:
+ matrix:
+ TESTS: ui-regression
+ publish-s3:
+ image: plugins/s3
+ endpoint: https://ci-assets.nextcloud.com
+ bucket: nextcloud-ui-regression
+ path_style: true
+ source: tests/ui-regression/out/**/*
+ strip_prefix: tests/ui-regression/out/
+ acl: public-read
+ target: ${DRONE_REPO_OWNER}/${DRONE_REPO_NAME}/${DRONE_PULL_REQUEST}
+ secrets: [ aws_access_key_id, aws_secret_access_key ]
+ when:
+ matrix:
+ TESTS: ui-regression
matrix:
include:
- TESTS: checkers
@@ -848,6 +874,7 @@ matrix:
ENABLE_REDIS_CLUSTER: true
- TESTS: sqlite-php7.0-webdav-apache
ENABLE_REDIS: true
+ - TESTS: ui-regression
services:
cache:
@@ -855,6 +882,27 @@ services:
when:
matrix:
ENABLE_REDIS: true
+ ui-regression-php:
+ image: nextcloudci/server:server-1
+ commands:
+ - . /etc/apache2/envvars
+ - rm -fr /var/www/html
+ - mkdir /var/www/html && cp -rT . /var/www/html && chown -R www-data:www-data /var/www/html
+ - rm -fr /var/www/html/config/config.php /var/www/html/data/*
+ - apache2 -DFOREGROUND
+ when:
+ matrix:
+ TESTS: ui-regression
+ ui-regression-php-master:
+ image: nextcloudci/server:server-1
+ commands:
+ - . /etc/apache2/envvars
+ - rm -fr /var/www/html/config/config.php /var/www/html/data/*
+ - su www-data -c "cd /var/www/html/ && git checkout $DRONE_REPO_BRANCH && git pull && git submodule update"
+ - apache2 -DFOREGROUND
+ when:
+ matrix:
+ TESTS: ui-regression
cache-cluster:
image: morrisjobke/redis-cluster
when:
diff --git a/.gitignore b/.gitignore
index a11e3a14597..f8721e67fa8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -139,6 +139,9 @@ Vagrantfile
/tests/autotest*
/tests/data/lorem-copy.txt
/tests/data/testimage-copy.png
+/tests/ui-regression/out/
+/tests/ui-regression/node_modules/
+/tests/ui-regression/package-lock.json
/config/config-autotest-backup.php
/config/autoconfig.php
clover.xml
diff --git a/apps/dav/l10n/cs.js b/apps/dav/l10n/cs.js
index 47d75da2dcc..0ca13f39764 100644
--- a/apps/dav/l10n/cs.js
+++ b/apps/dav/l10n/cs.js
@@ -56,7 +56,6 @@ OC.L10N.register(
"Link:" : "Odkaz:",
"Accept" : "Přijmout",
"Decline" : "Zamítnout",
- "More options ..." : "Další volby…",
"More options at %s" : "Další volby viz %s",
"Contacts" : "Kontakty",
"WebDAV" : "WebDAV",
diff --git a/apps/dav/l10n/cs.json b/apps/dav/l10n/cs.json
index 19ff64ef641..92d1f46cb6c 100644
--- a/apps/dav/l10n/cs.json
+++ b/apps/dav/l10n/cs.json
@@ -54,7 +54,6 @@
"Link:" : "Odkaz:",
"Accept" : "Přijmout",
"Decline" : "Zamítnout",
- "More options ..." : "Další volby…",
"More options at %s" : "Další volby viz %s",
"Contacts" : "Kontakty",
"WebDAV" : "WebDAV",
diff --git a/apps/dav/l10n/de.js b/apps/dav/l10n/de.js
index d7af0e5f459..c561d013d9e 100644
--- a/apps/dav/l10n/de.js
+++ b/apps/dav/l10n/de.js
@@ -56,7 +56,6 @@ OC.L10N.register(
"Link:" : "Link:",
"Accept" : "Akzeptieren",
"Decline" : "Ablehnen",
- "More options ..." : "Weitere Optionen…",
"More options at %s" : "Weitere Optionen unter %s",
"Contacts" : "Kontakte",
"WebDAV" : "WebDAV",
diff --git a/apps/dav/l10n/de.json b/apps/dav/l10n/de.json
index c4161d1d1da..29207859463 100644
--- a/apps/dav/l10n/de.json
+++ b/apps/dav/l10n/de.json
@@ -54,7 +54,6 @@
"Link:" : "Link:",
"Accept" : "Akzeptieren",
"Decline" : "Ablehnen",
- "More options ..." : "Weitere Optionen…",
"More options at %s" : "Weitere Optionen unter %s",
"Contacts" : "Kontakte",
"WebDAV" : "WebDAV",
diff --git a/apps/dav/l10n/de_DE.js b/apps/dav/l10n/de_DE.js
index 4edd79558a8..8ee5379da5b 100644
--- a/apps/dav/l10n/de_DE.js
+++ b/apps/dav/l10n/de_DE.js
@@ -56,7 +56,6 @@ OC.L10N.register(
"Link:" : "Link:",
"Accept" : "Akzeptieren",
"Decline" : "Ablehnen",
- "More options ..." : "Weitere Optionen…",
"More options at %s" : "Weitere Optionen unter %s",
"Contacts" : "Kontakte",
"WebDAV" : "WebDAV",
diff --git a/apps/dav/l10n/de_DE.json b/apps/dav/l10n/de_DE.json
index 72c60c0c6e6..f911db3397b 100644
--- a/apps/dav/l10n/de_DE.json
+++ b/apps/dav/l10n/de_DE.json
@@ -54,7 +54,6 @@
"Link:" : "Link:",
"Accept" : "Akzeptieren",
"Decline" : "Ablehnen",
- "More options ..." : "Weitere Optionen…",
"More options at %s" : "Weitere Optionen unter %s",
"Contacts" : "Kontakte",
"WebDAV" : "WebDAV",
diff --git a/apps/dav/l10n/es.js b/apps/dav/l10n/es.js
index 398741d9626..8682f7989c5 100644
--- a/apps/dav/l10n/es.js
+++ b/apps/dav/l10n/es.js
@@ -56,7 +56,6 @@ OC.L10N.register(
"Link:" : "Enlace:",
"Accept" : "Aceptar",
"Decline" : "Rechazar",
- "More options ..." : "Más opciones...",
"More options at %s" : "Más opciones en %s",
"Contacts" : "Contactos",
"WebDAV" : "WebDAV",
diff --git a/apps/dav/l10n/es.json b/apps/dav/l10n/es.json
index d31347608a3..a54a0ccb687 100644
--- a/apps/dav/l10n/es.json
+++ b/apps/dav/l10n/es.json
@@ -54,7 +54,6 @@
"Link:" : "Enlace:",
"Accept" : "Aceptar",
"Decline" : "Rechazar",
- "More options ..." : "Más opciones...",
"More options at %s" : "Más opciones en %s",
"Contacts" : "Contactos",
"WebDAV" : "WebDAV",
diff --git a/apps/dav/l10n/fi.js b/apps/dav/l10n/fi.js
index ed39f71a505..6252698be9f 100644
--- a/apps/dav/l10n/fi.js
+++ b/apps/dav/l10n/fi.js
@@ -55,7 +55,6 @@ OC.L10N.register(
"Link:" : "Linkki:",
"Accept" : "Hyväksy",
"Decline" : "Kieltäydy",
- "More options ..." : "Lisää valintoja...",
"Contacts" : "Yhteystiedot",
"WebDAV" : "WebDAV",
"Technical details" : "Tekniset yksityiskohdat",
diff --git a/apps/dav/l10n/fi.json b/apps/dav/l10n/fi.json
index b2780a9f9b7..59783251223 100644
--- a/apps/dav/l10n/fi.json
+++ b/apps/dav/l10n/fi.json
@@ -53,7 +53,6 @@
"Link:" : "Linkki:",
"Accept" : "Hyväksy",
"Decline" : "Kieltäydy",
- "More options ..." : "Lisää valintoja...",
"Contacts" : "Yhteystiedot",
"WebDAV" : "WebDAV",
"Technical details" : "Tekniset yksityiskohdat",
diff --git a/apps/dav/l10n/fr.js b/apps/dav/l10n/fr.js
index 93814bf53fa..c342b48809d 100644
--- a/apps/dav/l10n/fr.js
+++ b/apps/dav/l10n/fr.js
@@ -56,7 +56,6 @@ OC.L10N.register(
"Link:" : "Lien :",
"Accept" : "Accepter",
"Decline" : "Refuser",
- "More options ..." : "Plus d'options ...",
"More options at %s" : "Plus d'options à %s",
"Contacts" : "Contacts",
"WebDAV" : "WebDAV",
diff --git a/apps/dav/l10n/fr.json b/apps/dav/l10n/fr.json
index 6cb5367b1ab..4d63b38bc14 100644
--- a/apps/dav/l10n/fr.json
+++ b/apps/dav/l10n/fr.json
@@ -54,7 +54,6 @@
"Link:" : "Lien :",
"Accept" : "Accepter",
"Decline" : "Refuser",
- "More options ..." : "Plus d'options ...",
"More options at %s" : "Plus d'options à %s",
"Contacts" : "Contacts",
"WebDAV" : "WebDAV",
diff --git a/apps/dav/l10n/he.js b/apps/dav/l10n/he.js
index 2f91c82d3e5..cb0fb86580d 100644
--- a/apps/dav/l10n/he.js
+++ b/apps/dav/l10n/he.js
@@ -55,7 +55,6 @@ OC.L10N.register(
"Link:" : "קישור:",
"Accept" : "קבלה",
"Decline" : "דחייה",
- "More options ..." : "אפשרויות נוספות…",
"More options at %s" : "אפשרויות נוספים ב־%s",
"Contacts" : "אנשי קשר",
"WebDAV" : "WebDAV",
diff --git a/apps/dav/l10n/he.json b/apps/dav/l10n/he.json
index 5b46ea6e0a0..1af827194e9 100644
--- a/apps/dav/l10n/he.json
+++ b/apps/dav/l10n/he.json
@@ -53,7 +53,6 @@
"Link:" : "קישור:",
"Accept" : "קבלה",
"Decline" : "דחייה",
- "More options ..." : "אפשרויות נוספות…",
"More options at %s" : "אפשרויות נוספים ב־%s",
"Contacts" : "אנשי קשר",
"WebDAV" : "WebDAV",
diff --git a/apps/dav/l10n/is.js b/apps/dav/l10n/is.js
index 6debe2959d7..6ccb5ebf10f 100644
--- a/apps/dav/l10n/is.js
+++ b/apps/dav/l10n/is.js
@@ -54,11 +54,21 @@ OC.L10N.register(
"Where:" : "Hvar:",
"Description:" : "Lýsing:",
"Link:" : "Tengill:",
+ "Accept" : "Samþykkja",
+ "Decline" : "Hafna",
+ "More options at %s" : "Fleiri valkostir á %s",
"Contacts" : "Tengiliðir",
"WebDAV" : "WebDAV",
+ "WebDAV endpoint" : "WebDAV-endapunktur",
"Technical details" : "Tæknilegar upplýsingar",
"Remote Address: %s" : "Fjartengt vistfang: %s",
"Request ID: %s" : "Beiðni um auðkenni: %s",
+ "There was an error updating your attendance status." : "Það kom upp villa við að uppfæra mætingarstöðu þína.",
+ "Please contact the organizer directly." : "Hafðu samband beint við skipuleggjendurna.",
+ "Are you accepting the invitation?" : "Ætlar þú að samþykkja boðið?",
+ "Tentative" : "Bráðabirgða",
+ "Save" : "Vista",
+ "Your attendance was updated successfully." : "Mætingarstaða þín var uppfærð.",
"CalDAV server" : "CalDAV-þjónn",
"Send invitations to attendees" : "Senda boð til þátttakenda",
"Please make sure to properly set up the email settings above." : "Gakktu úr skugga um að tölvupóststillingarnar hér fyrir ofan séu réttar.",
diff --git a/apps/dav/l10n/is.json b/apps/dav/l10n/is.json
index 639569b55b6..f322721d096 100644
--- a/apps/dav/l10n/is.json
+++ b/apps/dav/l10n/is.json
@@ -52,11 +52,21 @@
"Where:" : "Hvar:",
"Description:" : "Lýsing:",
"Link:" : "Tengill:",
+ "Accept" : "Samþykkja",
+ "Decline" : "Hafna",
+ "More options at %s" : "Fleiri valkostir á %s",
"Contacts" : "Tengiliðir",
"WebDAV" : "WebDAV",
+ "WebDAV endpoint" : "WebDAV-endapunktur",
"Technical details" : "Tæknilegar upplýsingar",
"Remote Address: %s" : "Fjartengt vistfang: %s",
"Request ID: %s" : "Beiðni um auðkenni: %s",
+ "There was an error updating your attendance status." : "Það kom upp villa við að uppfæra mætingarstöðu þína.",
+ "Please contact the organizer directly." : "Hafðu samband beint við skipuleggjendurna.",
+ "Are you accepting the invitation?" : "Ætlar þú að samþykkja boðið?",
+ "Tentative" : "Bráðabirgða",
+ "Save" : "Vista",
+ "Your attendance was updated successfully." : "Mætingarstaða þín var uppfærð.",
"CalDAV server" : "CalDAV-þjónn",
"Send invitations to attendees" : "Senda boð til þátttakenda",
"Please make sure to properly set up the email settings above." : "Gakktu úr skugga um að tölvupóststillingarnar hér fyrir ofan séu réttar.",
diff --git a/apps/dav/l10n/it.js b/apps/dav/l10n/it.js
index 10fa534a9d6..09014fb964a 100644
--- a/apps/dav/l10n/it.js
+++ b/apps/dav/l10n/it.js
@@ -56,7 +56,6 @@ OC.L10N.register(
"Link:" : "Collegamento:",
"Accept" : "Accetta",
"Decline" : "Rifiuta",
- "More options ..." : "Altre opzioni...",
"More options at %s" : "Altre opzioni alle %s",
"Contacts" : "Contatti",
"WebDAV" : "WebDAV",
diff --git a/apps/dav/l10n/it.json b/apps/dav/l10n/it.json
index eee0dddcf95..dc23d1acb68 100644
--- a/apps/dav/l10n/it.json
+++ b/apps/dav/l10n/it.json
@@ -54,7 +54,6 @@
"Link:" : "Collegamento:",
"Accept" : "Accetta",
"Decline" : "Rifiuta",
- "More options ..." : "Altre opzioni...",
"More options at %s" : "Altre opzioni alle %s",
"Contacts" : "Contatti",
"WebDAV" : "WebDAV",
diff --git a/apps/dav/l10n/pt_BR.js b/apps/dav/l10n/pt_BR.js
index 9a14b215132..c0466b446fd 100644
--- a/apps/dav/l10n/pt_BR.js
+++ b/apps/dav/l10n/pt_BR.js
@@ -56,7 +56,6 @@ OC.L10N.register(
"Link:" : "Link:",
"Accept" : "Aceitar",
"Decline" : "Rejeitar",
- "More options ..." : "Mais opções...",
"More options at %s" : "Mais opções em %s",
"Contacts" : "Contatos",
"WebDAV" : "WebDAV",
diff --git a/apps/dav/l10n/pt_BR.json b/apps/dav/l10n/pt_BR.json
index 867977e00d1..5da36df3a7a 100644
--- a/apps/dav/l10n/pt_BR.json
+++ b/apps/dav/l10n/pt_BR.json
@@ -54,7 +54,6 @@
"Link:" : "Link:",
"Accept" : "Aceitar",
"Decline" : "Rejeitar",
- "More options ..." : "Mais opções...",
"More options at %s" : "Mais opções em %s",
"Contacts" : "Contatos",
"WebDAV" : "WebDAV",
diff --git a/apps/dav/l10n/ru.js b/apps/dav/l10n/ru.js
index 92f93511080..fdb23b25eab 100644
--- a/apps/dav/l10n/ru.js
+++ b/apps/dav/l10n/ru.js
@@ -56,7 +56,7 @@ OC.L10N.register(
"Link:" : "Ссылка:",
"Accept" : "Принять",
"Decline" : "Отклонить",
- "More options ..." : "Дополнительные параметры…",
+ "More options at %s" : "Дополнительные параметры на %s",
"Contacts" : "Контакты",
"WebDAV" : "WebDAV",
"WebDAV endpoint" : "Конечная точка WebDAV",
@@ -64,6 +64,7 @@ OC.L10N.register(
"Remote Address: %s" : "Удаленный адрес: %s",
"Request ID: %s" : "ID запроса: %s",
"There was an error updating your attendance status." : "Ошибка обновления статуса участия.",
+ "Please contact the organizer directly." : "Обратитесь к организатору напрямую.",
"Are you accepting the invitation?" : "Принять приглашение?",
"Tentative" : "Под вопросом",
"Save" : "Сохранить",
diff --git a/apps/dav/l10n/ru.json b/apps/dav/l10n/ru.json
index d0711a252cf..49732d79aac 100644
--- a/apps/dav/l10n/ru.json
+++ b/apps/dav/l10n/ru.json
@@ -54,7 +54,7 @@
"Link:" : "Ссылка:",
"Accept" : "Принять",
"Decline" : "Отклонить",
- "More options ..." : "Дополнительные параметры…",
+ "More options at %s" : "Дополнительные параметры на %s",
"Contacts" : "Контакты",
"WebDAV" : "WebDAV",
"WebDAV endpoint" : "Конечная точка WebDAV",
@@ -62,6 +62,7 @@
"Remote Address: %s" : "Удаленный адрес: %s",
"Request ID: %s" : "ID запроса: %s",
"There was an error updating your attendance status." : "Ошибка обновления статуса участия.",
+ "Please contact the organizer directly." : "Обратитесь к организатору напрямую.",
"Are you accepting the invitation?" : "Принять приглашение?",
"Tentative" : "Под вопросом",
"Save" : "Сохранить",
diff --git a/apps/dav/l10n/sr.js b/apps/dav/l10n/sr.js
index ce3292345ec..3c599f90ee8 100644
--- a/apps/dav/l10n/sr.js
+++ b/apps/dav/l10n/sr.js
@@ -56,7 +56,6 @@ OC.L10N.register(
"Link:" : "Веза:",
"Accept" : "Прихвати",
"Decline" : "Одбиј",
- "More options ..." : "Још опција...",
"More options at %s" : "Још опција на %s",
"Contacts" : "Контакти",
"WebDAV" : "ВебДАВ",
diff --git a/apps/dav/l10n/sr.json b/apps/dav/l10n/sr.json
index 2f9951b0e4d..1fb5b174b21 100644
--- a/apps/dav/l10n/sr.json
+++ b/apps/dav/l10n/sr.json
@@ -54,7 +54,6 @@
"Link:" : "Веза:",
"Accept" : "Прихвати",
"Decline" : "Одбиј",
- "More options ..." : "Још опција...",
"More options at %s" : "Још опција на %s",
"Contacts" : "Контакти",
"WebDAV" : "ВебДАВ",
diff --git a/apps/dav/l10n/tr.js b/apps/dav/l10n/tr.js
index 23661f0be06..bd4985b6cd8 100644
--- a/apps/dav/l10n/tr.js
+++ b/apps/dav/l10n/tr.js
@@ -56,7 +56,6 @@ OC.L10N.register(
"Link:" : "Bağlantı:",
"Accept" : "Onayla",
"Decline" : "Reddet",
- "More options ..." : "Diğer seçenekler...",
"More options at %s" : "%s üzerindeki diğer seçenekler",
"Contacts" : "Kişiler",
"WebDAV" : "WebDAV",
diff --git a/apps/dav/l10n/tr.json b/apps/dav/l10n/tr.json
index 6deedf9e462..d898d2d92e2 100644
--- a/apps/dav/l10n/tr.json
+++ b/apps/dav/l10n/tr.json
@@ -54,7 +54,6 @@
"Link:" : "Bağlantı:",
"Accept" : "Onayla",
"Decline" : "Reddet",
- "More options ..." : "Diğer seçenekler...",
"More options at %s" : "%s üzerindeki diğer seçenekler",
"Contacts" : "Kişiler",
"WebDAV" : "WebDAV",
diff --git a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php
index 4065c21b8a1..54feaa57ace 100644
--- a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php
+++ b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php
@@ -501,7 +501,7 @@ class IMipPlugin extends SabreIMipPlugin {
'token' => $token,
]);
$html = vsprintf('<small><a href="%s">%s</a></small>', [
- $moreOptionsURL, $l10n->t('More options ...')
+ $moreOptionsURL, $l10n->t('More options …')
]);
$text = $l10n->t('More options at %s', [$moreOptionsURL]);
diff --git a/apps/federatedfilesharing/l10n/is.js b/apps/federatedfilesharing/l10n/is.js
index f871eec5a6c..2cce749fff5 100644
--- a/apps/federatedfilesharing/l10n/is.js
+++ b/apps/federatedfilesharing/l10n/is.js
@@ -18,6 +18,7 @@ OC.L10N.register(
"Couldn't establish a federated share, maybe the password was wrong." : "Gat ekki bætt við skýjasambandssameign, hugsanlega var lykilorðið ekki rétt.",
"Federated Share request sent, you will receive an invitation. Check your notifications." : "Sendi beiðni um skýjasambandssameign, þú munt fá boðskort. Athugaðu skilaboð til þín.",
"Couldn't establish a federated share, it looks like the server to federate with is too old (Nextcloud <= 9)." : "Gat ekki bætt við skýjasambandssameign, það lítur út fyrir að þjónninn sem á að koma sambandi við sé of gamall (Nextcloud <= 9).",
+ "It is not allowed to send federated group shares from this server." : "Ekki er heimilt að senda skýjasambandssameign fyrir hópa af þessum þjóni.",
"Sharing %s failed, because this item is already shared with %s" : "Deiling %s mistókst, því þessu atriði er þegar deilt með %s",
"Not allowed to create a federated share with the same user" : "Ekki er heimilt að búa til skýjasambandssameign með sama notanda",
"File is already shared with %s" : "Skránni er þegar deilt með %s",
@@ -33,11 +34,14 @@ OC.L10N.register(
"Share with me through my #Nextcloud Federated Cloud ID" : "Deila með mér í gegnum víðværa skýjasambandsauðkennið mitt #Nextcloud Federated Cloud ID",
"Sharing" : "Deiling",
"Federated file sharing" : "Deiling skráa milli þjóna (skýjasambandssameign)",
+ "Provide federated file sharing across servers" : "Sér um deilingu skráa milli skýjaþjóna (skýjasambandssameign)",
"Federated Cloud Sharing" : "Deiling með skýjasambandi",
"Open documentation" : "Opna hjálparskjöl",
"Adjust how people can share between servers." : "Stilltu hvernig fólk getur deilt á milli þjóna.",
"Allow users on this server to send shares to other servers" : "Leyfa notendum á þessum þjóni að senda sameignir til annarra þjóna",
"Allow users on this server to receive shares from other servers" : "Leyfa notendum á þessum þjóni að taka á móti sameignum frá öðrum þjónum",
+ "Allow users on this server to send shares to groups on other servers" : "Leyfa notendum á þessum þjóni að senda sameignir til hópa á öðrum þjónum",
+ "Allow users on this server to receive group shares from other servers" : "Leyfa notendum á þessum þjóni að taka á móti hópsameignum frá öðrum þjónum",
"Search global and public address book for users" : "Leita að notendum í víðværri og opinberri vistfangaskrá",
"Allow users to publish their data to a global and public address book" : "Leifa notendum að birta gögnin sín í víðværri og opinberri vistfangaskrá",
"Federated Cloud" : "Skýjasamband (federated)",
diff --git a/apps/federatedfilesharing/l10n/is.json b/apps/federatedfilesharing/l10n/is.json
index 7a634292c02..457be9c38bf 100644
--- a/apps/federatedfilesharing/l10n/is.json
+++ b/apps/federatedfilesharing/l10n/is.json
@@ -16,6 +16,7 @@
"Couldn't establish a federated share, maybe the password was wrong." : "Gat ekki bætt við skýjasambandssameign, hugsanlega var lykilorðið ekki rétt.",
"Federated Share request sent, you will receive an invitation. Check your notifications." : "Sendi beiðni um skýjasambandssameign, þú munt fá boðskort. Athugaðu skilaboð til þín.",
"Couldn't establish a federated share, it looks like the server to federate with is too old (Nextcloud <= 9)." : "Gat ekki bætt við skýjasambandssameign, það lítur út fyrir að þjónninn sem á að koma sambandi við sé of gamall (Nextcloud <= 9).",
+ "It is not allowed to send federated group shares from this server." : "Ekki er heimilt að senda skýjasambandssameign fyrir hópa af þessum þjóni.",
"Sharing %s failed, because this item is already shared with %s" : "Deiling %s mistókst, því þessu atriði er þegar deilt með %s",
"Not allowed to create a federated share with the same user" : "Ekki er heimilt að búa til skýjasambandssameign með sama notanda",
"File is already shared with %s" : "Skránni er þegar deilt með %s",
@@ -31,11 +32,14 @@
"Share with me through my #Nextcloud Federated Cloud ID" : "Deila með mér í gegnum víðværa skýjasambandsauðkennið mitt #Nextcloud Federated Cloud ID",
"Sharing" : "Deiling",
"Federated file sharing" : "Deiling skráa milli þjóna (skýjasambandssameign)",
+ "Provide federated file sharing across servers" : "Sér um deilingu skráa milli skýjaþjóna (skýjasambandssameign)",
"Federated Cloud Sharing" : "Deiling með skýjasambandi",
"Open documentation" : "Opna hjálparskjöl",
"Adjust how people can share between servers." : "Stilltu hvernig fólk getur deilt á milli þjóna.",
"Allow users on this server to send shares to other servers" : "Leyfa notendum á þessum þjóni að senda sameignir til annarra þjóna",
"Allow users on this server to receive shares from other servers" : "Leyfa notendum á þessum þjóni að taka á móti sameignum frá öðrum þjónum",
+ "Allow users on this server to send shares to groups on other servers" : "Leyfa notendum á þessum þjóni að senda sameignir til hópa á öðrum þjónum",
+ "Allow users on this server to receive group shares from other servers" : "Leyfa notendum á þessum þjóni að taka á móti hópsameignum frá öðrum þjónum",
"Search global and public address book for users" : "Leita að notendum í víðværri og opinberri vistfangaskrá",
"Allow users to publish their data to a global and public address book" : "Leifa notendum að birta gögnin sín í víðværri og opinberri vistfangaskrá",
"Federated Cloud" : "Skýjasamband (federated)",
diff --git a/apps/files/l10n/is.js b/apps/files/l10n/is.js
index b3c8397807b..a443fe7cbb6 100644
--- a/apps/files/l10n/is.js
+++ b/apps/files/l10n/is.js
@@ -126,6 +126,7 @@ OC.L10N.register(
"A file or folder has been <strong>restored</strong>" : "Skjal eða mappa hefur verið <strong>endurheimt</strong>",
"Unlimited" : "Ótakmarkað",
"Upload (max. %s)" : "Senda inn (hám. %s)",
+ "File Management" : "Skráastjórnun",
"File handling" : "Meðhöndlun skráar",
"Maximum upload size" : "Hámarksstærð innsendingar",
"max. possible: " : "hámark mögulegt: ",
@@ -149,9 +150,11 @@ OC.L10N.register(
"Files and folders you mark as favorite will show up here" : "Skrár og möppur sem þú merkir sem eftirlæti birtast hér",
"Tags" : "Merki",
"Deleted files" : "Eyddar skrár",
+ "Shares" : "Sameignir",
"Shared with others" : "Deilt með öðrum",
"Shared with you" : "Deilt með þér",
"Shared by link" : "Deilt með tengli",
+ "Deleted shares" : "Eyddar sameignir",
"Text file" : "Textaskrá",
"New text file.txt" : "Ný textaskrá.txt",
"Move" : "Færa",
diff --git a/apps/files/l10n/is.json b/apps/files/l10n/is.json
index 923537bde4d..c462cac3214 100644
--- a/apps/files/l10n/is.json
+++ b/apps/files/l10n/is.json
@@ -124,6 +124,7 @@
"A file or folder has been <strong>restored</strong>" : "Skjal eða mappa hefur verið <strong>endurheimt</strong>",
"Unlimited" : "Ótakmarkað",
"Upload (max. %s)" : "Senda inn (hám. %s)",
+ "File Management" : "Skráastjórnun",
"File handling" : "Meðhöndlun skráar",
"Maximum upload size" : "Hámarksstærð innsendingar",
"max. possible: " : "hámark mögulegt: ",
@@ -147,9 +148,11 @@
"Files and folders you mark as favorite will show up here" : "Skrár og möppur sem þú merkir sem eftirlæti birtast hér",
"Tags" : "Merki",
"Deleted files" : "Eyddar skrár",
+ "Shares" : "Sameignir",
"Shared with others" : "Deilt með öðrum",
"Shared with you" : "Deilt með þér",
"Shared by link" : "Deilt með tengli",
+ "Deleted shares" : "Eyddar sameignir",
"Text file" : "Textaskrá",
"New text file.txt" : "Ný textaskrá.txt",
"Move" : "Færa",
diff --git a/apps/files_external/l10n/is.js b/apps/files_external/l10n/is.js
index e03005b36ce..5a4f8fd7d89 100644
--- a/apps/files_external/l10n/is.js
+++ b/apps/files_external/l10n/is.js
@@ -70,6 +70,8 @@ OC.L10N.register(
"User entered, store in database" : "Innskráður notandi, geyma í gagnagrunni",
"RSA public key" : "RSA-dreifilykill",
"Public key" : "Dreifilykill",
+ "RSA private key" : "RSA-einkalykill",
+ "Private key" : "Einkalykill",
"Amazon S3" : "Amazon S3",
"Bucket" : "Bucket",
"Hostname" : "Vélarheiti",
@@ -102,6 +104,7 @@ OC.L10N.register(
"The FTP support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it." : "Stuðningur við FTP í PHP er ekki virkjaður eða ekki uppsettur. Tenging %s í skráakerfi er ekki möguleg. Biddu kerfisstjórann þinn um að setja þetta upp.",
"\"%s\" is not installed. Mounting of %s is not possible. Please ask your system administrator to install it." : "\"%s\" er ekki uppsett. Tenging %s í skráakerfi er ekki möguleg. Biddu kerfisstjórann þinn um að setja þetta upp.",
"External storage support" : "Stuðningur við utanaðkomandi gagnageymslur",
+ "Adds basic external storage support" : "Bætir við grunnstuðningi fyrir utanaðkomandi gagnageymslur",
"No external storage configured or you don't have the permission to configure them" : "Engin utanaðkomandi gagnageymsla er uppsett eða að þú hefur ekki heimild til að stilla slíkt",
"Name" : "Nafn",
"Storage type" : "Tegund gagnageymslu",
diff --git a/apps/files_external/l10n/is.json b/apps/files_external/l10n/is.json
index 8a3ba2d4b3d..5cf67687066 100644
--- a/apps/files_external/l10n/is.json
+++ b/apps/files_external/l10n/is.json
@@ -68,6 +68,8 @@
"User entered, store in database" : "Innskráður notandi, geyma í gagnagrunni",
"RSA public key" : "RSA-dreifilykill",
"Public key" : "Dreifilykill",
+ "RSA private key" : "RSA-einkalykill",
+ "Private key" : "Einkalykill",
"Amazon S3" : "Amazon S3",
"Bucket" : "Bucket",
"Hostname" : "Vélarheiti",
@@ -100,6 +102,7 @@
"The FTP support in PHP is not enabled or installed. Mounting of %s is not possible. Please ask your system administrator to install it." : "Stuðningur við FTP í PHP er ekki virkjaður eða ekki uppsettur. Tenging %s í skráakerfi er ekki möguleg. Biddu kerfisstjórann þinn um að setja þetta upp.",
"\"%s\" is not installed. Mounting of %s is not possible. Please ask your system administrator to install it." : "\"%s\" er ekki uppsett. Tenging %s í skráakerfi er ekki möguleg. Biddu kerfisstjórann þinn um að setja þetta upp.",
"External storage support" : "Stuðningur við utanaðkomandi gagnageymslur",
+ "Adds basic external storage support" : "Bætir við grunnstuðningi fyrir utanaðkomandi gagnageymslur",
"No external storage configured or you don't have the permission to configure them" : "Engin utanaðkomandi gagnageymsla er uppsett eða að þú hefur ekki heimild til að stilla slíkt",
"Name" : "Nafn",
"Storage type" : "Tegund gagnageymslu",
diff --git a/apps/files_sharing/l10n/ru.js b/apps/files_sharing/l10n/ru.js
index dc9b90527a8..3d11f25b6ad 100644
--- a/apps/files_sharing/l10n/ru.js
+++ b/apps/files_sharing/l10n/ru.js
@@ -13,8 +13,11 @@ OC.L10N.register(
"No shared links" : "Нет общедоступных ссылок",
"Files and folders you share by link will show up here" : "Здесь появятся файлы и каталоги, ссылкой на которые вы поделитесь",
"No deleted shares" : "Нет удаленных общих ресурсов",
+ "Shares you deleted will show up here" : "Здесь будут показаны удалённые вами общие ресурсы",
"No shares" : "Нет ресурсов общего доступа",
+ "Shares will show up here" : "Здесь будут показаны общие ресурсы",
"Restore share" : "Восстановить ресурсы общего доступа",
+ "Something happened. Unable to restore the share." : "Не удалось восстановить общий ресурс.",
"You can upload into this folder" : "Вы можете загружать в этот каталог",
"No compatible server found at {remote}" : "Не найден совместимый сервер на {remote}",
"Invalid server URL" : "Неверный URL сервера",
@@ -84,6 +87,7 @@ OC.L10N.register(
"Public upload is only possible for publicly shared folders" : "Общедоступная загрузка возможна только в общедоступные папки",
"Invalid date, date format must be YYYY-MM-DD" : "Неверная дата, формат даты должен быть ГГГГ-ММ-ДД",
"Sharing %s failed because the back end does not allow shares from type %s" : "Не удалось предоставить общий доступ к «%s», поскольку механизм удалённого обмена не разрешает публикации типа %s",
+ "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Не удалось отправить пароль для доступа к «%s»: приложение Nextcloud Talk отключено.",
"You cannot share to a Circle if the app is not enabled" : "Вы не можете поделиться с кругом, если приложение «Круг» не включено",
"Please specify a valid circle" : "Укажите верный круг",
"Unknown share type" : "Общий доступ неизвестного типа",
@@ -91,6 +95,7 @@ OC.L10N.register(
"Could not lock path" : "Не удалось заблокировать путь",
"Wrong or no update parameter given" : "Параметр для изменения неправилен или не задан",
"Can't change permissions for public share links" : "Для общедоступных ссылок изменение прав невозможно",
+ "Sharing sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Не удалось отправить пароль для доступа: приложение Nextcloud Talk отключено.",
"Cannot increase permissions" : "Не удалось повысить права доступа",
"shared by %s" : "доступ предоставлен пользователем %s",
"Download" : "Скачать",
diff --git a/apps/files_sharing/l10n/ru.json b/apps/files_sharing/l10n/ru.json
index f75e9fb406f..15c166444ea 100644
--- a/apps/files_sharing/l10n/ru.json
+++ b/apps/files_sharing/l10n/ru.json
@@ -11,8 +11,11 @@
"No shared links" : "Нет общедоступных ссылок",
"Files and folders you share by link will show up here" : "Здесь появятся файлы и каталоги, ссылкой на которые вы поделитесь",
"No deleted shares" : "Нет удаленных общих ресурсов",
+ "Shares you deleted will show up here" : "Здесь будут показаны удалённые вами общие ресурсы",
"No shares" : "Нет ресурсов общего доступа",
+ "Shares will show up here" : "Здесь будут показаны общие ресурсы",
"Restore share" : "Восстановить ресурсы общего доступа",
+ "Something happened. Unable to restore the share." : "Не удалось восстановить общий ресурс.",
"You can upload into this folder" : "Вы можете загружать в этот каталог",
"No compatible server found at {remote}" : "Не найден совместимый сервер на {remote}",
"Invalid server URL" : "Неверный URL сервера",
@@ -82,6 +85,7 @@
"Public upload is only possible for publicly shared folders" : "Общедоступная загрузка возможна только в общедоступные папки",
"Invalid date, date format must be YYYY-MM-DD" : "Неверная дата, формат даты должен быть ГГГГ-ММ-ДД",
"Sharing %s failed because the back end does not allow shares from type %s" : "Не удалось предоставить общий доступ к «%s», поскольку механизм удалённого обмена не разрешает публикации типа %s",
+ "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Не удалось отправить пароль для доступа к «%s»: приложение Nextcloud Talk отключено.",
"You cannot share to a Circle if the app is not enabled" : "Вы не можете поделиться с кругом, если приложение «Круг» не включено",
"Please specify a valid circle" : "Укажите верный круг",
"Unknown share type" : "Общий доступ неизвестного типа",
@@ -89,6 +93,7 @@
"Could not lock path" : "Не удалось заблокировать путь",
"Wrong or no update parameter given" : "Параметр для изменения неправилен или не задан",
"Can't change permissions for public share links" : "Для общедоступных ссылок изменение прав невозможно",
+ "Sharing sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Не удалось отправить пароль для доступа: приложение Nextcloud Talk отключено.",
"Cannot increase permissions" : "Не удалось повысить права доступа",
"shared by %s" : "доступ предоставлен пользователем %s",
"Download" : "Скачать",
diff --git a/apps/files_trashbin/l10n/is.js b/apps/files_trashbin/l10n/is.js
index 39374bd8eb6..b98d738845e 100644
--- a/apps/files_trashbin/l10n/is.js
+++ b/apps/files_trashbin/l10n/is.js
@@ -11,11 +11,13 @@ OC.L10N.register(
"This operation is forbidden" : "Þessi aðgerð er bönnuð",
"This directory is unavailable, please check the logs or contact the administrator" : "Þessi mappa er ekki tiltæk, athugaðu atvikaskrár eða hafðu samband við kerfissjóra",
"restored" : "endurheimt",
+ "This application enables users to restore files that were deleted from the system." : "Þetta forrit gerir notendum kleift að endurheimta skrár sem eytt hefur verið af kerfinu.",
"No deleted files" : "Engar eyddar skrár",
"You will be able to recover deleted files from here" : "Þú getur endurheimt eyddar skrár héðan",
"No entries found in this folder" : "Engar skrár fundust í þessari möppu",
"Select all" : "Velja allt",
"Name" : "Heiti",
+ "Actions" : "Aðgerðir",
"Deleted" : "Eytt"
},
"nplurals=2; plural=(n % 10 != 1 || n % 100 == 11);");
diff --git a/apps/files_trashbin/l10n/is.json b/apps/files_trashbin/l10n/is.json
index e90c9a3cfb6..ac4fcdf2fd2 100644
--- a/apps/files_trashbin/l10n/is.json
+++ b/apps/files_trashbin/l10n/is.json
@@ -9,11 +9,13 @@
"This operation is forbidden" : "Þessi aðgerð er bönnuð",
"This directory is unavailable, please check the logs or contact the administrator" : "Þessi mappa er ekki tiltæk, athugaðu atvikaskrár eða hafðu samband við kerfissjóra",
"restored" : "endurheimt",
+ "This application enables users to restore files that were deleted from the system." : "Þetta forrit gerir notendum kleift að endurheimta skrár sem eytt hefur verið af kerfinu.",
"No deleted files" : "Engar eyddar skrár",
"You will be able to recover deleted files from here" : "Þú getur endurheimt eyddar skrár héðan",
"No entries found in this folder" : "Engar skrár fundust í þessari möppu",
"Select all" : "Velja allt",
"Name" : "Heiti",
+ "Actions" : "Aðgerðir",
"Deleted" : "Eytt"
},"pluralForm" :"nplurals=2; plural=(n % 10 != 1 || n % 100 == 11);"
} \ No newline at end of file
diff --git a/apps/sharebymail/l10n/is.js b/apps/sharebymail/l10n/is.js
index 87b18aa9981..10ad778f443 100644
--- a/apps/sharebymail/l10n/is.js
+++ b/apps/sharebymail/l10n/is.js
@@ -29,6 +29,10 @@ OC.L10N.register(
"Password to access »%s« shared to you by %s" : "Lykilorð fyrir aðgang að »%s« deilt með þér af %s",
"Password to access »%s«" : "Lykilorð fyrir aðgang að »%s«",
"It is protected with the following password: %s" : "Það er varið með eftirfarandi lykilorði: %s",
+ "%1$s shared »%2$s« with you and wants to add:" : "%1$s deildi »%2$s« með þér og vill bæta við:",
+ "%1$s shared »%2$s« with you and wants to add" : "%1$s deildi »%2$s« með þér og vill bæta við",
+ "»%s« added a note to a file shared with you" : "»%s« bætti minnispunkti við skrá sem deilt er með þér",
+ "%1$s via %2$s" : "%1$s með %2$s",
"You just shared »%s« with %s. The share was already send to the recipient. Due to the security policies defined by the administrator of %s each share needs to be protected by password and it is not allowed to send the password directly to the recipient. Therefore you need to forward the password manually to the recipient." : "Þú varst í þessu að deila »%s« með %s. Sameignin var þegar send til viðtakandans. Vegna öryggisskilmála sem skilgreindir hafa verið af kerfisstjóra %s þarf hver sameign að vera varin með lykilorði og að ekki er leyfilegt að senda það lykilorð beint til viðtakandans. Því er nauðsynlegt að þú homir lykilorðinu beint til sjálfs viðtakandans.",
"Password to access »%s« shared with %s" : "Lykilorð fyrir aðgang að »%s« deilt með %s",
"This is the password: %s" : "Þetta er lykilorðið: %s",
diff --git a/apps/sharebymail/l10n/is.json b/apps/sharebymail/l10n/is.json
index f1fc6f3f886..ee3701d9802 100644
--- a/apps/sharebymail/l10n/is.json
+++ b/apps/sharebymail/l10n/is.json
@@ -27,6 +27,10 @@
"Password to access »%s« shared to you by %s" : "Lykilorð fyrir aðgang að »%s« deilt með þér af %s",
"Password to access »%s«" : "Lykilorð fyrir aðgang að »%s«",
"It is protected with the following password: %s" : "Það er varið með eftirfarandi lykilorði: %s",
+ "%1$s shared »%2$s« with you and wants to add:" : "%1$s deildi »%2$s« með þér og vill bæta við:",
+ "%1$s shared »%2$s« with you and wants to add" : "%1$s deildi »%2$s« með þér og vill bæta við",
+ "»%s« added a note to a file shared with you" : "»%s« bætti minnispunkti við skrá sem deilt er með þér",
+ "%1$s via %2$s" : "%1$s með %2$s",
"You just shared »%s« with %s. The share was already send to the recipient. Due to the security policies defined by the administrator of %s each share needs to be protected by password and it is not allowed to send the password directly to the recipient. Therefore you need to forward the password manually to the recipient." : "Þú varst í þessu að deila »%s« með %s. Sameignin var þegar send til viðtakandans. Vegna öryggisskilmála sem skilgreindir hafa verið af kerfisstjóra %s þarf hver sameign að vera varin með lykilorði og að ekki er leyfilegt að senda það lykilorð beint til viðtakandans. Því er nauðsynlegt að þú homir lykilorðinu beint til sjálfs viðtakandans.",
"Password to access »%s« shared with %s" : "Lykilorð fyrir aðgang að »%s« deilt með %s",
"This is the password: %s" : "Þetta er lykilorðið: %s",
diff --git a/apps/sharebymail/l10n/ru.js b/apps/sharebymail/l10n/ru.js
index 6bbf738c787..7868b7f033f 100644
--- a/apps/sharebymail/l10n/ru.js
+++ b/apps/sharebymail/l10n/ru.js
@@ -29,6 +29,9 @@ OC.L10N.register(
"Password to access »%s« shared to you by %s" : "Пароль для «%s», общий доступ к которому предоставлен Вам пользователем %s",
"Password to access »%s«" : "Пароль для доступа к «%s»",
"It is protected with the following password: %s" : "Доступ защищён следующим паролем: %s",
+ "%1$s shared »%2$s« with you and wants to add:" : "%1$s предоставил(а) вам доступ к «%2$s» и хочет добавить:",
+ "%1$s shared »%2$s« with you and wants to add" : "%1$s предоставил(а) вам доступ к «%2$s» и хочет добавить",
+ "»%s« added a note to a file shared with you" : "%s добавил(а) примечание к файлу, к которому вам открыт доступ",
"%1$s via %2$s" : "%1$s через %2$s",
"You just shared »%s« with %s. The share was already send to the recipient. Due to the security policies defined by the administrator of %s each share needs to be protected by password and it is not allowed to send the password directly to the recipient. Therefore you need to forward the password manually to the recipient." : "Вы только что предоставили общий доступ к «%s» пользователю %s. Уведомление о предоставлении доступа было отправлено получателю. В соответствии с политиками безопасности, заданными администратором %s, каждый общий ресурс должен быть защищён паролем, а так же не допускается непосредственное отправление пароля получателю, поэтому Вам потребуется самостоятельно перенаправить получателю пароль для доступа.",
"Password to access »%s« shared with %s" : "Паролем для доступа к «%s» поделились с %s",
diff --git a/apps/sharebymail/l10n/ru.json b/apps/sharebymail/l10n/ru.json
index 61d43b845f8..4e11c190692 100644
--- a/apps/sharebymail/l10n/ru.json
+++ b/apps/sharebymail/l10n/ru.json
@@ -27,6 +27,9 @@
"Password to access »%s« shared to you by %s" : "Пароль для «%s», общий доступ к которому предоставлен Вам пользователем %s",
"Password to access »%s«" : "Пароль для доступа к «%s»",
"It is protected with the following password: %s" : "Доступ защищён следующим паролем: %s",
+ "%1$s shared »%2$s« with you and wants to add:" : "%1$s предоставил(а) вам доступ к «%2$s» и хочет добавить:",
+ "%1$s shared »%2$s« with you and wants to add" : "%1$s предоставил(а) вам доступ к «%2$s» и хочет добавить",
+ "»%s« added a note to a file shared with you" : "%s добавил(а) примечание к файлу, к которому вам открыт доступ",
"%1$s via %2$s" : "%1$s через %2$s",
"You just shared »%s« with %s. The share was already send to the recipient. Due to the security policies defined by the administrator of %s each share needs to be protected by password and it is not allowed to send the password directly to the recipient. Therefore you need to forward the password manually to the recipient." : "Вы только что предоставили общий доступ к «%s» пользователю %s. Уведомление о предоставлении доступа было отправлено получателю. В соответствии с политиками безопасности, заданными администратором %s, каждый общий ресурс должен быть защищён паролем, а так же не допускается непосредственное отправление пароля получателю, поэтому Вам потребуется самостоятельно перенаправить получателю пароль для доступа.",
"Password to access »%s« shared with %s" : "Паролем для доступа к «%s» поделились с %s",
diff --git a/apps/systemtags/l10n/is.js b/apps/systemtags/l10n/is.js
index 19a988902a0..659fb56c8d3 100644
--- a/apps/systemtags/l10n/is.js
+++ b/apps/systemtags/l10n/is.js
@@ -43,6 +43,7 @@ OC.L10N.register(
"<strong>System tags</strong> for a file have been modified" : "<strong>Kerfismerkjum</strong> á skrá hefur verið breytt",
"Collaborative tags" : "Samstarfsmerkingar",
"Select tag …" : "Veldu merki ...",
+ "Create a new tag" : "Búa til nýtt merki",
"Name" : "Heiti",
"Public" : "Opinbert",
"Restricted" : "Takmarkað",
diff --git a/apps/systemtags/l10n/is.json b/apps/systemtags/l10n/is.json
index ece3ff46ae7..8db4fb15fd0 100644
--- a/apps/systemtags/l10n/is.json
+++ b/apps/systemtags/l10n/is.json
@@ -41,6 +41,7 @@
"<strong>System tags</strong> for a file have been modified" : "<strong>Kerfismerkjum</strong> á skrá hefur verið breytt",
"Collaborative tags" : "Samstarfsmerkingar",
"Select tag …" : "Veldu merki ...",
+ "Create a new tag" : "Búa til nýtt merki",
"Name" : "Heiti",
"Public" : "Opinbert",
"Restricted" : "Takmarkað",
diff --git a/apps/theming/css/theming.scss b/apps/theming/css/theming.scss
index 4a32458d8a8..9e55680470c 100644
--- a/apps/theming/css/theming.scss
+++ b/apps/theming/css/theming.scss
@@ -27,16 +27,19 @@
filter: none;
}
.searchbox input[type="search"] {
- background: transparent url('../../../core/img/actions/search.svg') no-repeat 6px center;
+ background-repeat: no-repeat;
+ background-position: 6px center;
+ background-color: transparent;
+ @include icon-color('search', 'actions', $color-black, 1, true);
}
#contactsmenu .icon-contacts {
- background-image: url('../../../core/img/places/contacts-dark.svg');
+ @include icon-color('contacts', 'places', $color-black, 1, true);
}
#settings .icon-settings-white {
- background-image: url('../../../core/img/actions/settings-dark.svg');
+ @include icon-color('settings', 'actions', $color-black, 1, true);
}
#appmenu .icon-more-white {
- background-image: url('../../../core/img/actions/more.svg');
+ @include icon-color('more', 'actions', $color-black, 1, true);
}
#body-login {
@@ -64,8 +67,8 @@
}
input[type='checkbox'].checkbox--white:checked + label:before {
border-color: nc-darken($color-primary-element, 30%) !important;
- background-image: url('../../../core/img/actions/checkbox-mark.svg');
background-color: nc-darken($color-primary-element, 30%) !important;
+ @include icon-color('checkbox-mark', 'actions', $color-white, 1, true);
}
}
} @else {
diff --git a/apps/theming/l10n/is.js b/apps/theming/l10n/is.js
index 6b9ef6d80ea..5402cc7d9ee 100644
--- a/apps/theming/l10n/is.js
+++ b/apps/theming/l10n/is.js
@@ -24,6 +24,8 @@ OC.L10N.register(
"You are already using a custom theme. Theming app settings might be overwritten by that." : "Þú ert nú þegar að nota sérsniðið þema. Mögulega gæti það skrifað yfir stillingar í þemaforriti.",
"Theming" : "Þemu",
"Legal notice" : "Lagaleg atriði",
+ "Privacy policy" : "Stefna um meðferð persónulegra gagna",
+ "Adjust the Nextcloud theme" : "Laga Nextcloud þema",
"Theming makes it possible to easily customize the look and feel of your instance and supported clients. This will be visible for all users." : "Þemu gera þér kleift að breyta útliti og hegðun þíns eintaks af viðmótinu auk studdra biðlaraforrita. Það verður sýnilegt öllum notendum.",
"Name" : "Heiti",
"Reset to default" : "Endurstilla á sjálfgefið",
diff --git a/apps/theming/l10n/is.json b/apps/theming/l10n/is.json
index 68d1ef6c198..6b213c567ee 100644
--- a/apps/theming/l10n/is.json
+++ b/apps/theming/l10n/is.json
@@ -22,6 +22,8 @@
"You are already using a custom theme. Theming app settings might be overwritten by that." : "Þú ert nú þegar að nota sérsniðið þema. Mögulega gæti það skrifað yfir stillingar í þemaforriti.",
"Theming" : "Þemu",
"Legal notice" : "Lagaleg atriði",
+ "Privacy policy" : "Stefna um meðferð persónulegra gagna",
+ "Adjust the Nextcloud theme" : "Laga Nextcloud þema",
"Theming makes it possible to easily customize the look and feel of your instance and supported clients. This will be visible for all users." : "Þemu gera þér kleift að breyta útliti og hegðun þíns eintaks af viðmótinu auk studdra biðlaraforrita. Það verður sýnilegt öllum notendum.",
"Name" : "Heiti",
"Reset to default" : "Endurstilla á sjálfgefið",
diff --git a/core/css/apps.scss b/core/css/apps.scss
index d8b2def9e5f..02603562c6f 100644
--- a/core/css/apps.scss
+++ b/core/css/apps.scss
@@ -77,7 +77,7 @@ kbd {
position: fixed;
top: $header-height;
left: 0;
- z-index: 500;
+ z-index: 1000;
overflow-y: auto;
overflow-x: hidden;
// Do not use vh because of mobile headers
@@ -583,31 +583,25 @@ kbd {
/* CONTENT --------------------------------------------------------- */
-#content-wrapper {
- // everything not related to content but needs to be on the window
- // goes here (popups, tooltips...)
- position: relative;
- min-height: 100%;
- display: unset;
-}
#content {
box-sizing: border-box;
position: relative;
display: flex;
- margin-top: $header-height;
+ // padding is included in height
+ padding-top: $header-height;
min-height: 100%;
}
/* APP-CONTENT AND WRAPPER ------------------------------------------ */
/* Part where the content will be loaded into */
#app-content {
- z-index: 1000;
+ z-index: 500;
background-color: var(--color-main-background);
position: relative;
- min-height: 100%;
flex-basis: 100vw;
+ min-height: 100%;
/* margin if navigation element is here */
- #app-navigation + & {
+ #app-navigation + & {
margin-left: $navigation-width;
}
/* no top border for first settings item */
@@ -619,17 +613,10 @@ kbd {
#app-content-wrapper {
display: flex;
position: relative;
- align-items: start;
- .app-content-list,
- .app-content-detail {
- min-height: calc(100vh - #{$header-height});
- max-height: calc(100vh - #{$header-height});
- overflow-x: hidden;
- overflow-y: auto;
- }
+ align-items: stretch;
/* CONTENT DETAILS AFTER LIST*/
- .app-content-detail {
+ .app-content-details {
/* grow full width */
flex-grow: 1;
#app-navigation-toggle-back {
@@ -1049,10 +1036,17 @@ $popovericon-size: 16px;
/* CONTENT LIST ------------------------------------------------------------ */
.app-content-list {
width: 300px;
+ position: sticky;
+ top: $header-height;
border-right: 1px solid var(--color-border);
display: flex;
flex-direction: column;
transition: transform 250ms ease-in-out;
+ min-height: calc(100vh - #{$header-height});
+ max-height: calc(100vh - #{$header-height});
+ overflow-y: auto;
+ overflow-x: hidden;
+ flex: 0 0 300px;
/* Default item */
.app-content-list-item {
diff --git a/core/css/guest.css b/core/css/guest.css
index 75ad1a787da..422ceace5bc 100644
--- a/core/css/guest.css
+++ b/core/css/guest.css
@@ -454,13 +454,14 @@ form #selectDbType {
text-align:center;
white-space: nowrap;
margin: 0;
+ display: flex;
}
form #selectDbType .info {
white-space: normal;
}
form #selectDbType label {
- position: static;
- margin: 0 -3px 5px;
+ flex-grow: 1;
+ margin: 0 -1px 5px;
font-size: 12px;
background:#f8f8f8;
color:#888;
@@ -469,7 +470,7 @@ form #selectDbType label {
}
form #selectDbType label span {
cursor: pointer;
- padding: 10px 20px;
+ padding: 10px 17px;
}
form #selectDbType label.ui-state-hover,
form #selectDbType label.ui-state-active {
diff --git a/core/css/mobile.scss b/core/css/mobile.scss
index 93e2909a510..6330be9d399 100644
--- a/core/css/mobile.scss
+++ b/core/css/mobile.scss
@@ -35,19 +35,18 @@
/* full width for message list on mobile */
.app-content-list {
- width: 100%;
background: var(--color-main-background);
- position: relative;
- z-index: 100;
- }
-
- /* since list and content are only displayed full window size
- * we don't ant inner scrolling
- */
- #app-content-wrapper {
- .app-content-list,
- .app-content-detail {
- max-height: unset;
+ flex: 1 1 100%;
+ // make full height scroll since app-content-details is hidden
+ max-height: unset;
+ + .app-content-details {
+ display: none;
+ }
+ &.showdetails {
+ display: none;
+ + .app-content-details {
+ display: initial;
+ }
}
}
@@ -59,7 +58,7 @@
#app-navigation-toggle-back {
position: fixed;
display: inline-block !important;
- top: 45px;
+ top: $header-height;
left: 0;
width: 44px;
height: 44px;
@@ -73,18 +72,11 @@
transform: translateX(-100%);
}
- /* end of media query */
-}
- /* allow horizontal scrollbar in settings
- otherwise user management is not usable on mobile */
- #body-settings #app-content {
- overflow-x: auto !important;
}
#app-navigation-toggle {
position: fixed;
display: inline-block !important;
- top: $header-height;
left: 0;
width: 44px;
height: 44px;
@@ -132,11 +124,6 @@
max-width: 80%;
}
- /* fix controls bar jumping when navigation is slid out */
- .snapjs-left #app-navigation-toggle,
- .snapjs-left #controls {
- top: 0;
- }
.snapjs-left table.multiselect thead {
top: 44px;
}
diff --git a/core/css/styles.scss b/core/css/styles.scss
index 1269796b266..8996bee1cc3 100644
--- a/core/css/styles.scss
+++ b/core/css/styles.scss
@@ -818,7 +818,7 @@ span.ui-icon {
}
.content {
- max-height: calc(100% - $header-height);
+ max-height: calc(100% - #{$header-height});
height: 100%;
overflow-y: auto;
diff --git a/core/img/actions/caret-white.svg b/core/img/actions/caret-white.svg
new file mode 100644
index 00000000000..c97d6e26407
--- /dev/null
+++ b/core/img/actions/caret-white.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1" viewbox="0 0 16 16"><path d="M4 6l4 4 4-3.994z" fill="#fff"/></svg>
diff --git a/core/l10n/is.js b/core/l10n/is.js
index 57f4c189e1b..e6e27c7c64a 100644
--- a/core/l10n/is.js
+++ b/core/l10n/is.js
@@ -118,7 +118,13 @@ OC.L10N.register(
"Please check the <a target=\"_blank\" rel=\"noreferrer noopener\" href=\"{docLink}\">installation documentation ↗</a> for PHP configuration notes and the PHP configuration of your server, especially when using php-fpm." : "Endilega skoðaðu <a target=\"_blank\" rel=\"noreferrer noopener\" href=\"{docLink}\">hjálparskjöl uppsetningarinnar ↗</a> varðandi athugasemdir vegna uppsetningar PHP og sjálfa uppsetningu PHP-þjónsins, Sérstaklega ef þú notar php-fpm.",
"The read-only config has been enabled. This prevents setting some configurations via the web-interface. Furthermore, the file needs to be made writable manually for every update." : "Skrifvarða stillingaskráin hefur verið virkjuð. Þetta kemur í veg fyrir að hægt sé að sýsla með sumar stillingar í gegnum vefviðmótið. Að auki þarf þessi skrá að vera skrifanleg við hverja uppfærslu.",
"Your database does not run with \"READ COMMITTED\" transaction isolation level. This can cause problems when multiple actions are executed in parallel." : "Gagnagrunnurinn keyrir ekki með \"READ COMMITTED\" færsluaðgreiningarstiginu. Þetta getur valdið vandamálum þegar margar aðgerðir eru keyrðar í einu.",
+ "The PHP module \"fileinfo\" is missing. It is strongly recommended to enable this module to get the best results with MIME type detection." : "PHP-eininguna \"fileinfo\" vantar. Við mælum eindregið með notkun þessarar einingar til að fá bestu útkomu við greiningu á MIME-skráagerðum.",
+ "{name} below version {version} is installed, for stability and performance reasons it is recommended to update to a newer {name} version." : "{name} eldra en útgáfa {version} er uppsett, en vegna stöðugleika og afkasta mælum við með að útgáfa {name} verði sett upp.",
"Transactional file locking is disabled, this might lead to issues with race conditions. Enable \"filelocking.enabled\" in config.php to avoid these problems. See the <a target=\"_blank\" rel=\"noreferrer noopener\" href=\"{docLink}\">documentation ↗</a> for more information." : "Færslulæsing skráa (transactional file locking) er óvirk, þetta gæti leitt til vandamála út frá forgangsskilyrðum (race conditions). Virkjaðu 'filelocking.enabled' í config.php til að forðast slík vandamál. Skoðaðu <a target=\"_blank\" rel=\"noreferrer noopener\" href=\"{docLink}\">hjálparskjölin ↗</a> til að sjá nánari upplýsingar.",
+ "If your installation is not installed at the root of the domain and uses system cron, there can be issues with the URL generation. To avoid these problems, please set the \"overwrite.cli.url\" option in your config.php file to the webroot path of your installation (suggestion: \"{suggestedOverwriteCliURL}\")" : "Ef uppsetningin þín er ekki á rót lénsins og þú notar cron stýrikerfisins, þá geta komið upp vandamál við gerð URL-slóða. Til að forðast slík vandamál, skaltu stilla \"overwrite.cli.url\" valkostinn í config.php skránni þinni á slóð vefrótarinnar (webroot) í uppsetningunni (tillaga: \"{suggestedOverwriteCliURL}\")",
+ "It was not possible to execute the cron job via CLI. The following technical errors have appeared:" : "Ekki var hægt að keyra cron-verkið á skipanalínu. Eftirfarandi tæknilegar villur komu upp:",
+ "Last background job execution ran {relativeTime}. Something seems wrong." : "Síðasta keyrsla bakgrunnsverks var keyrt {relativeTime}. Eitthvað er ekki eins og það á að sér að vera.",
+ "Check the background job settings" : "Athugaðu stillingar bakgrunnsvinnslunnar",
"This server has no working Internet connection: Multiple endpoints could not be reached. This means that some of the features like mounting external storage, notifications about updates or installation of third-party apps will not work. Accessing files remotely and sending of notification emails might not work, either. Establish a connection from this server to the Internet to enjoy all features." : "Þessi þjónn er ekki með virka nettengingu: ekki náðis tenging við fjölmarga endapunkta. Þetta þýðir að sumir eiginleikar eins og að virkja ytri gagnageymslu, tilkynningar um uppfærslur eða uppsetningu á forritum þriðja aðila, mun ekki virka. Fjartengdur aðgangur að skrám og sending tilkynninga í tölvupósti virka líklega ekki heldur. Við leggjum til að internettenging sé virkjuð fyrir þennan vefþjón ef þú vilt hafa alla eiginleika tiltæka.",
"No memory cache has been configured. To enhance performance, please configure a memcache, if available. Further information can be found in the <a target=\"_blank\" rel=\"noreferrer noopener\" href=\"{docLink}\">documentation</a>." : "Ekkert skyndiminni (cache) hefur verið stillt. Til að auka afköst ættirðu að setja upp skyndiminni (með memcache) ef það er tiltækt. Hægt er að finna nánari upplýsingar um þetta í <a target=\"_blank\" rel=\"noreferrer noopener\" href=\"{docLink}\">hjálparskjölum</a> okkar.",
"/dev/urandom is not readable by PHP which is highly discouraged for security reasons. Further information can be found in the <a target=\"_blank\" rel=\"noreferrer noopener\" href=\"{docLink}\">documentation</a>." : "/dev/urandom er ekki lesanlegt af PHP sem er mjög óráðlegt af öryggisástæðum. Hægt er að finna nánari upplýsingar um þetta í <a target=\"_blank\" rel=\"noreferrer noopener\" href=\"{docLink}\">hjálparskjölum</a> okkar.",
@@ -137,6 +143,8 @@ OC.L10N.register(
"Your data directory and files are probably accessible from the Internet. The .htaccess file is not working. It is strongly recommended that you configure your web server so that the data directory is no longer accessible, or move the data directory outside the web server document root." : "Gagnamappan og skrárnar þínar eru líklega aðgengilegar öllum af internetinu vegna þess að .htaccess skrá er ekki virk. Við mælum eindregið með að þú stillir vefþjóninn þinn á þann hátt að gagnamappa er ekki lengur aðgengileg eða þú færir gagnamöppu út fyrir skjalarót vefþjóns.",
"The \"{header}\" HTTP header is not set to \"{expected}\". This is a potential security or privacy risk, as it is recommended to adjust this setting accordingly." : "\"{header}\" HTTP-hausinn er ekki stilltur á \"{expected}\". Þetta er möguleg áhætta varðandi öryggi og gagnaleynd, við mælum með því að laga þessa stillingu.",
"The \"{header}\" HTTP header is not set to \"{expected}\". Some features might not work correctly, as it is recommended to adjust this setting accordingly." : "\"{header}\" HTTP-hausinn er ekki stilltur á \"{expected}\". Einhverjir eiginleikar gætu virkað ekki rétt, við mælum með því að laga þessa stillingu.",
+ "The \"Strict-Transport-Security\" HTTP header is not set to at least \"{seconds}\" seconds. For enhanced security, it is recommended to enable HSTS as described in the <a href=\"{docUrl}\" rel=\"noreferrer noopener\">security tips ↗</a>." : "\"Strict-Transport-Security\" HTTP-hausinn er ekki stilltur á að minnsa kosti \"{seconds}\" sekúndur. Fyrir aukið öryggi mælum við með því að virkja HSTS eins og lýst er í <a href=\"{docUrl}\" rel=\"noreferrer noopener\">öryggisleiðbeiningum ↗ </a>.",
+ "Accessing site insecurely via HTTP. You are strongly adviced to set up your server to require HTTPS instead, as described in the <a href=\"{docUrl}\">security tips ↗</a>." : " Þú ert að tengjast þessu vefsvæði með HTTP. Við mælum eindregið með að þú stillir þjóninn á að krefjast HTTPS í staðinn eins og lýst er í <a href=\"{docUrl}\">öryggisleiðbeiningunum okkar ↗</a>.",
"Shared" : "Deilt",
"Shared with" : "Deilt með",
"Shared by" : "Deilt af",
@@ -160,6 +168,7 @@ OC.L10N.register(
"Set expiration date" : "Setja gildistíma",
"Expiration" : "Rennur út",
"Expiration date" : "Gildir til",
+ "Set share note" : "Setja minnispunkt á sameign",
"Share link" : "Deila tengli",
"Enable" : "Virkja",
"Shared with you and the group {group} by {owner}" : "Deilt með þér og hópnum {group} af {owner}",
diff --git a/core/l10n/is.json b/core/l10n/is.json
index 1f8abddd881..7b019cc3c4c 100644
--- a/core/l10n/is.json
+++ b/core/l10n/is.json
@@ -116,7 +116,13 @@
"Please check the <a target=\"_blank\" rel=\"noreferrer noopener\" href=\"{docLink}\">installation documentation ↗</a> for PHP configuration notes and the PHP configuration of your server, especially when using php-fpm." : "Endilega skoðaðu <a target=\"_blank\" rel=\"noreferrer noopener\" href=\"{docLink}\">hjálparskjöl uppsetningarinnar ↗</a> varðandi athugasemdir vegna uppsetningar PHP og sjálfa uppsetningu PHP-þjónsins, Sérstaklega ef þú notar php-fpm.",
"The read-only config has been enabled. This prevents setting some configurations via the web-interface. Furthermore, the file needs to be made writable manually for every update." : "Skrifvarða stillingaskráin hefur verið virkjuð. Þetta kemur í veg fyrir að hægt sé að sýsla með sumar stillingar í gegnum vefviðmótið. Að auki þarf þessi skrá að vera skrifanleg við hverja uppfærslu.",
"Your database does not run with \"READ COMMITTED\" transaction isolation level. This can cause problems when multiple actions are executed in parallel." : "Gagnagrunnurinn keyrir ekki með \"READ COMMITTED\" færsluaðgreiningarstiginu. Þetta getur valdið vandamálum þegar margar aðgerðir eru keyrðar í einu.",
+ "The PHP module \"fileinfo\" is missing. It is strongly recommended to enable this module to get the best results with MIME type detection." : "PHP-eininguna \"fileinfo\" vantar. Við mælum eindregið með notkun þessarar einingar til að fá bestu útkomu við greiningu á MIME-skráagerðum.",
+ "{name} below version {version} is installed, for stability and performance reasons it is recommended to update to a newer {name} version." : "{name} eldra en útgáfa {version} er uppsett, en vegna stöðugleika og afkasta mælum við með að útgáfa {name} verði sett upp.",
"Transactional file locking is disabled, this might lead to issues with race conditions. Enable \"filelocking.enabled\" in config.php to avoid these problems. See the <a target=\"_blank\" rel=\"noreferrer noopener\" href=\"{docLink}\">documentation ↗</a> for more information." : "Færslulæsing skráa (transactional file locking) er óvirk, þetta gæti leitt til vandamála út frá forgangsskilyrðum (race conditions). Virkjaðu 'filelocking.enabled' í config.php til að forðast slík vandamál. Skoðaðu <a target=\"_blank\" rel=\"noreferrer noopener\" href=\"{docLink}\">hjálparskjölin ↗</a> til að sjá nánari upplýsingar.",
+ "If your installation is not installed at the root of the domain and uses system cron, there can be issues with the URL generation. To avoid these problems, please set the \"overwrite.cli.url\" option in your config.php file to the webroot path of your installation (suggestion: \"{suggestedOverwriteCliURL}\")" : "Ef uppsetningin þín er ekki á rót lénsins og þú notar cron stýrikerfisins, þá geta komið upp vandamál við gerð URL-slóða. Til að forðast slík vandamál, skaltu stilla \"overwrite.cli.url\" valkostinn í config.php skránni þinni á slóð vefrótarinnar (webroot) í uppsetningunni (tillaga: \"{suggestedOverwriteCliURL}\")",
+ "It was not possible to execute the cron job via CLI. The following technical errors have appeared:" : "Ekki var hægt að keyra cron-verkið á skipanalínu. Eftirfarandi tæknilegar villur komu upp:",
+ "Last background job execution ran {relativeTime}. Something seems wrong." : "Síðasta keyrsla bakgrunnsverks var keyrt {relativeTime}. Eitthvað er ekki eins og það á að sér að vera.",
+ "Check the background job settings" : "Athugaðu stillingar bakgrunnsvinnslunnar",
"This server has no working Internet connection: Multiple endpoints could not be reached. This means that some of the features like mounting external storage, notifications about updates or installation of third-party apps will not work. Accessing files remotely and sending of notification emails might not work, either. Establish a connection from this server to the Internet to enjoy all features." : "Þessi þjónn er ekki með virka nettengingu: ekki náðis tenging við fjölmarga endapunkta. Þetta þýðir að sumir eiginleikar eins og að virkja ytri gagnageymslu, tilkynningar um uppfærslur eða uppsetningu á forritum þriðja aðila, mun ekki virka. Fjartengdur aðgangur að skrám og sending tilkynninga í tölvupósti virka líklega ekki heldur. Við leggjum til að internettenging sé virkjuð fyrir þennan vefþjón ef þú vilt hafa alla eiginleika tiltæka.",
"No memory cache has been configured. To enhance performance, please configure a memcache, if available. Further information can be found in the <a target=\"_blank\" rel=\"noreferrer noopener\" href=\"{docLink}\">documentation</a>." : "Ekkert skyndiminni (cache) hefur verið stillt. Til að auka afköst ættirðu að setja upp skyndiminni (með memcache) ef það er tiltækt. Hægt er að finna nánari upplýsingar um þetta í <a target=\"_blank\" rel=\"noreferrer noopener\" href=\"{docLink}\">hjálparskjölum</a> okkar.",
"/dev/urandom is not readable by PHP which is highly discouraged for security reasons. Further information can be found in the <a target=\"_blank\" rel=\"noreferrer noopener\" href=\"{docLink}\">documentation</a>." : "/dev/urandom er ekki lesanlegt af PHP sem er mjög óráðlegt af öryggisástæðum. Hægt er að finna nánari upplýsingar um þetta í <a target=\"_blank\" rel=\"noreferrer noopener\" href=\"{docLink}\">hjálparskjölum</a> okkar.",
@@ -135,6 +141,8 @@
"Your data directory and files are probably accessible from the Internet. The .htaccess file is not working. It is strongly recommended that you configure your web server so that the data directory is no longer accessible, or move the data directory outside the web server document root." : "Gagnamappan og skrárnar þínar eru líklega aðgengilegar öllum af internetinu vegna þess að .htaccess skrá er ekki virk. Við mælum eindregið með að þú stillir vefþjóninn þinn á þann hátt að gagnamappa er ekki lengur aðgengileg eða þú færir gagnamöppu út fyrir skjalarót vefþjóns.",
"The \"{header}\" HTTP header is not set to \"{expected}\". This is a potential security or privacy risk, as it is recommended to adjust this setting accordingly." : "\"{header}\" HTTP-hausinn er ekki stilltur á \"{expected}\". Þetta er möguleg áhætta varðandi öryggi og gagnaleynd, við mælum með því að laga þessa stillingu.",
"The \"{header}\" HTTP header is not set to \"{expected}\". Some features might not work correctly, as it is recommended to adjust this setting accordingly." : "\"{header}\" HTTP-hausinn er ekki stilltur á \"{expected}\". Einhverjir eiginleikar gætu virkað ekki rétt, við mælum með því að laga þessa stillingu.",
+ "The \"Strict-Transport-Security\" HTTP header is not set to at least \"{seconds}\" seconds. For enhanced security, it is recommended to enable HSTS as described in the <a href=\"{docUrl}\" rel=\"noreferrer noopener\">security tips ↗</a>." : "\"Strict-Transport-Security\" HTTP-hausinn er ekki stilltur á að minnsa kosti \"{seconds}\" sekúndur. Fyrir aukið öryggi mælum við með því að virkja HSTS eins og lýst er í <a href=\"{docUrl}\" rel=\"noreferrer noopener\">öryggisleiðbeiningum ↗ </a>.",
+ "Accessing site insecurely via HTTP. You are strongly adviced to set up your server to require HTTPS instead, as described in the <a href=\"{docUrl}\">security tips ↗</a>." : " Þú ert að tengjast þessu vefsvæði með HTTP. Við mælum eindregið með að þú stillir þjóninn á að krefjast HTTPS í staðinn eins og lýst er í <a href=\"{docUrl}\">öryggisleiðbeiningunum okkar ↗</a>.",
"Shared" : "Deilt",
"Shared with" : "Deilt með",
"Shared by" : "Deilt af",
@@ -158,6 +166,7 @@
"Set expiration date" : "Setja gildistíma",
"Expiration" : "Rennur út",
"Expiration date" : "Gildir til",
+ "Set share note" : "Setja minnispunkt á sameign",
"Share link" : "Deila tengli",
"Enable" : "Virkja",
"Shared with you and the group {group} by {owner}" : "Deilt með þér og hópnum {group} af {owner}",
diff --git a/core/templates/installation.php b/core/templates/installation.php
index 616ca1f47da..3fdde8da1c2 100644
--- a/core/templates/installation.php
+++ b/core/templates/installation.php
@@ -57,7 +57,7 @@ script('core', [
<?php if(!$_['directoryIsSet'] OR !$_['dbIsSet'] OR count($_['errors']) > 0): ?>
<fieldset id="advancedHeader">
- <legend><a id="showAdvanced"><?php p($l->t( 'Storage & database' )); ?> <img src="<?php print_unescaped(image_path('', 'actions/caret.svg')); ?>" /></a></legend>
+ <legend><a id="showAdvanced"><?php p($l->t( 'Storage & database' )); ?> <img src="<?php print_unescaped(image_path('', 'actions/caret-white.svg')); ?>" /></a></legend>
</fieldset>
<?php endif; ?>
diff --git a/core/templates/layout.base.php b/core/templates/layout.base.php
index 8bb8e8ba3ca..d38bd114c3e 100644
--- a/core/templates/layout.base.php
+++ b/core/templates/layout.base.php
@@ -18,10 +18,8 @@
</head>
<body id="body-public">
<?php include 'layout.noscript.warning.php'; ?>
- <div id="content-wrapper">
- <div id="content" class="app-public" role="main">
- <?php print_unescaped($_['content']); ?>
- </div>
+ <div id="content" class="app-public" role="main">
+ <?php print_unescaped($_['content']); ?>
</div>
</body>
</html>
diff --git a/core/templates/layout.public.php b/core/templates/layout.public.php
index afdd5656be4..07aff03127d 100644
--- a/core/templates/layout.public.php
+++ b/core/templates/layout.public.php
@@ -70,11 +70,9 @@
</div>
<?php } ?>
</header>
- <div id="content-wrapper">
- <div id="content" class="app-<?php p($_['appid']) ?>" role="main">
- <?php print_unescaped($_['content']); ?>
- </div>
- </div
+ <div id="content" class="app-<?php p($_['appid']) ?>" role="main">
+ <?php print_unescaped($_['content']); ?>
+ </div>
<?php if($template->getFooterVisible()) { ?>
<footer>
<p class="info"><?php print_unescaped($theme->getLongFooter()); ?></p>
diff --git a/core/templates/layout.user.php b/core/templates/layout.user.php
index fbd71ec825b..32385c37aea 100644
--- a/core/templates/layout.user.php
+++ b/core/templates/layout.user.php
@@ -159,10 +159,8 @@
<input class="confirm" value="<?php p($l->t('Confirm')); ?>" type="submit">
</form>
- <div id="content-wrapper">
- <div id="content" class="app-<?php p($_['appid']) ?>" role="main">
- <?php print_unescaped($_['content']); ?>
- </div>
+ <div id="content" class="app-<?php p($_['appid']) ?>" role="main">
+ <?php print_unescaped($_['content']); ?>
</div>
</body>
diff --git a/lib/l10n/ru.js b/lib/l10n/ru.js
index 383d33ded2f..a7572b9ffed 100644
--- a/lib/l10n/ru.js
+++ b/lib/l10n/ru.js
@@ -126,6 +126,9 @@ OC.L10N.register(
"Sharing %s failed, because resharing is not allowed" : "Не удалось поделиться %s, повторное открытие доступа запрещено",
"Sharing %s failed, because the sharing backend for %s could not find its source" : "Не удалось поделиться %s, бэкенд общего доступа не нашел путь до %s",
"Sharing %s failed, because the file could not be found in the file cache" : "Не удалось поделиться %s, элемент не найден в файловом кеше.",
+ "%1$s shared »%2$s« with you and wants to add:" : "%1$s предоставил(а) вам доступ к «%2$s» и хочет добавить:",
+ "%1$s shared »%2$s« with you and wants to add" : "%1$s предоставил(а) вам доступ к «%2$s» и хочет добавить",
+ "»%s« added a note to a file shared with you" : "%s добавил(а) примечание к файлу, к которому вам открыт доступ",
"Open »%s«" : "Открыть «%s»",
"%1$s via %2$s" : "%1$s через %2$s",
"Can’t increase permissions of %s" : "Невозможно увеличить права доступа для %s",
diff --git a/lib/l10n/ru.json b/lib/l10n/ru.json
index 6462e7f0afc..af7ad060884 100644
--- a/lib/l10n/ru.json
+++ b/lib/l10n/ru.json
@@ -124,6 +124,9 @@
"Sharing %s failed, because resharing is not allowed" : "Не удалось поделиться %s, повторное открытие доступа запрещено",
"Sharing %s failed, because the sharing backend for %s could not find its source" : "Не удалось поделиться %s, бэкенд общего доступа не нашел путь до %s",
"Sharing %s failed, because the file could not be found in the file cache" : "Не удалось поделиться %s, элемент не найден в файловом кеше.",
+ "%1$s shared »%2$s« with you and wants to add:" : "%1$s предоставил(а) вам доступ к «%2$s» и хочет добавить:",
+ "%1$s shared »%2$s« with you and wants to add" : "%1$s предоставил(а) вам доступ к «%2$s» и хочет добавить",
+ "»%s« added a note to a file shared with you" : "%s добавил(а) примечание к файлу, к которому вам открыт доступ",
"Open »%s«" : "Открыть «%s»",
"%1$s via %2$s" : "%1$s через %2$s",
"Can’t increase permissions of %s" : "Невозможно увеличить права доступа для %s",
diff --git a/lib/private/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDao.php b/lib/private/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDao.php
index 5b3f2849433..0e9cd8045cd 100644
--- a/lib/private/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDao.php
+++ b/lib/private/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDao.php
@@ -1,6 +1,6 @@
<?php
-declare(strict_types = 1);
+declare(strict_types=1);
/**
* @copyright 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
@@ -59,7 +59,7 @@ class ProviderUserAssignmentDao {
$result = $query->execute();
$providers = [];
foreach ($result->fetchAll() as $row) {
- $providers[$row['provider_id']] = 1 === (int) $row['enabled'];
+ $providers[$row['provider_id']] = 1 === (int)$row['enabled'];
}
$result->closeCursor();
@@ -72,15 +72,23 @@ class ProviderUserAssignmentDao {
public function persist(string $providerId, string $uid, int $enabled) {
$qb = $this->conn->getQueryBuilder();
- // TODO: concurrency? What if (providerId, uid) private key is inserted
- // twice at the same time?
- $query = $qb->insert(self::TABLE_NAME)->values([
- 'provider_id' => $qb->createNamedParameter($providerId),
- 'uid' => $qb->createNamedParameter($uid),
- 'enabled' => $qb->createNamedParameter($enabled, IQueryBuilder::PARAM_INT),
- ]);
+ // First, try to update an existing entry
+ $updateQuery = $qb->update(self::TABLE_NAME)
+ ->set('enabled', $qb->createNamedParameter($enabled))
+ ->where($qb->expr()->eq('provider_id', $qb->createNamedParameter($providerId)))
+ ->andWhere($qb->expr()->eq('uid', $qb->createNamedParameter($uid)));
+ $updatedRows = $updateQuery->execute();
- $query->execute();
+ // If this (providerId, UID) key tuple is new, we have to insert it
+ if (0 === (int)$updatedRows) {
+ $insertQuery = $qb->insert(self::TABLE_NAME)->values([
+ 'provider_id' => $qb->createNamedParameter($providerId),
+ 'uid' => $qb->createNamedParameter($uid),
+ 'enabled' => $qb->createNamedParameter($enabled, IQueryBuilder::PARAM_INT),
+ ]);
+
+ $insertQuery->execute();
+ }
}
}
diff --git a/lib/private/DB/MigrationService.php b/lib/private/DB/MigrationService.php
index 6f5a74103a5..412bb61a086 100644
--- a/lib/private/DB/MigrationService.php
+++ b/lib/private/DB/MigrationService.php
@@ -23,7 +23,13 @@
namespace OC\DB;
+use Doctrine\DBAL\Platforms\OraclePlatform;
+use Doctrine\DBAL\Platforms\PostgreSqlPlatform;
+use Doctrine\DBAL\Schema\Column;
+use Doctrine\DBAL\Schema\Index;
+use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\SchemaException;
+use Doctrine\DBAL\Schema\Sequence;
use OC\IntegrityCheck\Helpers\AppLocator;
use OC\Migration\SimpleOutput;
use OCP\AppFramework\App;
@@ -450,7 +456,9 @@ class MigrationService {
}, ['tablePrefix' => $this->connection->getPrefix()]);
if ($toSchema instanceof SchemaWrapper) {
- $this->connection->migrateToSchema($toSchema->getWrappedSchema());
+ $targetSchema = $toSchema->getWrappedSchema();
+ $this->ensureOracleIdentifierLengthLimit($targetSchema, strlen($this->connection->getPrefix()));
+ $this->connection->migrateToSchema($targetSchema);
$toSchema->performDropTableCalls();
}
@@ -463,6 +471,68 @@ class MigrationService {
$this->markAsExecuted($version);
}
+ public function ensureOracleIdentifierLengthLimit(Schema $schema, int $prefixLength) {
+ $sequences = $schema->getSequences();
+
+ foreach ($schema->getTables() as $table) {
+ if (\strlen($table->getName()) - $prefixLength > 27) {
+ throw new \InvalidArgumentException('Table name "' . $table->getName() . '" is too long.');
+ }
+
+ foreach ($table->getColumns() as $thing) {
+ if (\strlen($thing->getName()) - $prefixLength > 27) {
+ throw new \InvalidArgumentException('Column name "' . $table->getName() . '"."' . $thing->getName() . '" is too long.');
+ }
+ }
+
+ foreach ($table->getIndexes() as $thing) {
+ if (\strlen($thing->getName()) - $prefixLength > 27) {
+ throw new \InvalidArgumentException('Index name "' . $table->getName() . '"."' . $thing->getName() . '" is too long.');
+ }
+ }
+
+ foreach ($table->getForeignKeys() as $thing) {
+ if (\strlen($thing->getName()) - $prefixLength > 27) {
+ throw new \InvalidArgumentException('Foreign key name "' . $table->getName() . '"."' . $thing->getName() . '" is too long.');
+ }
+ }
+
+ $primaryKey = $table->getPrimaryKey();
+ if ($primaryKey instanceof Index) {
+ $indexName = strtolower($primaryKey->getName());
+ $isUsingDefaultName = $indexName === 'primary';
+
+ if ($this->connection->getDatabasePlatform() instanceof PostgreSqlPlatform) {
+ $defaultName = $table->getName() . '_pkey';
+ $isUsingDefaultName = strtolower($defaultName) === $indexName;
+
+ if ($isUsingDefaultName) {
+ $sequenceName = $table->getName() . '_' . implode('_', $primaryKey->getColumns()) . '_seq';
+ $sequences = array_filter($sequences, function(Sequence $sequence) use ($sequenceName) {
+ return $sequence->getName() !== $sequenceName;
+ });
+ }
+ } else if ($this->connection->getDatabasePlatform() instanceof OraclePlatform) {
+ $defaultName = $table->getName() . '_seq';
+ $isUsingDefaultName = strtolower($defaultName) === $indexName;
+ }
+
+ if (!$isUsingDefaultName && \strlen($indexName) - $prefixLength > 27) {
+ throw new \InvalidArgumentException('Primary index name on "' . $table->getName() . '" is too long.');
+ }
+ if ($isUsingDefaultName && \strlen($table->getName()) - $prefixLength > 23) {
+ throw new \InvalidArgumentException('Primary index name on "' . $table->getName() . '" is too long.');
+ }
+ }
+ }
+
+ foreach ($sequences as $sequence) {
+ if (\strlen($sequence->getName()) - $prefixLength > 27) {
+ throw new \InvalidArgumentException('Sequence name "' . $sequence->getName() . '" is too long.');
+ }
+ }
+ }
+
private function ensureMigrationsAreLoaded() {
if (empty($this->migrations)) {
$this->migrations = $this->findMigrations();
diff --git a/lib/private/Log.php b/lib/private/Log.php
index 4170acbb69a..33d1ddcdc34 100644
--- a/lib/private/Log.php
+++ b/lib/private/Log.php
@@ -220,6 +220,8 @@ class Log implements ILogger {
}
private function getLogLevel($context) {
+ $logCondition = $this->config->getValue('log.condition', []);
+
/**
* check for a special log condition - this enables an increased log on
* a per request/user base
@@ -265,7 +267,6 @@ class Log implements ILogger {
}
if (isset($context['app'])) {
- $logCondition = $this->config->getValue('log.condition', []);
$app = $context['app'];
/**
diff --git a/tests/lib/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDaoTest.php b/tests/lib/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDaoTest.php
index d1fed0c3a60..6323c2f0aa4 100644
--- a/tests/lib/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDaoTest.php
+++ b/tests/lib/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDaoTest.php
@@ -93,4 +93,23 @@ class ProviderUserAssignmentDaoTest extends TestCase {
$this->assertCount(1, $data);
}
+ public function testPersistTwice() {
+ $qb = $this->dbConn->getQueryBuilder();
+
+ $this->dao->persist('twofactor_totp', 'user123', 0);
+ $this->dao->persist('twofactor_totp', 'user123', 1);
+
+ $q = $qb
+ ->select('*')
+ ->from(ProviderUserAssignmentDao::TABLE_NAME)
+ ->where($qb->expr()->eq('provider_id', $qb->createNamedParameter('twofactor_totp')))
+ ->andWhere($qb->expr()->eq('uid', $qb->createNamedParameter('user123')))
+ ->andWhere($qb->expr()->eq('enabled', $qb->createNamedParameter(1)));
+ $res = $q->execute();
+ $data = $res->fetchAll();
+ $res->closeCursor();
+
+ $this->assertCount(1, $data);
+ }
+
}
diff --git a/tests/lib/DB/MigrationsTest.php b/tests/lib/DB/MigrationsTest.php
index 191164c7eed..dd06b9096cc 100644
--- a/tests/lib/DB/MigrationsTest.php
+++ b/tests/lib/DB/MigrationsTest.php
@@ -10,7 +10,14 @@
namespace Test\DB;
+use Doctrine\DBAL\Platforms\OraclePlatform;
+use Doctrine\DBAL\Platforms\PostgreSqlPlatform;
+use Doctrine\DBAL\Schema\Column;
+use Doctrine\DBAL\Schema\ForeignKeyConstraint;
+use Doctrine\DBAL\Schema\Index;
use Doctrine\DBAL\Schema\Schema;
+use Doctrine\DBAL\Schema\Sequence;
+use Doctrine\DBAL\Schema\Table;
use OC\DB\Connection;
use OC\DB\MigrationService;
use OC\DB\SchemaWrapper;
@@ -94,10 +101,18 @@ class MigrationsTest extends \Test\TestCase {
$this->db->expects($this->once())
->method('migrateToSchema');
+ $wrappedSchema = $this->createMock(Schema::class);
+ $wrappedSchema->expects($this->once())
+ ->method('getTables')
+ ->willReturn([]);
+ $wrappedSchema->expects($this->once())
+ ->method('getSequences')
+ ->willReturn([]);
+
$schemaResult = $this->createMock(SchemaWrapper::class);
$schemaResult->expects($this->once())
->method('getWrappedSchema')
- ->willReturn($this->createMock(Schema::class));
+ ->willReturn($wrappedSchema);
$step = $this->createMock(IMigrationStep::class);
$step->expects($this->at(0))
@@ -205,4 +220,338 @@ class MigrationsTest extends \Test\TestCase {
->withConsecutive(['20170130180002'], ['20170130180003']);
$this->migrationService->migrate();
}
+
+ public function testEnsureOracleIdentifierLengthLimitValid() {
+ $column = $this->createMock(Column::class);
+ $column->expects($this->once())
+ ->method('getName')
+ ->willReturn(\str_repeat('a', 30));
+
+ $index = $this->createMock(Index::class);
+ $index->expects($this->once())
+ ->method('getName')
+ ->willReturn(\str_repeat('a', 30));
+
+ $foreignKey = $this->createMock(ForeignKeyConstraint::class);
+ $foreignKey->expects($this->once())
+ ->method('getName')
+ ->willReturn(\str_repeat('a', 30));
+
+ $table = $this->createMock(Table::class);
+ $table->expects($this->once())
+ ->method('getName')
+ ->willReturn(\str_repeat('a', 30));
+
+ $sequence = $this->createMock(Sequence::class);
+ $sequence->expects($this->once())
+ ->method('getName')
+ ->willReturn(\str_repeat('a', 30));
+
+ $table->expects($this->once())
+ ->method('getColumns')
+ ->willReturn([$column]);
+ $table->expects($this->once())
+ ->method('getIndexes')
+ ->willReturn([$index]);
+ $table->expects($this->once())
+ ->method('getForeignKeys')
+ ->willReturn([$foreignKey]);
+ $table->expects($this->once())
+ ->method('getPrimaryKey')
+ ->willReturn(null);
+
+ $schema = $this->createMock(Schema::class);
+ $schema->expects($this->once())
+ ->method('getTables')
+ ->willReturn([$table]);
+ $schema->expects($this->once())
+ ->method('getSequences')
+ ->willReturn([$sequence]);
+
+ self::invokePrivate($this->migrationService, 'ensureOracleIdentifierLengthLimit', [$schema, 3]);
+ }
+
+ public function testEnsureOracleIdentifierLengthLimitValidWithPrimaryKey() {
+ $index = $this->createMock(Index::class);
+ $index->expects($this->any())
+ ->method('getName')
+ ->willReturn(\str_repeat('a', 30));
+
+ $table = $this->createMock(Table::class);
+ $table->expects($this->any())
+ ->method('getName')
+ ->willReturn(\str_repeat('a', 26));
+
+ $table->expects($this->once())
+ ->method('getColumns')
+ ->willReturn([]);
+ $table->expects($this->once())
+ ->method('getIndexes')
+ ->willReturn([]);
+ $table->expects($this->once())
+ ->method('getForeignKeys')
+ ->willReturn([]);
+ $table->expects($this->once())
+ ->method('getPrimaryKey')
+ ->willReturn($index);
+
+ $schema = $this->createMock(Schema::class);
+ $schema->expects($this->once())
+ ->method('getTables')
+ ->willReturn([$table]);
+ $schema->expects($this->once())
+ ->method('getSequences')
+ ->willReturn([]);
+
+ self::invokePrivate($this->migrationService, 'ensureOracleIdentifierLengthLimit', [$schema, 3]);
+ }
+
+ public function testEnsureOracleIdentifierLengthLimitValidWithPrimaryKeyDefault() {
+ $defaultName = 'PRIMARY';
+ if ($this->db->getDatabasePlatform() instanceof PostgreSqlPlatform) {
+ $defaultName = \str_repeat('a', 26) . '_' . \str_repeat('b', 30) . '_seq';
+ } else if ($this->db->getDatabasePlatform() instanceof OraclePlatform) {
+ $defaultName = \str_repeat('a', 26) . '_seq';
+ }
+
+ $index = $this->createMock(Index::class);
+ $index->expects($this->any())
+ ->method('getName')
+ ->willReturn($defaultName);
+ $index->expects($this->any())
+ ->method('getColumns')
+ ->willReturn([\str_repeat('b', 30)]);
+
+ $table = $this->createMock(Table::class);
+ $table->expects($this->any())
+ ->method('getName')
+ ->willReturn(\str_repeat('a', 26));
+
+ $table->expects($this->once())
+ ->method('getColumns')
+ ->willReturn([]);
+ $table->expects($this->once())
+ ->method('getIndexes')
+ ->willReturn([]);
+ $table->expects($this->once())
+ ->method('getForeignKeys')
+ ->willReturn([]);
+ $table->expects($this->once())
+ ->method('getPrimaryKey')
+ ->willReturn($index);
+
+ $schema = $this->createMock(Schema::class);
+ $schema->expects($this->once())
+ ->method('getTables')
+ ->willReturn([$table]);
+ $schema->expects($this->once())
+ ->method('getSequences')
+ ->willReturn([]);
+
+ self::invokePrivate($this->migrationService, 'ensureOracleIdentifierLengthLimit', [$schema, 3]);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testEnsureOracleIdentifierLengthLimitTooLongTableName() {
+ $table = $this->createMock(Table::class);
+ $table->expects($this->any())
+ ->method('getName')
+ ->willReturn(\str_repeat('a', 31));
+
+ $schema = $this->createMock(Schema::class);
+ $schema->expects($this->once())
+ ->method('getTables')
+ ->willReturn([$table]);
+
+ self::invokePrivate($this->migrationService, 'ensureOracleIdentifierLengthLimit', [$schema, 3]);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testEnsureOracleIdentifierLengthLimitTooLongPrimaryWithDefault() {
+ $defaultName = 'PRIMARY';
+ if ($this->db->getDatabasePlatform() instanceof PostgreSqlPlatform) {
+ $defaultName = \str_repeat('a', 27) . '_' . \str_repeat('b', 30) . '_seq';
+ } else if ($this->db->getDatabasePlatform() instanceof OraclePlatform) {
+ $defaultName = \str_repeat('a', 27) . '_seq';
+ }
+
+ $index = $this->createMock(Index::class);
+ $index->expects($this->any())
+ ->method('getName')
+ ->willReturn($defaultName);
+ $index->expects($this->any())
+ ->method('getColumns')
+ ->willReturn([\str_repeat('b', 30)]);
+
+ $table = $this->createMock(Table::class);
+ $table->expects($this->any())
+ ->method('getName')
+ ->willReturn(\str_repeat('a', 27));
+
+ $table->expects($this->once())
+ ->method('getColumns')
+ ->willReturn([]);
+ $table->expects($this->once())
+ ->method('getIndexes')
+ ->willReturn([]);
+ $table->expects($this->once())
+ ->method('getForeignKeys')
+ ->willReturn([]);
+ $table->expects($this->once())
+ ->method('getPrimaryKey')
+ ->willReturn($index);
+
+ $schema = $this->createMock(Schema::class);
+ $schema->expects($this->once())
+ ->method('getTables')
+ ->willReturn([$table]);
+
+ self::invokePrivate($this->migrationService, 'ensureOracleIdentifierLengthLimit', [$schema, 3]);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testEnsureOracleIdentifierLengthLimitTooLongPrimaryWithName() {
+ $index = $this->createMock(Index::class);
+ $index->expects($this->any())
+ ->method('getName')
+ ->willReturn(\str_repeat('a', 31));
+
+ $table = $this->createMock(Table::class);
+ $table->expects($this->any())
+ ->method('getName')
+ ->willReturn(\str_repeat('a', 26));
+
+ $table->expects($this->once())
+ ->method('getColumns')
+ ->willReturn([]);
+ $table->expects($this->once())
+ ->method('getIndexes')
+ ->willReturn([]);
+ $table->expects($this->once())
+ ->method('getForeignKeys')
+ ->willReturn([]);
+ $table->expects($this->once())
+ ->method('getPrimaryKey')
+ ->willReturn($index);
+
+ $schema = $this->createMock(Schema::class);
+ $schema->expects($this->once())
+ ->method('getTables')
+ ->willReturn([$table]);
+
+ self::invokePrivate($this->migrationService, 'ensureOracleIdentifierLengthLimit', [$schema, 3]);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testEnsureOracleIdentifierLengthLimitTooLongColumnName() {
+ $column = $this->createMock(Column::class);
+ $column->expects($this->any())
+ ->method('getName')
+ ->willReturn(\str_repeat('a', 31));
+
+ $table = $this->createMock(Table::class);
+ $table->expects($this->any())
+ ->method('getName')
+ ->willReturn(\str_repeat('a', 30));
+
+ $table->expects($this->once())
+ ->method('getColumns')
+ ->willReturn([$column]);
+
+ $schema = $this->createMock(Schema::class);
+ $schema->expects($this->once())
+ ->method('getTables')
+ ->willReturn([$table]);
+
+ self::invokePrivate($this->migrationService, 'ensureOracleIdentifierLengthLimit', [$schema, 3]);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testEnsureOracleIdentifierLengthLimitTooLongIndexName() {
+ $index = $this->createMock(Index::class);
+ $index->expects($this->any())
+ ->method('getName')
+ ->willReturn(\str_repeat('a', 31));
+
+ $table = $this->createMock(Table::class);
+ $table->expects($this->any())
+ ->method('getName')
+ ->willReturn(\str_repeat('a', 30));
+
+ $table->expects($this->once())
+ ->method('getColumns')
+ ->willReturn([]);
+ $table->expects($this->once())
+ ->method('getIndexes')
+ ->willReturn([$index]);
+
+ $schema = $this->createMock(Schema::class);
+ $schema->expects($this->once())
+ ->method('getTables')
+ ->willReturn([$table]);
+
+ self::invokePrivate($this->migrationService, 'ensureOracleIdentifierLengthLimit', [$schema, 3]);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testEnsureOracleIdentifierLengthLimitTooLongForeignKeyName() {
+ $foreignKey = $this->createMock(ForeignKeyConstraint::class);
+ $foreignKey->expects($this->any())
+ ->method('getName')
+ ->willReturn(\str_repeat('a', 31));
+
+ $table = $this->createMock(Table::class);
+ $table->expects($this->any())
+ ->method('getName')
+ ->willReturn(\str_repeat('a', 30));
+
+ $table->expects($this->once())
+ ->method('getColumns')
+ ->willReturn([]);
+ $table->expects($this->once())
+ ->method('getIndexes')
+ ->willReturn([]);
+ $table->expects($this->once())
+ ->method('getForeignKeys')
+ ->willReturn([$foreignKey]);
+
+ $schema = $this->createMock(Schema::class);
+ $schema->expects($this->once())
+ ->method('getTables')
+ ->willReturn([$table]);
+
+ self::invokePrivate($this->migrationService, 'ensureOracleIdentifierLengthLimit', [$schema, 3]);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testEnsureOracleIdentifierLengthLimitTooLongSequenceName() {
+ $sequence = $this->createMock(Sequence::class);
+ $sequence->expects($this->any())
+ ->method('getName')
+ ->willReturn(\str_repeat('a', 31));
+
+ $schema = $this->createMock(Schema::class);
+ $schema->expects($this->once())
+ ->method('getTables')
+ ->willReturn([]);
+ $schema->expects($this->once())
+ ->method('getSequences')
+ ->willReturn([$sequence]);
+
+ self::invokePrivate($this->migrationService, 'ensureOracleIdentifierLengthLimit', [$schema, 3]);
+ }
}
diff --git a/tests/ui-regression/config.js b/tests/ui-regression/config.js
new file mode 100644
index 00000000000..c40efd722d7
--- /dev/null
+++ b/tests/ui-regression/config.js
@@ -0,0 +1,58 @@
+/**
+ * @copyright 2018 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author 2018 Julius Härtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+module.exports = {
+
+ /**
+ * Define resolutions to be tested when diffing screenshots
+ */
+ resolutions: [
+ {title: 'mobile', w: 360, h: 480},
+ {title: 'narrow', w: 800, h: 600},
+ {title: 'normal', w: 1024, h: 768},
+ {title: 'wide', w: 1920, h: 1080},
+ {title: 'qhd', w: 2560, h: 1440},
+ ],
+
+ /**
+ * URL that holds the base branch
+ */
+ urlBase: 'http://ui-regression-php-master/',
+
+ /**
+ * URL that holds the branch to be diffed
+ */
+ urlChange: 'http://ui-regression-php/',
+
+ /**
+ * Path to output directory for screenshot files
+ */
+ outputDirectory: 'out',
+
+ /**
+ * Run in headless mode (useful for debugging)
+ */
+ headless: true,
+
+ slowMo: 0,
+
+};
diff --git a/tests/ui-regression/helper.js b/tests/ui-regression/helper.js
new file mode 100644
index 00000000000..1ec62728dc7
--- /dev/null
+++ b/tests/ui-regression/helper.js
@@ -0,0 +1,256 @@
+/**
+ * @copyright 2018 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author 2018 Julius Härtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+const puppeteer = require('puppeteer');
+const pixelmatch = require('pixelmatch');
+const expect = require('chai').expect;
+const PNG = require('pngjs2').PNG;
+const fs = require('fs');
+const config = require('./config.js');
+
+
+module.exports = {
+ browser: null,
+ pageBase: null,
+ pageCompare: null,
+ lastBase: 0,
+ lastCompare: 0,
+ init: async function (test) {
+ this._outputDirectory = `${config.outputDirectory}/${test.title}`;
+ if (!fs.existsSync(config.outputDirectory)) fs.mkdirSync(config.outputDirectory);
+ if (!fs.existsSync(this._outputDirectory)) fs.mkdirSync(this._outputDirectory);
+ await this.resetBrowser();
+ },
+ exit: async function () {
+ await this.browser.close();
+ },
+ resetBrowser: async function () {
+ if (this.browser) {
+ await this.browser.close();
+ }
+ this.browser = await puppeteer.launch({
+ args: ['--no-sandbox', '--disable-setuid-sandbox'],
+ headless: config.headless,
+ slowMo: config.slowMo,
+ });
+ this.pageBase = await this.browser.newPage();
+ this.pageCompare = await this.browser.newPage();
+ this.pageBase.setDefaultNavigationTimeout(60000);
+ this.pageCompare.setDefaultNavigationTimeout(60000);
+
+ const self = this;
+ this.pageCompare.on('requestfinished', function() {
+ self.lastCompare = Date.now();
+ });
+ this.pageBase.on('requestfinished', function() {
+ self.lastBase = Date.now();
+ });
+ },
+
+ awaitNetworkIdle: async function (seconds) {
+ var self = this;
+ return new Promise(function (resolve, reject) {
+ const timeout = setTimeout(function() {
+ reject();
+ }, 10000)
+ const waitForFoo = function() {
+ const currentTime = Date.now() - seconds*1000;
+ if (self.lastBase < currentTime && self.lastCompare < currentTime) {
+ clearTimeout(timeout);
+ return resolve();
+ }
+ setTimeout(waitForFoo, 100);
+ };
+ waitForFoo();
+
+ });
+ },
+
+ login: async function (test) {
+ test.timeout(20000);
+ await this.resetBrowser();
+ await Promise.all([
+ this.performLogin(this.pageBase, config.urlBase),
+ this.performLogin(this.pageCompare, config.urlChange)
+ ]);
+ },
+
+ performLogin: async function (page, baseUrl) {
+ await page.bringToFront();
+ await page.goto(baseUrl + '/index.php/login', {waitUntil: 'networkidle0'});
+ await page.type('#user', 'admin');
+ await page.type('#password', 'admin');
+ const inputElement = await page.$('input[type=submit]');
+ await inputElement.click();
+ await page.waitForNavigation({waitUntil: 'networkidle2'});
+ return await page.waitForSelector('#header');
+ },
+
+ takeAndCompare: async function (test, route, action, options) {
+ // use Promise.all
+ if (options === undefined)
+ options = {};
+ if (options.waitUntil === undefined) {
+ options.waitUntil = 'networkidle0';
+ }
+ if (options.viewport) {
+ if (options.viewport.scale === undefined) {
+ options.viewport.scale = 1;
+ }
+ await Promise.all([
+ this.pageBase.setViewport({
+ width: options.viewport.w,
+ height: options.viewport.h,
+ deviceScaleFactor: options.viewport.scale
+ }),
+ this.pageCompare.setViewport({
+ width: options.viewport.w,
+ height: options.viewport.h,
+ deviceScaleFactor: options.viewport.scale
+ })
+ ]);
+ await this.delay(100);
+ }
+ let fileName = test.test.title
+ if (route !== undefined) {
+ await Promise.all([
+ this.pageBase.goto(`${config.urlBase}${route}`, {waitUntil: options.waitUntil}),
+ this.pageCompare.goto(`${config.urlChange}${route}`, {waitUntil: options.waitUntil})
+ ]);
+ }
+ await this.pageBase.$eval('body', function (e) {
+ $('.live-relative-timestamp').removeClass('live-relative-timestamp').text('5 minutes ago');
+ $(':focus').blur();
+ });
+ await this.pageCompare.$eval('body', function (e) {
+ $('.live-relative-timestamp').removeClass('live-relative-timestamp').text('5 minutes ago');
+ $(':focus').blur();
+ });
+ var failed = null;
+ try {
+ await this.pageBase.bringToFront();
+ await action(this.pageBase);
+ await this.pageCompare.bringToFront();
+ await action(this.pageCompare);
+ } catch (err) {
+ failed = err;
+ }
+ await this.awaitNetworkIdle(3);
+ await this.pageBase.$eval('body', function (e) {
+ $('.live-relative-timestamp').removeClass('live-relative-timestamp').text('5 minutes ago');
+ $(':focus').blur();
+ });
+ await this.pageCompare.$eval('body', function (e) {
+ $('.live-relative-timestamp').removeClass('live-relative-timestamp').text('5 minutes ago');
+ $(':focus').blur();
+ });
+ await Promise.all([
+ this.pageBase.screenshot({
+ path: `${this._outputDirectory}/${fileName}.base.png`,
+ fullPage: false,
+ }),
+ this.pageCompare.screenshot({
+ path: `${this._outputDirectory}/${fileName}.change.png`,
+ fullPage: false
+ })
+ ]);
+
+ if (options.runOnly === true) {
+ fs.unlinkSync(`${this._outputDirectory}/${fileName}.base.png`);
+ fs.renameSync(`${this._outputDirectory}/${fileName}.change.png`, `${this._outputDirectory}/${fileName}.png`);
+ }
+
+ return new Promise(async (resolve, reject) => {
+ try {
+ if (options.runOnly !== true) {
+ await this.compareScreenshots(fileName);
+ }
+ } catch (err) {
+ if (failed) {
+ console.log('Failure during takeAndCompare action callback');
+ console.log(failed);
+ }
+ console.log('Failure when comparing images');
+ return reject(err);
+ }
+ if (options.runOnly !== true && failed) {
+ console.log('Failure during takeAndCompare action callback');
+ console.log(failed);
+ failed.failedAction = true;
+ return reject(failed);
+ }
+ return resolve();
+ });
+ },
+
+ compareScreenshots: function (fileName) {
+ let self = this;
+ return new Promise((resolve, reject) => {
+ const img1 = fs.createReadStream(`${self._outputDirectory}/${fileName}.base.png`).pipe(new PNG()).on('parsed', doneReading);
+ const img2 = fs.createReadStream(`${self._outputDirectory}/${fileName}.change.png`).pipe(new PNG()).on('parsed', doneReading);
+
+ let filesRead = 0;
+
+ function doneReading () {
+ // Wait until both files are read.
+ if (++filesRead < 2) return;
+
+ // The files should be the same size.
+ expect(img1.width, 'image widths are the same').equal(img2.width);
+ expect(img1.height, 'image heights are the same').equal(img2.height);
+
+ // Do the visual diff.
+ const diff = new PNG({width: img1.width, height: img2.height});
+ const numDiffPixels = pixelmatch(
+ img1.data, img2.data, diff.data, img1.width, img1.height,
+ {threshold: 0.3});
+ if (numDiffPixels > 0) {
+ diff.pack().pipe(fs.createWriteStream(`${self._outputDirectory}/${fileName}.diff.png`));
+ } else {
+ fs.unlinkSync(`${self._outputDirectory}/${fileName}.base.png`);
+ fs.renameSync(`${self._outputDirectory}/${fileName}.change.png`, `${self._outputDirectory}/${fileName}.png`);
+ }
+
+ // The files should look the same.
+ expect(numDiffPixels, 'number of different pixels').equal(0);
+ resolve();
+ }
+ });
+ },
+ /**
+ * Helper function to wait
+ * to make sure that initial animations are done
+ */
+ delay: async function (timeout) {
+ return new Promise((resolve) => {
+ setTimeout(resolve, timeout);
+ });
+ },
+
+ childOfClassByText: async function (page, classname, text) {
+ return page.$x('//*[contains(concat(" ", normalize-space(@class), " "), " ' + classname + ' ")]//text()[normalize-space() = \'' + text + '\']/..');
+ },
+
+ childOfIdByText: async function (page, classname, text) {
+ return page.$x('//*[contains(concat(" ", normalize-space(@id), " "), " ' + classname + ' ")]//text()[normalize-space() = \'' + text + '\']/..');
+ }
+};
diff --git a/tests/ui-regression/out/index.html b/tests/ui-regression/out/index.html
new file mode 100644
index 00000000000..a94dae13445
--- /dev/null
+++ b/tests/ui-regression/out/index.html
@@ -0,0 +1,219 @@
+<!doctype html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous" />
+ <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" />
+ <script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
+ <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
+ <title>Nextcloud UI regression tests</title>
+ <style>
+
+ h2 {
+ margin-top: 40px;
+ margin-bottom: 20px;
+ }
+ .error {
+ color: #aa0000;
+ }
+ .success {
+ color: #00aa00;
+ }
+ .success img {
+ display: none;
+ width: 100px;
+ }
+ .success pre {
+ display: none;
+ }
+ .test-result h3 span {
+ width: 40px;
+ }
+ .test-result {
+ padding: 20px;
+ }
+ img {
+ max-width: 33%;
+ padding: 10px;
+ background-color: #eee;
+ margin: 0;
+ }
+ .overview ul {
+ position: fixed;
+ max-width: inherit;
+ margin: 0;
+ padding: 0;
+ }
+ ul li {
+ list-style-type: none;
+ padding: 3px;
+ }
+ ul a:first-child {
+ width: 100%;
+ display: inline-block;
+ }
+ ul span {
+ width: 16px;
+ height: 16px;
+ margin: 1px;
+ display: inline-block;
+ }
+ span.fa-check {
+ color: green;
+ }
+ span.fa-times {
+ color: red;
+ }
+ .navbar a {
+ color: #fff;
+ }
+
+ .fade-enter-active, .fade-leave-active {
+ transition: opacity .5s;
+ }
+ .fade-enter, .fade-leave-to {
+ opacity: 0;
+ }
+ </style>
+</head>
+
+<body>
+<div id="app">
+<nav class="navbar navbar-expand-md navbar-dark bg-dark sticky-top">
+ <div class="container">
+ <a class="navbar-brand" href="#">Nextcloud UI regression test</a>
+ <a class="nav-link" :href="config.repoUrl">{{config.repoUrl}}</a>
+ <a class="nav-link" :href="config.repoUrl + '/pull/' + config.pr">#{{ config.pr }}</span></a>
+ </div>
+</nav>
+
+<main role="main" class="container-fluid">
+ <div class="row">
+ <div class="col-md-2 overview">
+ <ul>
+ <li v-for="suite in config.tests" v-if="result[suite]">
+ <a :href="'#' + suite">{{ suite }}</a>
+ <a v-for="test in result[suite].tests" :href="test.fullTitle | convertToAnchor" :title="test.fullTitle">
+ <span class="fa fa-times" v-if="Object.keys(test.err).length > 0"></span>
+ <span class="fa fa-check" v-else></span>
+ </a>
+ </li>
+ </ul>
+ </div>
+ <div class="col-md-10" id="container">
+ <div v-for="suite in config.tests" v-if="result[suite]">
+ <h2 :id="suite | convertToId">{{ suite }} <span>{{ result[suite].passes.length }}/{{ result[suite].tests.length }}</span></h2>
+ <test-result v-for="test in result[suite].tests" :key="test.fullTitle" :suite="suite" :test="test"></test-result>
+ </div>
+ </div>
+ </div>
+</main>
+</div>
+
+<script type="text/x-template" id="test-result-template">
+ <div class="test-result" :id="test.fullTitle | convertToId">
+ <h3 :class="{ error: Object.keys(test.err).length > 0, success: Object.keys(test.err).length == 0}"
+ v-on:click="hidden === undefined ? hidden = false : hidden = !hidden">
+ <span class="fa fa-times" v-if="Object.keys(test.err).length > 0"></span>
+ <span class="fa fa-check" v-else></span>
+ {{ test.title }}
+ <i v-if="test.duration">{{ test.duration }}ms</i>
+ </h3>
+ <transition name="fade">
+ <div v-if="(hidden === undefined && Object.keys(test.err).length > 0) || hidden === false">
+ <div v-if="Object.keys(test.err).length > 0 && !test.err.failedAction">
+ <a :href="getImagePath('.base')"><img :src="getImagePath('.base')" /></a>
+ <a :href="getImagePath('.diff')"><img :src="getImagePath('.diff')" /></a>
+ <a :href="getImagePath('.change')"><img :src="getImagePath('.change')" /></a>
+ </div>
+ <div v-else>
+ <a :href="getImagePath('')"><img :src="getImagePath('')" /></a>
+ </div>
+ <pre>{{ jsonData }}</pre>
+ </div>
+ </transition>
+ </div>
+</script>
+
+<script>
+
+ Vue.filter('convertToId', function (id) {
+ return id.replace(/\W/g,'_');
+ });
+
+ Vue.filter('convertToAnchor', function (id) {
+ return '#' + id.replace(/\W/g,'_');
+ });
+
+ Vue.component('test-result', {
+ template: '#test-result-template',
+ props: ['test', 'suite'],
+ data: function () {
+ return {
+ hidden: undefined
+ }
+ },
+ computed: {
+ jsonData: function() {
+ return JSON.stringify(this.test, null, 2)
+ }
+ },
+ methods: {
+ getImagePath: function(type) {
+ return this.suite + '/' + this.test.title + type + '.png';
+ }
+ }
+ });
+
+ var app = new Vue({
+ el: '#app',
+ data: {
+ message: 'Hello Vue!',
+ config: {},
+ result: {
+ login: {}
+ },
+ },
+ created: function() {
+ this.fetchConfig();
+ },
+ methods: {
+ fetchConfig: function() {
+ var request = new XMLHttpRequest();
+ request.open('GET', 'config.json', true);
+
+ request.onload = function() {
+ if (request.status >= 200 && request.status < 400) {
+ app.config = JSON.parse(request.responseText);
+ app.config.tests.forEach(function(item, i){
+ app.fetchResults(item);
+ });
+ }
+ };
+
+ request.onerror = function() {
+ };
+
+ request.send();
+ },
+ fetchResults: function(suite) {
+ var request = new XMLHttpRequest();
+ request.open('GET', suite + '.json', true);
+
+ request.onload = function() {
+ if (request.status >= 200 && request.status < 400) {
+ Vue.set(app.result, suite, JSON.parse(request.responseText));
+ }
+ };
+
+ request.onerror = function() {
+ };
+
+ request.send();
+ }
+ }
+ });
+
+</script>
+</body>
+</html>
diff --git a/tests/ui-regression/package.json b/tests/ui-regression/package.json
new file mode 100644
index 00000000000..8ab4cf530f2
--- /dev/null
+++ b/tests/ui-regression/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "ui-regression",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "mocha test/"
+ },
+ "author": "",
+ "dependencies": {
+ "chai": "^4.1.2",
+ "fs": "0.0.1-security",
+ "mocha": "^5.2.0",
+ "mocha-json-report": "0.0.2",
+ "pixelmatch": "^4.0.2",
+ "png-js": "^0.1.1",
+ "pngjs2": "^2.0.0",
+ "polyserve": "^0.23.0",
+ "puppeteer": "^1.6.1"
+ }
+}
diff --git a/tests/ui-regression/runTests.js b/tests/ui-regression/runTests.js
new file mode 100644
index 00000000000..4eb94f79347
--- /dev/null
+++ b/tests/ui-regression/runTests.js
@@ -0,0 +1,129 @@
+/**
+ * @copyright 2018 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author 2018 Julius Härtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+const fs = require('fs')
+const Mocha = require('mocha')
+
+const testFolder = './test/'
+
+
+var tests = [
+ 'install',
+ 'login',
+ 'files',
+ 'public',
+ 'settings',
+ 'apps',
+]
+
+var args = process.argv.slice(2);
+if (args.length > 0) {
+ tests = args
+}
+
+var config = {
+ tests: tests,
+ pr: process.env.DRONE_PULL_REQUEST,
+ repoUrl: process.env.DRONE_REPO_LINK,
+};
+
+console.log('=> Write test config');
+console.log(config);
+fs.writeFile('out/config.json', JSON.stringify(config), 'utf8', () => {});
+
+var mocha = new Mocha({
+ timeout: 60000
+});
+let result = {};
+
+tests.forEach(async function (test) {
+ mocha.addFile('./test/' + test + 'Spec.js')
+ result[test] = {
+ failures: [],
+ passes: [],
+ tests: [],
+ pending: [],
+ stats: {}
+ }
+
+});
+
+// fixme fail if installation failed
+// write json to file
+
+function clean (test) {
+ return {
+ title: test.title,
+ fullTitle: test.fullTitle(),
+ duration: test.duration,
+ currentRetry: test.currentRetry(),
+ failedAction: test.failedAction,
+ err: errorJSON(test.err || {})
+ };
+}
+
+function errorJSON (err) {
+ var res = {};
+ Object.getOwnPropertyNames(err).forEach(function (key) {
+ res[key] = err[key];
+ }, err);
+ return res;
+}
+
+mocha.run()
+ .on('test', function (test) {
+ })
+ .on('suite end', function(suite) {
+ if (result[suite.title] === undefined)
+ return;
+ result[suite.title].stats = suite.stats;
+ })
+ .on('test end', function (test) {
+ result[test.parent.title].tests.push(test);
+ })
+ .on('pass', function (test) {
+ result[test.parent.title].passes.push(test);
+ })
+ .on('fail', function (test) {
+ result[test.parent.title].failures.push(test);
+ })
+ .on('pending', function (test) {
+ result[test.parent.title].pending.push(test);
+ })
+ .on('end', function () {
+ tests.forEach(function (test) {
+ var json = JSON.stringify({
+ stats: result[test].stats,
+ tests: result[test].tests.map(clean),
+ pending: result[test].pending.map(clean),
+ failures: result[test].failures.map(clean),
+ passes: result[test].passes.map(clean)
+ }, null, 2);
+ fs.writeFile(`out/${test}.json`, json, 'utf8', function () {
+ console.log(`Written test result to out/${test}.json`)
+ });
+ });
+
+ var errorMessage = 'This PR introduces some UI differences, please check at {LINK}, if there are regressions based on the changes.'
+ fs.writeFile('out/GITHUB_COMMENT', errorMessage, 'utf8', () => {});
+ });
+
diff --git a/tests/ui-regression/test/appsSpec.js b/tests/ui-regression/test/appsSpec.js
new file mode 100644
index 00000000000..3a23c1df773
--- /dev/null
+++ b/tests/ui-regression/test/appsSpec.js
@@ -0,0 +1,60 @@
+/**
+ * @copyright 2018 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author 2018 Julius Härtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+const helper = require('../helper.js');
+const config = require('../config.js');
+
+describe('apps', function () {
+
+ before(async () => {
+ await helper.init(this)
+ await helper.login(this)
+ });
+ after(async () => await helper.exit());
+
+ config.resolutions.forEach(function (resolution) {
+ it('apps.' + resolution.title, async function () {
+ return helper.takeAndCompare(this, 'index.php/settings/apps', async function (page) {
+ await page.waitForSelector('#apps-list .section', {timeout: 5000});
+ await page.waitFor(500);
+ }, {viewport: resolution, waitUntil: 'networkidle2'});
+ });
+
+ ['your-apps', 'enabled', 'disabled', 'app-bundles'].forEach(function(endpoint) {
+ it('apps.' + endpoint + '.' + resolution.title, async function () {
+ return helper.takeAndCompare(this, undefined, async function (page) {
+ try {
+ await page.waitForSelector('#app-navigation-toggle', {
+ visible: true,
+ timeout: 1000,
+ }).then((element) => element.click())
+ } catch (err) {}
+ await helper.delay(500);
+ await page.click('li#app-category-' + endpoint + ' a');
+ await helper.delay(500);
+ await page.waitForSelector('#app-content:not(.icon-loading)');
+ }, {viewport: resolution});
+ });
+ });
+ });
+
+});
diff --git a/tests/ui-regression/test/filesSpec.js b/tests/ui-regression/test/filesSpec.js
new file mode 100644
index 00000000000..7a029b2f311
--- /dev/null
+++ b/tests/ui-regression/test/filesSpec.js
@@ -0,0 +1,102 @@
+/**
+ * @copyright 2018 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author 2018 Julius Härtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+const puppeteer = require('puppeteer');
+const helper = require('../helper.js');
+const config = require('../config.js');
+
+describe('files', function () {
+
+ before(async () => {
+ await helper.init(this)
+ await helper.login(this)
+ });
+ after(async () => await helper.exit());
+
+ config.resolutions.forEach(function (resolution) {
+
+ it('file-sidebar-share.' + resolution.title, async function () {
+ return helper.takeAndCompare(this, 'index.php/apps/files', async function (page) {
+ let element = await page.$('[data-file="welcome.txt"] .action-share');
+ await element.click('[data-file="welcome.txt"] .action-share');
+ await page.waitForSelector('.shareWithField');
+ await helper.delay(500);
+ await page.$eval('body', e => { $('.shareWithField').blur() });
+ }, {viewport: resolution});
+ });
+ it('file-popover.' + resolution.title, async function () {
+ return helper.takeAndCompare(this, 'index.php/apps/files', async function (page) {
+ await page.click('[data-file=\'welcome.txt\'] .action-menu');
+ await page.waitForSelector('.fileActionsMenu');
+ }, {viewport: resolution});
+ });
+ it('file-sidebar-details.' + resolution.title, async function() {
+ return helper.takeAndCompare(this, undefined, async function (page) {
+ await page.click('[data-file=\'welcome.txt\'] .fileActionsMenu [data-action=\'Details\']');
+ await page.waitForSelector('[data-tabid=\'commentsTabView\']');
+ await page.$eval('body', e => { $('.shareWithField').blur() });
+ await helper.delay(500); // wait for animation
+ }, {viewport: resolution});
+ });
+ it('file-sidebar-details-sharing.' + resolution.title, async function() {
+ return helper.takeAndCompare(this, undefined, async function (page) {
+ let tab = await helper.childOfClassByText(page, 'tabHeaders', 'Sharing');
+ tab[0].click();
+ await page.waitForSelector('input.shareWithField');
+ await page.$eval('body', e => { $('.shareWithField').blur() });
+ await helper.delay(500); // wait for animation
+ }, {viewport: resolution});
+ });
+ it('file-sidebar-details-versions.' + resolution.title, async function() {
+ return helper.takeAndCompare(this, undefined, async function (page) {
+ let tab = await helper.childOfClassByText(page, 'tabHeaders', 'Versions');
+ tab[0].click();
+ await helper.delay(100); // wait for animation
+ }, {viewport: resolution});
+ });
+ it('file-popover.favorite.' + resolution.title, async function () {
+ return helper.takeAndCompare(this, 'index.php/apps/files', async function (page) {
+ await page.click('[data-file=\'welcome.txt\'] .action-menu');
+ await page.waitForSelector('.fileActionsMenu')
+ await page.click('[data-file=\'welcome.txt\'] .fileActionsMenu [data-action=\'Favorite\']');;
+ }, {viewport: resolution});
+ });
+
+ it('file-favorites.' + resolution.title, async function () {
+ return helper.takeAndCompare(this, 'index.php/apps/files', async function (page) {
+ try {
+ await page.waitForSelector('#app-navigation-toggle', {
+ visible: true,
+ timeout: 1000,
+ }).then((element) => element.click())
+ } catch (err) {}
+ await page.click('#app-navigation [data-id=\'favorites\'] a');
+ await helper.delay(500); // wait for animation
+ }, {viewport: resolution});
+ });
+
+
+ });
+
+
+
+});
diff --git a/tests/ui-regression/test/installSpec.js b/tests/ui-regression/test/installSpec.js
new file mode 100644
index 00000000000..ffb4854f1b6
--- /dev/null
+++ b/tests/ui-regression/test/installSpec.js
@@ -0,0 +1,76 @@
+/**
+ * @copyright 2018 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author 2018 Julius Härtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+const helper = require('../helper.js');
+const config = require('../config.js');
+
+describe('install', function () {
+
+ before(async () => await helper.init(this));
+ after(async () => await helper.exit());
+
+ config.resolutions.forEach(function (resolution) {
+ it('show-page.' + resolution.title, async function () {
+ // (test, route, prepare, action, options
+ return helper.takeAndCompare(this, 'index.php', async (page) => {
+ await helper.delay(100);
+ await page.$eval('body', function (e) {
+ $('#adminlogin').blur();
+ });
+ await helper.delay(100);
+ }, { waitUntil: 'networkidle0', viewport: resolution});
+ });
+
+ it('show-advanced.' + resolution.title, async function () {
+ // (test, route, prepare, action, options
+ return helper.takeAndCompare(this, undefined, async (page) => {
+ await page.click('#showAdvanced');
+ await helper.delay(300);
+ }, { waitUntil: 'networkidle0', viewport: resolution});
+ });
+ it('show-advanced-mysql.' + resolution.title, async function () {
+ // (test, route, prepare, action, options
+ return helper.takeAndCompare(this, undefined, async (page) => {
+ await page.click('label.mysql');
+ await helper.delay(300);
+ }, { waitUntil: 'networkidle0', viewport: resolution});
+ });
+ });
+
+ it('runs', async function () {
+ this.timeout(5*60*1000);
+ helper.pageBase.setDefaultNavigationTimeout(5*60*1000);
+ helper.pageCompare.setDefaultNavigationTimeout(5*60*1000);
+ // just run for one resolution since we can only install once
+ return helper.takeAndCompare(this, 'index.php', async function (page) {
+ const login = await page.type('#adminlogin', 'admin');
+ const password = await page.type('#adminpass', 'admin');
+ const inputElement = await page.$('input[type=submit]');
+ await inputElement.click();
+ await page.waitForNavigation({waitUntil: 'networkidle2'});
+ await page.waitForSelector('#header');
+ helper.pageBase.setDefaultNavigationTimeout(60000);
+ helper.pageCompare.setDefaultNavigationTimeout(60000);
+ }, { waitUntil: 'networkidle0', viewport: {w: 1920, h: 1080}});
+ });
+
+});
diff --git a/tests/ui-regression/test/loginSpec.js b/tests/ui-regression/test/loginSpec.js
new file mode 100644
index 00000000000..8607bbabc84
--- /dev/null
+++ b/tests/ui-regression/test/loginSpec.js
@@ -0,0 +1,81 @@
+/**
+ * @copyright 2018 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author 2018 Julius Härtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+const helper = require('../helper.js');
+const config = require('../config.js');
+
+describe('login', function () {
+
+ before(async () => await helper.init(this));
+ after(async () => await helper.exit());
+
+ /**
+ * Test login page rendering
+ */
+ config.resolutions.forEach(function (resolution) {
+ it('login-page.' + resolution.title, async function () {
+ return helper.takeAndCompare(this, '/', async (page) => {
+ // make sure the cursor is not blinking in the login field
+ await page.$eval('body', function (e) {
+ $('#user').blur();
+ });
+ return await helper.delay(100);
+ }, {viewport: resolution});
+ });
+
+ it('login-page.forgot.' + resolution.title, async function () {
+ return helper.takeAndCompare(this, undefined, async (page) => {
+ const lostPassword = await page.$('#lost-password');
+ await lostPassword.click();
+ await helper.delay(500);
+ await page.$eval('body', function (e) {
+ $('#user').blur();
+ });
+ }, {viewport: resolution});
+ });
+ });
+
+ /**
+ * Perform login
+ */
+ config.resolutions.forEach(function (resolution) {
+ it('login-success.' + resolution.title, async function () {
+ this.timeout(30000);
+ await helper.resetBrowser();
+ return helper.takeAndCompare(this, '/', async function (page) {
+ await page.waitForSelector('input#user');
+ await page.type('#user', 'admin');
+ await page.type('#password', 'admin');
+ const inputElement = await page.$('input[type=submit]');
+ await inputElement.click();
+ await page.waitForNavigation({waitUntil: 'networkidle2'});
+ await page.waitForSelector('#header');
+ await page.$eval('body', function (e) {
+ // force relative timestamp to fixed value, since it breaks screenshot diffing
+ $('.live-relative-timestamp').removeClass('live-relative-timestamp').text('5 minutes ago');
+ });
+ return await helper.delay(100);
+ }, {viewport: resolution});
+ })
+ });
+
+});
diff --git a/tests/ui-regression/test/publicSpec.js b/tests/ui-regression/test/publicSpec.js
new file mode 100644
index 00000000000..0893adf9a42
--- /dev/null
+++ b/tests/ui-regression/test/publicSpec.js
@@ -0,0 +1,102 @@
+/**
+ * @copyright 2018 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author 2018 Julius Härtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+const puppeteer = require('puppeteer');
+const helper = require('../helper.js');
+const config = require('../config.js');
+
+describe('public', function () {
+
+ before(async () => {
+ await helper.init(this)
+ await helper.login(this)
+ });
+ after(async () => await helper.exit());
+
+ /**
+ * Test invalid file share rendering
+ */
+ config.resolutions.forEach(function (resolution) {
+ it('file-share-invalid.' + resolution.title, async function () {
+ return helper.takeAndCompare(this, 'index.php/s/invalid', async function () {
+ }, {waitUntil: 'networkidle2', viewport: resolution});
+ });
+ });
+
+ /**
+ * Share a file via public link
+ */
+
+ var shareLink = {};
+ it('file-share-link', async function () {
+ return helper.takeAndCompare(this, 'index.php/apps/files', async function (page) {
+ const element = await page.$('[data-file="welcome.txt"] .action-share');
+ await element.click('[data-file="welcome.txt"] .action-share');
+ await page.waitForSelector('input.linkCheckbox');
+ const linkCheckbox = await page.$('.linkShareView label');
+ await Promise.all([
+ linkCheckbox.click(),
+ page.waitForSelector('.linkText')
+ ]);
+ await helper.delay(500);
+ const text = await page.waitForSelector('.linkText');
+ const link = await (await text.getProperty('value')).jsonValue();
+ shareLink[page.url()] = link;
+ return await helper.delay(500);
+ }, {
+ runOnly: true,
+ waitUntil: 'networkidle2',
+ viewport: {w: 1920, h: 1080}
+ });
+ });
+
+ config.resolutions.forEach(function (resolution) {
+ it('file-share-valid.' + resolution.title, async function () {
+ return helper.takeAndCompare(this, 'index.php/apps/files', async function (page) {
+ await page.goto(shareLink[page.url()]);
+ await helper.delay(500);
+ }, {waitUntil: 'networkidle2', viewport: resolution});
+ });
+ it('file-share-valid-actions.' + resolution.title, async function () {
+ return helper.takeAndCompare(this, undefined, async function (page) {
+ const moreButton = await page.waitForSelector('#header-secondary-action');
+ await moreButton.click();
+ await page.evaluate((data) => {
+ return document.querySelector('#directLink').value = 'http://nextcloud.example.com/';
+ });
+ await helper.delay(500);
+ }, {waitUntil: 'networkidle2', viewport: resolution});
+ });
+ });
+
+ it('file-unshare', async function () {
+ return helper.takeAndCompare(this, 'index.php/apps/files', async function (page) {
+ const element = await page.$('[data-file="welcome.txt"] .action-share');
+ await element.click('[data-file="welcome.txt"] .action-share');
+ await page.waitForSelector('input.linkCheckbox');
+ const linkCheckbox = await page.$('.linkShareView label');
+ await linkCheckbox.click();
+ await helper.delay(500);
+ }, { waitUntil: 'networkidle2', viewport: {w: 1920, h:1080}});
+ });
+
+});
diff --git a/tests/ui-regression/test/settingsSpec.js b/tests/ui-regression/test/settingsSpec.js
new file mode 100644
index 00000000000..8b10b281fb6
--- /dev/null
+++ b/tests/ui-regression/test/settingsSpec.js
@@ -0,0 +1,76 @@
+/**
+ * @copyright 2018 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author 2018 Julius Härtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+const helper = require('../helper.js');
+const config = require('../config.js');
+
+describe('settings', function () {
+
+ before(async () => {
+ await helper.init(this)
+ await helper.login(this)
+ });
+ after(async () => await helper.exit());
+
+ config.resolutions.forEach(function (resolution) {
+ it('personal.' + resolution.title, async function () {
+ return helper.takeAndCompare(this, 'index.php/settings/user', async function (page) {
+ }, {viewport: resolution});
+ });
+
+ it('admin.' + resolution.title, async function () {
+ return helper.takeAndCompare(this, 'index.php/settings/admin', async function (page) {
+ }, {viewport: resolution});
+ });
+
+ ['sharing', 'security', 'theming', 'encryption', 'additional', 'tips-tricks'].forEach(function(endpoint) {
+ it('admin.' + endpoint + '.' + resolution.title, async function () {
+ return helper.takeAndCompare(this, 'index.php/settings/admin/' + endpoint, async function (page) {
+ }, {viewport: resolution, waitUntil: 'networkidle2'});
+ });
+ });
+
+ it('usermanagement.' + resolution.title, async function () {
+ return helper.takeAndCompare(this, 'index.php/settings/users', async function (page) {
+ }, {viewport: resolution});
+ });
+
+ it('usermanagement.add.' + resolution.title, async function () {
+ return helper.takeAndCompare(this, undefined, async function (page) {
+ try {
+ await page.waitForSelector('#app-navigation-toggle', {
+ visible: true,
+ timeout: 1000,
+ }).then((element) => element.click())
+ } catch (err) {}
+ let newUserButton = await page.waitForSelector('#new-user-button');
+ await newUserButton.click();
+ await helper.delay(200);
+ await page.$eval('body', function (e) {
+ $('#newusername').blur();
+ })
+ await helper.delay(100);
+ }, {viewport: resolution});
+ });
+
+ });
+});