summaryrefslogtreecommitdiffstats
path: root/apps/theming
diff options
context:
space:
mode:
Diffstat (limited to 'apps/theming')
-rw-r--r--apps/theming/appinfo/info.xml13
-rw-r--r--apps/theming/appinfo/routes.php114
-rw-r--r--apps/theming/css/default.css57
-rw-r--r--apps/theming/css/settings-admin.scss12
-rw-r--r--apps/theming/css/theming.scss28
-rw-r--r--apps/theming/fonts/OpenDyslexic-Bold.otfbin0 -> 42408 bytes
-rw-r--r--apps/theming/fonts/OpenDyslexic-Bold.ttfbin0 -> 142508 bytes
-rw-r--r--apps/theming/fonts/OpenDyslexic-Bold.woffbin0 -> 16592 bytes
-rw-r--r--apps/theming/fonts/OpenDyslexic-Regular.otfbin0 -> 41088 bytes
-rw-r--r--apps/theming/fonts/OpenDyslexic-Regular.ttfbin0 -> 138312 bytes
-rw-r--r--apps/theming/fonts/OpenDyslexic-Regular.woffbin0 -> 16636 bytes
-rw-r--r--apps/theming/img/dark-highcontrast.jpgbin0 -> 432490 bytes
-rw-r--r--apps/theming/img/dark.jpgbin0 -> 377505 bytes
-rw-r--r--apps/theming/img/default.jpgbin0 -> 391078 bytes
-rw-r--r--apps/theming/img/highcontrast.jpgbin0 -> 429052 bytes
-rw-r--r--apps/theming/img/opendyslexic.jpgbin0 -> 65130 bytes
-rw-r--r--apps/theming/js/settings-admin.js52
-rw-r--r--apps/theming/l10n/af.js2
-rw-r--r--apps/theming/l10n/af.json2
-rw-r--r--apps/theming/l10n/ar.js2
-rw-r--r--apps/theming/l10n/ar.json2
-rw-r--r--apps/theming/l10n/bg.js2
-rw-r--r--apps/theming/l10n/bg.json2
-rw-r--r--apps/theming/l10n/ca.js2
-rw-r--r--apps/theming/l10n/ca.json2
-rw-r--r--apps/theming/l10n/cs.js2
-rw-r--r--apps/theming/l10n/cs.json2
-rw-r--r--apps/theming/l10n/da.js2
-rw-r--r--apps/theming/l10n/da.json2
-rw-r--r--apps/theming/l10n/de.js2
-rw-r--r--apps/theming/l10n/de.json2
-rw-r--r--apps/theming/l10n/de_DE.js2
-rw-r--r--apps/theming/l10n/de_DE.json2
-rw-r--r--apps/theming/l10n/el.js2
-rw-r--r--apps/theming/l10n/el.json2
-rw-r--r--apps/theming/l10n/en_GB.js2
-rw-r--r--apps/theming/l10n/en_GB.json2
-rw-r--r--apps/theming/l10n/eo.js2
-rw-r--r--apps/theming/l10n/eo.json2
-rw-r--r--apps/theming/l10n/es.js2
-rw-r--r--apps/theming/l10n/es.json2
-rw-r--r--apps/theming/l10n/es_CL.js2
-rw-r--r--apps/theming/l10n/es_CL.json2
-rw-r--r--apps/theming/l10n/es_CO.js2
-rw-r--r--apps/theming/l10n/es_CO.json2
-rw-r--r--apps/theming/l10n/es_CR.js2
-rw-r--r--apps/theming/l10n/es_CR.json2
-rw-r--r--apps/theming/l10n/es_DO.js2
-rw-r--r--apps/theming/l10n/es_DO.json2
-rw-r--r--apps/theming/l10n/es_EC.js2
-rw-r--r--apps/theming/l10n/es_EC.json2
-rw-r--r--apps/theming/l10n/es_GT.js2
-rw-r--r--apps/theming/l10n/es_GT.json2
-rw-r--r--apps/theming/l10n/es_MX.js2
-rw-r--r--apps/theming/l10n/es_MX.json2
-rw-r--r--apps/theming/l10n/es_SV.js2
-rw-r--r--apps/theming/l10n/es_SV.json2
-rw-r--r--apps/theming/l10n/eu.js2
-rw-r--r--apps/theming/l10n/eu.json2
-rw-r--r--apps/theming/l10n/fi.js2
-rw-r--r--apps/theming/l10n/fi.json2
-rw-r--r--apps/theming/l10n/fr.js2
-rw-r--r--apps/theming/l10n/fr.json2
-rw-r--r--apps/theming/l10n/gl.js2
-rw-r--r--apps/theming/l10n/gl.json2
-rw-r--r--apps/theming/l10n/he.js2
-rw-r--r--apps/theming/l10n/he.json2
-rw-r--r--apps/theming/l10n/hr.js2
-rw-r--r--apps/theming/l10n/hr.json2
-rw-r--r--apps/theming/l10n/hu.js2
-rw-r--r--apps/theming/l10n/hu.json2
-rw-r--r--apps/theming/l10n/is.js2
-rw-r--r--apps/theming/l10n/is.json2
-rw-r--r--apps/theming/l10n/it.js2
-rw-r--r--apps/theming/l10n/it.json2
-rw-r--r--apps/theming/l10n/ja.js2
-rw-r--r--apps/theming/l10n/ja.json2
-rw-r--r--apps/theming/l10n/ko.js2
-rw-r--r--apps/theming/l10n/ko.json2
-rw-r--r--apps/theming/l10n/lt_LT.js2
-rw-r--r--apps/theming/l10n/lt_LT.json2
-rw-r--r--apps/theming/l10n/mk.js2
-rw-r--r--apps/theming/l10n/mk.json2
-rw-r--r--apps/theming/l10n/nb.js2
-rw-r--r--apps/theming/l10n/nb.json2
-rw-r--r--apps/theming/l10n/nl.js2
-rw-r--r--apps/theming/l10n/nl.json2
-rw-r--r--apps/theming/l10n/pl.js2
-rw-r--r--apps/theming/l10n/pl.json2
-rw-r--r--apps/theming/l10n/pt_BR.js2
-rw-r--r--apps/theming/l10n/pt_BR.json2
-rw-r--r--apps/theming/l10n/pt_PT.js2
-rw-r--r--apps/theming/l10n/pt_PT.json2
-rw-r--r--apps/theming/l10n/ru.js2
-rw-r--r--apps/theming/l10n/ru.json2
-rw-r--r--apps/theming/l10n/sc.js2
-rw-r--r--apps/theming/l10n/sc.json2
-rw-r--r--apps/theming/l10n/sk.js2
-rw-r--r--apps/theming/l10n/sk.json2
-rw-r--r--apps/theming/l10n/sl.js2
-rw-r--r--apps/theming/l10n/sl.json2
-rw-r--r--apps/theming/l10n/sr.js2
-rw-r--r--apps/theming/l10n/sr.json2
-rw-r--r--apps/theming/l10n/sv.js2
-rw-r--r--apps/theming/l10n/sv.json2
-rw-r--r--apps/theming/l10n/tr.js2
-rw-r--r--apps/theming/l10n/tr.json2
-rw-r--r--apps/theming/l10n/uk.js2
-rw-r--r--apps/theming/l10n/uk.json2
-rw-r--r--apps/theming/l10n/vi.js2
-rw-r--r--apps/theming/l10n/vi.json2
-rw-r--r--apps/theming/l10n/zh_CN.js2
-rw-r--r--apps/theming/l10n/zh_CN.json2
-rw-r--r--apps/theming/l10n/zh_HK.js2
-rw-r--r--apps/theming/l10n/zh_HK.json2
-rw-r--r--apps/theming/l10n/zh_TW.js2
-rw-r--r--apps/theming/l10n/zh_TW.json2
-rw-r--r--apps/theming/lib/Controller/ThemingController.php117
-rw-r--r--apps/theming/lib/Controller/UserThemeController.php111
-rw-r--r--apps/theming/lib/ITheme.php99
-rw-r--r--apps/theming/lib/Listener/BeforeTemplateRenderedListener.php35
-rw-r--r--apps/theming/lib/Migration/MigrateUserConfig.php123
-rw-r--r--apps/theming/lib/Service/JSDataService.php16
-rw-r--r--apps/theming/lib/Service/ThemeInjectionService.php88
-rw-r--r--apps/theming/lib/Service/ThemesService.php172
-rw-r--r--apps/theming/lib/Settings/Admin.php26
-rw-r--r--apps/theming/lib/Settings/AdminSection.php (renamed from apps/theming/lib/Settings/Section.php)20
-rw-r--r--apps/theming/lib/Settings/Personal.php93
-rw-r--r--apps/theming/lib/Settings/PersonalSection.php100
-rw-r--r--apps/theming/lib/Themes/DarkHighContrastTheme.php98
-rw-r--r--apps/theming/lib/Themes/DarkTheme.php87
-rw-r--r--apps/theming/lib/Themes/DefaultTheme.php203
-rw-r--r--apps/theming/lib/Themes/DyslexiaFont.php91
-rw-r--r--apps/theming/lib/Themes/HighContrastTheme.php91
-rw-r--r--apps/theming/lib/Util.php85
-rw-r--r--apps/theming/src/UserThemes.vue186
-rw-r--r--apps/theming/src/components/ItemPreview.vue124
-rw-r--r--apps/theming/src/settings.js32
-rw-r--r--apps/theming/templates/settings-admin.php4
-rw-r--r--apps/theming/templates/settings-personal.php26
-rw-r--r--apps/theming/tests/Controller/ThemingControllerTest.php104
-rw-r--r--apps/theming/tests/Controller/UserThemeControllerTest.php142
-rw-r--r--apps/theming/tests/Service/ThemesServiceTest.php244
-rw-r--r--apps/theming/tests/ServicesTest.php6
-rw-r--r--apps/theming/tests/Settings/AdminTest.php2
-rw-r--r--apps/theming/tests/Settings/SectionTest.php8
-rw-r--r--apps/theming/tests/Themes/DefaultThemeTest.php135
-rw-r--r--apps/theming/tests/Themes/DyslexiaFontTest.php162
-rw-r--r--apps/theming/tests/UtilTest.php9
149 files changed, 2813 insertions, 502 deletions
diff --git a/apps/theming/appinfo/info.xml b/apps/theming/appinfo/info.xml
index 3d7cabe7213..b67b461c410 100644
--- a/apps/theming/appinfo/info.xml
+++ b/apps/theming/appinfo/info.xml
@@ -5,7 +5,7 @@
<name>Theming</name>
<summary>Adjust the Nextcloud theme</summary>
<description>Adjust the Nextcloud theme</description>
- <version>1.16.0</version>
+ <version>2.0.0</version>
<licence>agpl</licence>
<author>Nextcloud</author>
<namespace>Theming</namespace>
@@ -23,8 +23,17 @@
<settings>
<admin>OCA\Theming\Settings\Admin</admin>
- <admin-section>OCA\Theming\Settings\Section</admin-section>
+ <admin-section>OCA\Theming\Settings\AdminSection</admin-section>
+ <personal>OCA\Theming\Settings\Personal</personal>
+ <personal-section>OCA\Theming\Settings\PersonalSection</personal-section>
</settings>
+
+ <repair-steps>
+ <pre-migration>
+ <step>OCA\Theming\Migration\MigrateUserConfig</step>
+ </pre-migration>
+ </repair-steps>
+
<commands>
<command>OCA\Theming\Command\UpdateConfig</command>
</commands>
diff --git a/apps/theming/appinfo/routes.php b/apps/theming/appinfo/routes.php
index 0628ade8032..fa8dde90786 100644
--- a/apps/theming/appinfo/routes.php
+++ b/apps/theming/appinfo/routes.php
@@ -27,54 +27,68 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
-return ['routes' => [
- [
- 'name' => 'Theming#updateStylesheet',
- 'url' => '/ajax/updateStylesheet',
- 'verb' => 'POST'
+return [
+ 'routes' => [
+ [
+ 'name' => 'Theming#updateStylesheet',
+ 'url' => '/ajax/updateStylesheet',
+ 'verb' => 'POST'
+ ],
+ [
+ 'name' => 'Theming#undo',
+ 'url' => '/ajax/undoChanges',
+ 'verb' => 'POST'
+ ],
+ [
+ 'name' => 'Theming#uploadImage',
+ 'url' => '/ajax/uploadImage',
+ 'verb' => 'POST'
+ ],
+ [
+ 'name' => 'Theming#getThemeStylesheet',
+ 'url' => '/theme/{themeId}.css',
+ 'verb' => 'GET',
+ ],
+ [
+ 'name' => 'Theming#getImage',
+ 'url' => '/image/{key}',
+ 'verb' => 'GET',
+ ],
+ [
+ 'name' => 'Theming#getManifest',
+ 'url' => '/manifest/{app}',
+ 'verb' => 'GET',
+ 'defaults' => ['app' => 'core']
+ ],
+ [
+ 'name' => 'Icon#getFavicon',
+ 'url' => '/favicon/{app}',
+ 'verb' => 'GET',
+ 'defaults' => ['app' => 'core'],
+ ],
+ [
+ 'name' => 'Icon#getTouchIcon',
+ 'url' => '/icon/{app}',
+ 'verb' => 'GET',
+ 'defaults' => ['app' => 'core'],
+ ],
+ [
+ 'name' => 'Icon#getThemedIcon',
+ 'url' => '/img/{app}/{image}',
+ 'verb' => 'GET',
+ 'requirements' => ['image' => '.+']
+ ],
],
- [
- 'name' => 'Theming#undo',
- 'url' => '/ajax/undoChanges',
- 'verb' => 'POST'
- ],
- [
- 'name' => 'Theming#uploadImage',
- 'url' => '/ajax/uploadImage',
- 'verb' => 'POST'
- ],
- [
- 'name' => 'Theming#getStylesheet',
- 'url' => '/styles',
- 'verb' => 'GET',
- ],
- [
- 'name' => 'Theming#getImage',
- 'url' => '/image/{key}',
- 'verb' => 'GET',
- ],
- [
- 'name' => 'Theming#getManifest',
- 'url' => '/manifest/{app}',
- 'verb' => 'GET',
- 'defaults' => ['app' => 'core']
- ],
- [
- 'name' => 'Icon#getFavicon',
- 'url' => '/favicon/{app}',
- 'verb' => 'GET',
- 'defaults' => ['app' => 'core'],
- ],
- [
- 'name' => 'Icon#getTouchIcon',
- 'url' => '/icon/{app}',
- 'verb' => 'GET',
- 'defaults' => ['app' => 'core'],
- ],
- [
- 'name' => 'Icon#getThemedIcon',
- 'url' => '/img/{app}/{image}',
- 'verb' => 'GET',
- 'requirements' => ['image' => '.+']
- ],
-]];
+ 'ocs' => [
+ [
+ 'name' => 'userTheme#enableTheme',
+ 'url' => '/api/v1/theme/{themeId}/enable',
+ 'verb' => 'PUT',
+ ],
+ [
+ 'name' => 'userTheme#disableTheme',
+ 'url' => '/api/v1/theme/{themeId}',
+ 'verb' => 'DELETE',
+ ],
+ ]
+];
diff --git a/apps/theming/css/default.css b/apps/theming/css/default.css
new file mode 100644
index 00000000000..f7bf3a20d09
--- /dev/null
+++ b/apps/theming/css/default.css
@@ -0,0 +1,57 @@
+:root {
+ --color-main-background: #ffffff;
+ --color-main-background-rgb: 255,255,255;
+ --color-main-background-translucent: rgba(var(--color-main-background-rgb), .97);
+ --gradient-main-background: var(--color-main-background) 0%, var(--color-main-background-translucent) 85%, transparent 100%;
+ --color-background-hover: #f5f5f5;
+ --color-background-dark: #ededed;
+ --color-background-darker: #dbdbdb;
+ --color-placeholder-light: #e6e6e6;
+ --color-placeholder-dark: #cccccc;
+ --color-primary: #0082c9;
+ --color-primary-text: #ffffff;
+ --color-primary-hover: #198ece;
+ --color-primary-light: #72bae1;
+ --color-primary-light-text: #0082c9;
+ --color-primary-light-hover: #0f567d;
+ --color-primary-text-dark: #ededed;
+ --color-primary-element: #0082c9;
+ --color-primary-element-hover: #198ece;
+ --color-primary-element-light: #17adff;
+ --color-primary-element-lighter: #6cb7df;
+ --color-main-text: #222222;
+ --color-text-maxcontrast: #767676;
+ --color-text-light: #222222;
+ --color-text-lighter: #767676;
+ --color-error: #e9322d;
+ --color-error-hover: #eb4642;
+ --color-warning: #eca700;
+ --color-warning-hover: #edaf19;
+ --color-success: #46ba61;
+ --color-success-hover: #58c070;
+ --color-loading-light: #cccccc;
+ --color-loading-dark: #444444;
+ --color-box-shadow-rgb: 77,77,77;
+ --color-box-shadow: rgba(var(--color-box-shadow-rgb), 0.5);
+ --color-border: #ededed;
+ --color-border-dark: #dbdbdb;
+ --font-face: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Cantarell, Ubuntu, 'Helvetica Neue', Arial, sans-serif, 'Noto Color Emoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
+ --default-font-size: 15px;
+ --animation-quick: 100ms;
+ --animation-slow: 300ms;
+ --border-radius: 3px;
+ --border-radius-large: 10px;
+ --border-radius-pill: 100px;
+ --default-line-height: 24px;
+ --header-height: 50px;
+ --navigation-width: 300px;
+ --sidebar-min-width: 300px;
+ --sidebar-max-width: 500px;
+ --list-min-width: 200px;
+ --list-max-width: 300px;
+ --header-menu-item-height: 44px;
+ --header-menu-profile-item-height: 66px;
+ --breakpoint-mobile: 1024px;
+ --primary-invert-if-bright: unset;
+ --background-invert-if-dark: unset;
+}
diff --git a/apps/theming/css/settings-admin.scss b/apps/theming/css/settings-admin.scss
index 504760d4596..5cec0e98199 100644
--- a/apps/theming/css/settings-admin.scss
+++ b/apps/theming/css/settings-admin.scss
@@ -100,6 +100,8 @@
margin-top: 10px;
margin-bottom: 20px;
cursor: pointer;
+ background-color: var(--color-primary);
+ background-image: var(--image-background, var(--image-background-plain, url('../../../core/img/background.svg'), linear-gradient(40deg, #0082c9 0%, #30b6ff 100%)));
#theming-preview-logo {
cursor: pointer;
@@ -110,6 +112,7 @@
background-position: center;
background-repeat: no-repeat;
background-size: contain;
+ background-image: var(--image-logo, url('../../../core/img/logo/logo.svg'));
}
}
@@ -125,6 +128,15 @@
background-repeat: no-repeat;
background-size: contain;
}
+
+ #theming-preview-logoheader {
+ // Only using --image-logoheader to show the custom value only
+ background-image: var(--image-logoheader);
+ }
+
+ #theming-preview-favicon {
+ background-image: var(--image-favicon);
+ }
}
/* transition effects for theming value changes */
diff --git a/apps/theming/css/theming.scss b/apps/theming/css/theming.scss
index 52337d2105f..a5b55a5a738 100644
--- a/apps/theming/css/theming.scss
+++ b/apps/theming/css/theming.scss
@@ -45,18 +45,6 @@ $invert: luma($color-primary) > 0.6;
}
}
-.nc-theming-main-background {
- background-color: $color-primary;
-}
-
-.nc-theming-main-text {
- color: $color-primary-text;
-}
-
-.nc-theming-contrast {
- color: $color-primary-text;
-}
-
@if ($invert) {
// too bright, use dark text to mix the primary
$color-primary-light: mix($color-primary, $color-main-text, 10%);
@@ -157,16 +145,6 @@ $invert: luma($color-primary) > 0.6;
}
}
-@if variable_exists('theming-favicon-mime') and $theming-favicon-mime != '' {
- #theming .advanced-option-favicon .image-preview {
- background-image: $image-favicon;
- }
-} @else {
- #theming .advanced-option-favicon .image-preview {
- background-image: none;
- }
-}
-
input.primary {
background-color: $color-primary-element;
border: 1px solid $color-primary-text;
@@ -305,9 +283,3 @@ input.primary {
border: 1px solid #ebebeb;
}
}
-
-@if ($has-legal-links == 'true') {
- footer {
- height: 92px;
- }
-}
diff --git a/apps/theming/fonts/OpenDyslexic-Bold.otf b/apps/theming/fonts/OpenDyslexic-Bold.otf
new file mode 100644
index 00000000000..4c492e2fcc2
--- /dev/null
+++ b/apps/theming/fonts/OpenDyslexic-Bold.otf
Binary files differ
diff --git a/apps/theming/fonts/OpenDyslexic-Bold.ttf b/apps/theming/fonts/OpenDyslexic-Bold.ttf
new file mode 100644
index 00000000000..7c97eb4329b
--- /dev/null
+++ b/apps/theming/fonts/OpenDyslexic-Bold.ttf
Binary files differ
diff --git a/apps/theming/fonts/OpenDyslexic-Bold.woff b/apps/theming/fonts/OpenDyslexic-Bold.woff
new file mode 100644
index 00000000000..755476f6b4a
--- /dev/null
+++ b/apps/theming/fonts/OpenDyslexic-Bold.woff
Binary files differ
diff --git a/apps/theming/fonts/OpenDyslexic-Regular.otf b/apps/theming/fonts/OpenDyslexic-Regular.otf
new file mode 100644
index 00000000000..1226d2ab281
--- /dev/null
+++ b/apps/theming/fonts/OpenDyslexic-Regular.otf
Binary files differ
diff --git a/apps/theming/fonts/OpenDyslexic-Regular.ttf b/apps/theming/fonts/OpenDyslexic-Regular.ttf
new file mode 100644
index 00000000000..e7849348cdb
--- /dev/null
+++ b/apps/theming/fonts/OpenDyslexic-Regular.ttf
Binary files differ
diff --git a/apps/theming/fonts/OpenDyslexic-Regular.woff b/apps/theming/fonts/OpenDyslexic-Regular.woff
new file mode 100644
index 00000000000..fdf9e37dd43
--- /dev/null
+++ b/apps/theming/fonts/OpenDyslexic-Regular.woff
Binary files differ
diff --git a/apps/theming/img/dark-highcontrast.jpg b/apps/theming/img/dark-highcontrast.jpg
new file mode 100644
index 00000000000..5fce8ef0851
--- /dev/null
+++ b/apps/theming/img/dark-highcontrast.jpg
Binary files differ
diff --git a/apps/theming/img/dark.jpg b/apps/theming/img/dark.jpg
new file mode 100644
index 00000000000..b207c390cfa
--- /dev/null
+++ b/apps/theming/img/dark.jpg
Binary files differ
diff --git a/apps/theming/img/default.jpg b/apps/theming/img/default.jpg
new file mode 100644
index 00000000000..ad3fafd96f2
--- /dev/null
+++ b/apps/theming/img/default.jpg
Binary files differ
diff --git a/apps/theming/img/highcontrast.jpg b/apps/theming/img/highcontrast.jpg
new file mode 100644
index 00000000000..e9087e898ee
--- /dev/null
+++ b/apps/theming/img/highcontrast.jpg
Binary files differ
diff --git a/apps/theming/img/opendyslexic.jpg b/apps/theming/img/opendyslexic.jpg
new file mode 100644
index 00000000000..f56ea33e6c4
--- /dev/null
+++ b/apps/theming/img/opendyslexic.jpg
Binary files differ
diff --git a/apps/theming/js/settings-admin.js b/apps/theming/js/settings-admin.js
index 335492fdae2..7efdab6dda4 100644
--- a/apps/theming/js/settings-admin.js
+++ b/apps/theming/js/settings-admin.js
@@ -28,9 +28,9 @@ function setThemingValue(setting, value) {
startLoading();
$.post(
OC.generateUrl('/apps/theming/ajax/updateStylesheet'), {'setting' : setting, 'value' : value}
- ).done(function(response) {
+ ).done(function() {
hideUndoButton(setting, value);
- preview(setting, value, response.data.serverCssUrl);
+ preview(setting, value);
}).fail(function(response) {
OC.msg.finishedSaving('#theming_settings_msg', response.responseJSON);
$('#theming_settings_loading').hide();
@@ -39,41 +39,31 @@ function setThemingValue(setting, value) {
function preview(setting, value, serverCssUrl) {
OC.msg.startAction('#theming_settings_msg', t('theming', 'Loading preview…'));
- var stylesheetsLoaded = 1;
- var reloadStylesheets = function(cssFile) {
- var queryString = '?reload=' + new Date().getTime();
- var url = cssFile + queryString;
- var old = $('link[href*="' + cssFile + '"]');
- var stylesheet = $("<link/>", {
- rel: "stylesheet",
- type: "text/css",
- href: url
- });
- stylesheet.load(function () {
- $(old).remove();
- stylesheetsLoaded--;
- if(stylesheetsLoaded === 0) {
- $('#theming_settings_loading').hide();
- var response = { status: 'success', data: {message: t('theming', 'Saved')}};
- OC.msg.finishedSaving('#theming_settings_msg', response);
- }
- });
- stylesheet.appendTo("head");
- };
-
- if (serverCssUrl !== undefined) {
- stylesheetsLoaded++;
- reloadStylesheets(serverCssUrl);
- }
- reloadStylesheets(OC.generateUrl('/apps/theming/styles'));
+ // Get all theming themes css links and force reload them
+ [...document.querySelectorAll('link.theme')]
+ .forEach(theme => {
+ // Only edit the clone to not remove applied one
+ var clone = theme.cloneNode()
+ var url = new URL(clone.href)
+ // Set current timestamp as cache buster
+ url.searchParams.set('v', Date.now())
+ clone.href = url.toString()
+ clone.onload = function() {
+ theme.remove()
+ }
+ document.head.append(clone)
+ })
if (setting === 'name') {
window.document.title = t('core', 'Admin') + " - " + value;
}
-
+
+ // Finish
+ $('#theming_settings_loading').hide();
+ var response = { status: 'success', data: {message: t('theming', 'Saved')}};
+ OC.msg.finishedSaving('#theming_settings_msg', response);
hideUndoButton(setting, value);
-
}
function hideUndoButton(setting, value) {
diff --git a/apps/theming/l10n/af.js b/apps/theming/l10n/af.js
index 54a53468909..1ce6ca7f39f 100644
--- a/apps/theming/l10n/af.js
+++ b/apps/theming/l10n/af.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Laai voorskou…",
- "Saved" : "Bewaar",
"Admin" : "Admin",
+ "Saved" : "Bewaar",
"a safe home for all your data" : "’n veilige tuiste vir al u data",
"Name cannot be empty" : "Naam kan nie leeg wees nie",
"The given name is too long" : "Die gegewe naam is te lank",
diff --git a/apps/theming/l10n/af.json b/apps/theming/l10n/af.json
index 92b8c2808eb..3ccb76ddbaa 100644
--- a/apps/theming/l10n/af.json
+++ b/apps/theming/l10n/af.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Laai voorskou…",
- "Saved" : "Bewaar",
"Admin" : "Admin",
+ "Saved" : "Bewaar",
"a safe home for all your data" : "’n veilige tuiste vir al u data",
"Name cannot be empty" : "Naam kan nie leeg wees nie",
"The given name is too long" : "Die gegewe naam is te lank",
diff --git a/apps/theming/l10n/ar.js b/apps/theming/l10n/ar.js
index a4a4dd7d19d..e2d5789b7ea 100644
--- a/apps/theming/l10n/ar.js
+++ b/apps/theming/l10n/ar.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "جار تحميل المعاينة ...",
- "Saved" : "تم الحفظ",
"Admin" : "ادارة",
+ "Saved" : "تم الحفظ",
"a safe home for all your data" : "مكان آمن لكل معلوماتك",
"The given name is too long" : "هذا الاسم أطول مما يجب",
"The given web address is too long" : "هذا العنوان أطول مما يجب",
diff --git a/apps/theming/l10n/ar.json b/apps/theming/l10n/ar.json
index d64edb00661..17c011e2572 100644
--- a/apps/theming/l10n/ar.json
+++ b/apps/theming/l10n/ar.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "جار تحميل المعاينة ...",
- "Saved" : "تم الحفظ",
"Admin" : "ادارة",
+ "Saved" : "تم الحفظ",
"a safe home for all your data" : "مكان آمن لكل معلوماتك",
"The given name is too long" : "هذا الاسم أطول مما يجب",
"The given web address is too long" : "هذا العنوان أطول مما يجب",
diff --git a/apps/theming/l10n/bg.js b/apps/theming/l10n/bg.js
index 1f18348c791..d50117bcbd5 100644
--- a/apps/theming/l10n/bg.js
+++ b/apps/theming/l10n/bg.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Визуализацията се зарежда …",
- "Saved" : "Запазено",
"Admin" : "Админ",
+ "Saved" : "Запазено",
"a safe home for all your data" : "безопасен дом за всички ваши данни",
"Name cannot be empty" : "Името не може да бъде празно",
"The given name is too long" : "Името е прекалено дълго",
diff --git a/apps/theming/l10n/bg.json b/apps/theming/l10n/bg.json
index 2de7ad5139c..551b6e679b0 100644
--- a/apps/theming/l10n/bg.json
+++ b/apps/theming/l10n/bg.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Визуализацията се зарежда …",
- "Saved" : "Запазено",
"Admin" : "Админ",
+ "Saved" : "Запазено",
"a safe home for all your data" : "безопасен дом за всички ваши данни",
"Name cannot be empty" : "Името не може да бъде празно",
"The given name is too long" : "Името е прекалено дълго",
diff --git a/apps/theming/l10n/ca.js b/apps/theming/l10n/ca.js
index f6d65ef5172..5c3cdbe8acc 100644
--- a/apps/theming/l10n/ca.js
+++ b/apps/theming/l10n/ca.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "S'està carregant la previsualització...",
- "Saved" : "Desat",
"Admin" : "Administrador",
+ "Saved" : "Desat",
"a safe home for all your data" : "Un lloc segur per a les vostres dades",
"Name cannot be empty" : "El nom no pot estar buit",
"The given name is too long" : "El nom proporcionat és massa llarg",
diff --git a/apps/theming/l10n/ca.json b/apps/theming/l10n/ca.json
index c655ba85373..9dddf95f283 100644
--- a/apps/theming/l10n/ca.json
+++ b/apps/theming/l10n/ca.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "S'està carregant la previsualització...",
- "Saved" : "Desat",
"Admin" : "Administrador",
+ "Saved" : "Desat",
"a safe home for all your data" : "Un lloc segur per a les vostres dades",
"Name cannot be empty" : "El nom no pot estar buit",
"The given name is too long" : "El nom proporcionat és massa llarg",
diff --git a/apps/theming/l10n/cs.js b/apps/theming/l10n/cs.js
index ecf2be68900..a1c7471957f 100644
--- a/apps/theming/l10n/cs.js
+++ b/apps/theming/l10n/cs.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Načítání náhledu…",
- "Saved" : "Uloženo",
"Admin" : "Správa",
+ "Saved" : "Uloženo",
"a safe home for all your data" : "bezpečný domov pro všechna vaše data",
"Name cannot be empty" : "Je třeba zadat název",
"The given name is too long" : "Zadaný název je příliš dlouhý",
diff --git a/apps/theming/l10n/cs.json b/apps/theming/l10n/cs.json
index b975ce99a2c..c07bf7303b6 100644
--- a/apps/theming/l10n/cs.json
+++ b/apps/theming/l10n/cs.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Načítání náhledu…",
- "Saved" : "Uloženo",
"Admin" : "Správa",
+ "Saved" : "Uloženo",
"a safe home for all your data" : "bezpečný domov pro všechna vaše data",
"Name cannot be empty" : "Je třeba zadat název",
"The given name is too long" : "Zadaný název je příliš dlouhý",
diff --git a/apps/theming/l10n/da.js b/apps/theming/l10n/da.js
index 326f7da5341..e28b0a034c3 100644
--- a/apps/theming/l10n/da.js
+++ b/apps/theming/l10n/da.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Indlæser forhåndsvisning...",
- "Saved" : "Gemt",
"Admin" : "Admin",
+ "Saved" : "Gemt",
"a safe home for all your data" : "Et sikket hjem til alt dit data",
"Name cannot be empty" : "Navn må ikke være tomt",
"The given name is too long" : "Navnet er for langt",
diff --git a/apps/theming/l10n/da.json b/apps/theming/l10n/da.json
index b38a9ecf3fc..0fd4e3984ee 100644
--- a/apps/theming/l10n/da.json
+++ b/apps/theming/l10n/da.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Indlæser forhåndsvisning...",
- "Saved" : "Gemt",
"Admin" : "Admin",
+ "Saved" : "Gemt",
"a safe home for all your data" : "Et sikket hjem til alt dit data",
"Name cannot be empty" : "Navn må ikke være tomt",
"The given name is too long" : "Navnet er for langt",
diff --git a/apps/theming/l10n/de.js b/apps/theming/l10n/de.js
index 1112de27fc5..d11ee884e32 100644
--- a/apps/theming/l10n/de.js
+++ b/apps/theming/l10n/de.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Lade Vorschau…",
- "Saved" : "Gespeichert",
"Admin" : "Administrator",
+ "Saved" : "Gespeichert",
"a safe home for all your data" : "Ein sicheres Zuhause für Deine Daten",
"Name cannot be empty" : "Der Name darf nicht leer sein",
"The given name is too long" : "Der eingegebene Name ist zu lang",
diff --git a/apps/theming/l10n/de.json b/apps/theming/l10n/de.json
index d1d6bea754d..6350d384ae2 100644
--- a/apps/theming/l10n/de.json
+++ b/apps/theming/l10n/de.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Lade Vorschau…",
- "Saved" : "Gespeichert",
"Admin" : "Administrator",
+ "Saved" : "Gespeichert",
"a safe home for all your data" : "Ein sicheres Zuhause für Deine Daten",
"Name cannot be empty" : "Der Name darf nicht leer sein",
"The given name is too long" : "Der eingegebene Name ist zu lang",
diff --git a/apps/theming/l10n/de_DE.js b/apps/theming/l10n/de_DE.js
index f43c22b571c..0d11e013dc9 100644
--- a/apps/theming/l10n/de_DE.js
+++ b/apps/theming/l10n/de_DE.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Lade Vorschau…",
- "Saved" : "Gespeichert",
"Admin" : "Administrator",
+ "Saved" : "Gespeichert",
"a safe home for all your data" : "Ein sicheres Zuhause für Ihre Daten",
"Name cannot be empty" : "Der Name darf nicht leer sein",
"The given name is too long" : "Der eingegebene Name ist zu lang",
diff --git a/apps/theming/l10n/de_DE.json b/apps/theming/l10n/de_DE.json
index bed88aca6e9..48505e4a8d2 100644
--- a/apps/theming/l10n/de_DE.json
+++ b/apps/theming/l10n/de_DE.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Lade Vorschau…",
- "Saved" : "Gespeichert",
"Admin" : "Administrator",
+ "Saved" : "Gespeichert",
"a safe home for all your data" : "Ein sicheres Zuhause für Ihre Daten",
"Name cannot be empty" : "Der Name darf nicht leer sein",
"The given name is too long" : "Der eingegebene Name ist zu lang",
diff --git a/apps/theming/l10n/el.js b/apps/theming/l10n/el.js
index f286b0d2877..b5c06e1e9f8 100644
--- a/apps/theming/l10n/el.js
+++ b/apps/theming/l10n/el.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Φόρτωση προεπισκόπησης ...",
- "Saved" : "Αποθηκεύτηκαν",
"Admin" : "Διαχειριστής",
+ "Saved" : "Αποθηκεύτηκαν",
"a safe home for all your data" : "μια ασφαλής τοποθεσία για όλα σας τα δεδομένα",
"Name cannot be empty" : "Το όνομα δεν μπορεί να είναι κενό",
"The given name is too long" : "Το όνομα που δόθηκε είναι πολύ μεγάλο",
diff --git a/apps/theming/l10n/el.json b/apps/theming/l10n/el.json
index f2a1f981941..37ff3b6605d 100644
--- a/apps/theming/l10n/el.json
+++ b/apps/theming/l10n/el.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Φόρτωση προεπισκόπησης ...",
- "Saved" : "Αποθηκεύτηκαν",
"Admin" : "Διαχειριστής",
+ "Saved" : "Αποθηκεύτηκαν",
"a safe home for all your data" : "μια ασφαλής τοποθεσία για όλα σας τα δεδομένα",
"Name cannot be empty" : "Το όνομα δεν μπορεί να είναι κενό",
"The given name is too long" : "Το όνομα που δόθηκε είναι πολύ μεγάλο",
diff --git a/apps/theming/l10n/en_GB.js b/apps/theming/l10n/en_GB.js
index a3cd12c800a..d723f5755f4 100644
--- a/apps/theming/l10n/en_GB.js
+++ b/apps/theming/l10n/en_GB.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Loading preview…",
- "Saved" : "Saved",
"Admin" : "Admin",
+ "Saved" : "Saved",
"a safe home for all your data" : "a safe home for all your data",
"Name cannot be empty" : "Name cannot be empty",
"The given name is too long" : "The given name is too long",
diff --git a/apps/theming/l10n/en_GB.json b/apps/theming/l10n/en_GB.json
index c910816330f..ff88d63c4a6 100644
--- a/apps/theming/l10n/en_GB.json
+++ b/apps/theming/l10n/en_GB.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Loading preview…",
- "Saved" : "Saved",
"Admin" : "Admin",
+ "Saved" : "Saved",
"a safe home for all your data" : "a safe home for all your data",
"Name cannot be empty" : "Name cannot be empty",
"The given name is too long" : "The given name is too long",
diff --git a/apps/theming/l10n/eo.js b/apps/theming/l10n/eo.js
index 2a06038ccf8..9bd567191ab 100644
--- a/apps/theming/l10n/eo.js
+++ b/apps/theming/l10n/eo.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Ŝargo de antaŭrigardo...",
- "Saved" : "Konservita",
"Admin" : "Administranto",
+ "Saved" : "Konservita",
"a safe home for all your data" : "sekura hejmo por ĉiuj viaj datumoj",
"Name cannot be empty" : "Nomo ne povas mapleni",
"The given name is too long" : "Tiu nomo tro longas",
diff --git a/apps/theming/l10n/eo.json b/apps/theming/l10n/eo.json
index 244d6d626d1..505003c3db6 100644
--- a/apps/theming/l10n/eo.json
+++ b/apps/theming/l10n/eo.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Ŝargo de antaŭrigardo...",
- "Saved" : "Konservita",
"Admin" : "Administranto",
+ "Saved" : "Konservita",
"a safe home for all your data" : "sekura hejmo por ĉiuj viaj datumoj",
"Name cannot be empty" : "Nomo ne povas mapleni",
"The given name is too long" : "Tiu nomo tro longas",
diff --git a/apps/theming/l10n/es.js b/apps/theming/l10n/es.js
index 3f4ca62e3d0..907d13ea060 100644
--- a/apps/theming/l10n/es.js
+++ b/apps/theming/l10n/es.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Cargando previsualización...",
- "Saved" : "Guardado",
"Admin" : "Administración",
+ "Saved" : "Guardado",
"a safe home for all your data" : "un hogar seguro para todos tus datos",
"Name cannot be empty" : "El nombre no puede estar vacío",
"The given name is too long" : "El nombre provisto es demasiado largo",
diff --git a/apps/theming/l10n/es.json b/apps/theming/l10n/es.json
index 456be5e812c..97632a17564 100644
--- a/apps/theming/l10n/es.json
+++ b/apps/theming/l10n/es.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Cargando previsualización...",
- "Saved" : "Guardado",
"Admin" : "Administración",
+ "Saved" : "Guardado",
"a safe home for all your data" : "un hogar seguro para todos tus datos",
"Name cannot be empty" : "El nombre no puede estar vacío",
"The given name is too long" : "El nombre provisto es demasiado largo",
diff --git a/apps/theming/l10n/es_CL.js b/apps/theming/l10n/es_CL.js
index 45f61b53d56..e792e1ee881 100644
--- a/apps/theming/l10n/es_CL.js
+++ b/apps/theming/l10n/es_CL.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Cargando vista previa...",
- "Saved" : "Guardado",
"Admin" : "Administración",
+ "Saved" : "Guardado",
"a safe home for all your data" : "un lugar seguro para todos tus datos",
"Name cannot be empty" : "El nombre no puede estar en blanco",
"The given name is too long" : "El nombre dado es demasiado largo",
diff --git a/apps/theming/l10n/es_CL.json b/apps/theming/l10n/es_CL.json
index dda0a555617..8a9ee91e18e 100644
--- a/apps/theming/l10n/es_CL.json
+++ b/apps/theming/l10n/es_CL.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Cargando vista previa...",
- "Saved" : "Guardado",
"Admin" : "Administración",
+ "Saved" : "Guardado",
"a safe home for all your data" : "un lugar seguro para todos tus datos",
"Name cannot be empty" : "El nombre no puede estar en blanco",
"The given name is too long" : "El nombre dado es demasiado largo",
diff --git a/apps/theming/l10n/es_CO.js b/apps/theming/l10n/es_CO.js
index 45f61b53d56..e792e1ee881 100644
--- a/apps/theming/l10n/es_CO.js
+++ b/apps/theming/l10n/es_CO.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Cargando vista previa...",
- "Saved" : "Guardado",
"Admin" : "Administración",
+ "Saved" : "Guardado",
"a safe home for all your data" : "un lugar seguro para todos tus datos",
"Name cannot be empty" : "El nombre no puede estar en blanco",
"The given name is too long" : "El nombre dado es demasiado largo",
diff --git a/apps/theming/l10n/es_CO.json b/apps/theming/l10n/es_CO.json
index dda0a555617..8a9ee91e18e 100644
--- a/apps/theming/l10n/es_CO.json
+++ b/apps/theming/l10n/es_CO.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Cargando vista previa...",
- "Saved" : "Guardado",
"Admin" : "Administración",
+ "Saved" : "Guardado",
"a safe home for all your data" : "un lugar seguro para todos tus datos",
"Name cannot be empty" : "El nombre no puede estar en blanco",
"The given name is too long" : "El nombre dado es demasiado largo",
diff --git a/apps/theming/l10n/es_CR.js b/apps/theming/l10n/es_CR.js
index 45f61b53d56..e792e1ee881 100644
--- a/apps/theming/l10n/es_CR.js
+++ b/apps/theming/l10n/es_CR.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Cargando vista previa...",
- "Saved" : "Guardado",
"Admin" : "Administración",
+ "Saved" : "Guardado",
"a safe home for all your data" : "un lugar seguro para todos tus datos",
"Name cannot be empty" : "El nombre no puede estar en blanco",
"The given name is too long" : "El nombre dado es demasiado largo",
diff --git a/apps/theming/l10n/es_CR.json b/apps/theming/l10n/es_CR.json
index dda0a555617..8a9ee91e18e 100644
--- a/apps/theming/l10n/es_CR.json
+++ b/apps/theming/l10n/es_CR.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Cargando vista previa...",
- "Saved" : "Guardado",
"Admin" : "Administración",
+ "Saved" : "Guardado",
"a safe home for all your data" : "un lugar seguro para todos tus datos",
"Name cannot be empty" : "El nombre no puede estar en blanco",
"The given name is too long" : "El nombre dado es demasiado largo",
diff --git a/apps/theming/l10n/es_DO.js b/apps/theming/l10n/es_DO.js
index 45f61b53d56..e792e1ee881 100644
--- a/apps/theming/l10n/es_DO.js
+++ b/apps/theming/l10n/es_DO.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Cargando vista previa...",
- "Saved" : "Guardado",
"Admin" : "Administración",
+ "Saved" : "Guardado",
"a safe home for all your data" : "un lugar seguro para todos tus datos",
"Name cannot be empty" : "El nombre no puede estar en blanco",
"The given name is too long" : "El nombre dado es demasiado largo",
diff --git a/apps/theming/l10n/es_DO.json b/apps/theming/l10n/es_DO.json
index dda0a555617..8a9ee91e18e 100644
--- a/apps/theming/l10n/es_DO.json
+++ b/apps/theming/l10n/es_DO.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Cargando vista previa...",
- "Saved" : "Guardado",
"Admin" : "Administración",
+ "Saved" : "Guardado",
"a safe home for all your data" : "un lugar seguro para todos tus datos",
"Name cannot be empty" : "El nombre no puede estar en blanco",
"The given name is too long" : "El nombre dado es demasiado largo",
diff --git a/apps/theming/l10n/es_EC.js b/apps/theming/l10n/es_EC.js
index 375fe8cd87b..8d75dc6c958 100644
--- a/apps/theming/l10n/es_EC.js
+++ b/apps/theming/l10n/es_EC.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Cargando vista previa...",
- "Saved" : "Guardado",
"Admin" : "Administración",
+ "Saved" : "Guardado",
"a safe home for all your data" : "un lugar seguro para todos tus datos",
"Name cannot be empty" : "El nombre no puede estar en blanco",
"The given name is too long" : "El nombre dado es demasiado largo",
diff --git a/apps/theming/l10n/es_EC.json b/apps/theming/l10n/es_EC.json
index 6cf3e17a820..9fd2aa514a4 100644
--- a/apps/theming/l10n/es_EC.json
+++ b/apps/theming/l10n/es_EC.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Cargando vista previa...",
- "Saved" : "Guardado",
"Admin" : "Administración",
+ "Saved" : "Guardado",
"a safe home for all your data" : "un lugar seguro para todos tus datos",
"Name cannot be empty" : "El nombre no puede estar en blanco",
"The given name is too long" : "El nombre dado es demasiado largo",
diff --git a/apps/theming/l10n/es_GT.js b/apps/theming/l10n/es_GT.js
index 45f61b53d56..e792e1ee881 100644
--- a/apps/theming/l10n/es_GT.js
+++ b/apps/theming/l10n/es_GT.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Cargando vista previa...",
- "Saved" : "Guardado",
"Admin" : "Administración",
+ "Saved" : "Guardado",
"a safe home for all your data" : "un lugar seguro para todos tus datos",
"Name cannot be empty" : "El nombre no puede estar en blanco",
"The given name is too long" : "El nombre dado es demasiado largo",
diff --git a/apps/theming/l10n/es_GT.json b/apps/theming/l10n/es_GT.json
index dda0a555617..8a9ee91e18e 100644
--- a/apps/theming/l10n/es_GT.json
+++ b/apps/theming/l10n/es_GT.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Cargando vista previa...",
- "Saved" : "Guardado",
"Admin" : "Administración",
+ "Saved" : "Guardado",
"a safe home for all your data" : "un lugar seguro para todos tus datos",
"Name cannot be empty" : "El nombre no puede estar en blanco",
"The given name is too long" : "El nombre dado es demasiado largo",
diff --git a/apps/theming/l10n/es_MX.js b/apps/theming/l10n/es_MX.js
index 00baf5ee294..8bb5b9fb679 100644
--- a/apps/theming/l10n/es_MX.js
+++ b/apps/theming/l10n/es_MX.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Cargando vista previa...",
- "Saved" : "Guardado",
"Admin" : "Administración",
+ "Saved" : "Guardado",
"a safe home for all your data" : "un lugar seguro para todos tus datos",
"Name cannot be empty" : "El nombre no puede estar en blanco",
"The given name is too long" : "El nombre dado es demasiado largo",
diff --git a/apps/theming/l10n/es_MX.json b/apps/theming/l10n/es_MX.json
index fa36ae0b5c0..22921776bc4 100644
--- a/apps/theming/l10n/es_MX.json
+++ b/apps/theming/l10n/es_MX.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Cargando vista previa...",
- "Saved" : "Guardado",
"Admin" : "Administración",
+ "Saved" : "Guardado",
"a safe home for all your data" : "un lugar seguro para todos tus datos",
"Name cannot be empty" : "El nombre no puede estar en blanco",
"The given name is too long" : "El nombre dado es demasiado largo",
diff --git a/apps/theming/l10n/es_SV.js b/apps/theming/l10n/es_SV.js
index 45f61b53d56..e792e1ee881 100644
--- a/apps/theming/l10n/es_SV.js
+++ b/apps/theming/l10n/es_SV.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Cargando vista previa...",
- "Saved" : "Guardado",
"Admin" : "Administración",
+ "Saved" : "Guardado",
"a safe home for all your data" : "un lugar seguro para todos tus datos",
"Name cannot be empty" : "El nombre no puede estar en blanco",
"The given name is too long" : "El nombre dado es demasiado largo",
diff --git a/apps/theming/l10n/es_SV.json b/apps/theming/l10n/es_SV.json
index dda0a555617..8a9ee91e18e 100644
--- a/apps/theming/l10n/es_SV.json
+++ b/apps/theming/l10n/es_SV.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Cargando vista previa...",
- "Saved" : "Guardado",
"Admin" : "Administración",
+ "Saved" : "Guardado",
"a safe home for all your data" : "un lugar seguro para todos tus datos",
"Name cannot be empty" : "El nombre no puede estar en blanco",
"The given name is too long" : "El nombre dado es demasiado largo",
diff --git a/apps/theming/l10n/eu.js b/apps/theming/l10n/eu.js
index d8ee59ef841..bb274233006 100644
--- a/apps/theming/l10n/eu.js
+++ b/apps/theming/l10n/eu.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Aurreikuspena kargatzen...",
- "Saved" : "Gordeta",
"Admin" : "Administratzailea",
+ "Saved" : "Gordeta",
"a safe home for all your data" : "zure datu guztientzako toki segurua",
"Name cannot be empty" : "Izena ezin da hutsik egon",
"The given name is too long" : "Idatzitako izena luzeegia da",
diff --git a/apps/theming/l10n/eu.json b/apps/theming/l10n/eu.json
index df63e11dbc4..e0d0a74ad93 100644
--- a/apps/theming/l10n/eu.json
+++ b/apps/theming/l10n/eu.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Aurreikuspena kargatzen...",
- "Saved" : "Gordeta",
"Admin" : "Administratzailea",
+ "Saved" : "Gordeta",
"a safe home for all your data" : "zure datu guztientzako toki segurua",
"Name cannot be empty" : "Izena ezin da hutsik egon",
"The given name is too long" : "Idatzitako izena luzeegia da",
diff --git a/apps/theming/l10n/fi.js b/apps/theming/l10n/fi.js
index 5ee32c930b2..6bbde4011c3 100644
--- a/apps/theming/l10n/fi.js
+++ b/apps/theming/l10n/fi.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Ladataan esikatselua…",
- "Saved" : "Tallennettu",
"Admin" : "Ylläpito",
+ "Saved" : "Tallennettu",
"a safe home for all your data" : "turvallinen koti kaikille tiedostoillesi",
"Name cannot be empty" : "Nimi ei voi olla tyhjä",
"The given name is too long" : "Nimi on liian pitkä",
diff --git a/apps/theming/l10n/fi.json b/apps/theming/l10n/fi.json
index afef679a8a0..9249cf9d9e6 100644
--- a/apps/theming/l10n/fi.json
+++ b/apps/theming/l10n/fi.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Ladataan esikatselua…",
- "Saved" : "Tallennettu",
"Admin" : "Ylläpito",
+ "Saved" : "Tallennettu",
"a safe home for all your data" : "turvallinen koti kaikille tiedostoillesi",
"Name cannot be empty" : "Nimi ei voi olla tyhjä",
"The given name is too long" : "Nimi on liian pitkä",
diff --git a/apps/theming/l10n/fr.js b/apps/theming/l10n/fr.js
index 1165934ccec..d2df382e3a0 100644
--- a/apps/theming/l10n/fr.js
+++ b/apps/theming/l10n/fr.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Chargement de la prévisualisation...",
- "Saved" : "Enregistré",
"Admin" : "Administration",
+ "Saved" : "Enregistré",
"a safe home for all your data" : "un lieu sûr pour toutes vos données",
"Name cannot be empty" : "Le nom ne peut pas être vide",
"The given name is too long" : "Le nom donné est trop long",
diff --git a/apps/theming/l10n/fr.json b/apps/theming/l10n/fr.json
index 8e0e69e3f03..eec01f93031 100644
--- a/apps/theming/l10n/fr.json
+++ b/apps/theming/l10n/fr.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Chargement de la prévisualisation...",
- "Saved" : "Enregistré",
"Admin" : "Administration",
+ "Saved" : "Enregistré",
"a safe home for all your data" : "un lieu sûr pour toutes vos données",
"Name cannot be empty" : "Le nom ne peut pas être vide",
"The given name is too long" : "Le nom donné est trop long",
diff --git a/apps/theming/l10n/gl.js b/apps/theming/l10n/gl.js
index 1f16f903955..2d66e7e54ed 100644
--- a/apps/theming/l10n/gl.js
+++ b/apps/theming/l10n/gl.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Cargando a vista previa…",
- "Saved" : "Gardado",
"Admin" : "Administración",
+ "Saved" : "Gardado",
"a safe home for all your data" : "un lugar seguro para todos os seus datos",
"Name cannot be empty" : "O nome non pode estar baleiro",
"The given name is too long" : "O nome indicado é longo de máis",
diff --git a/apps/theming/l10n/gl.json b/apps/theming/l10n/gl.json
index 4b606083beb..ca69c9209f6 100644
--- a/apps/theming/l10n/gl.json
+++ b/apps/theming/l10n/gl.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Cargando a vista previa…",
- "Saved" : "Gardado",
"Admin" : "Administración",
+ "Saved" : "Gardado",
"a safe home for all your data" : "un lugar seguro para todos os seus datos",
"Name cannot be empty" : "O nome non pode estar baleiro",
"The given name is too long" : "O nome indicado é longo de máis",
diff --git a/apps/theming/l10n/he.js b/apps/theming/l10n/he.js
index 32bcd089e73..4f27b7cb7ff 100644
--- a/apps/theming/l10n/he.js
+++ b/apps/theming/l10n/he.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "תצוגה מקדימה נטענת…",
- "Saved" : "נשמר",
"Admin" : "ניהול",
+ "Saved" : "נשמר",
"a safe home for all your data" : "מקום בטוח לכל הנתונים שלך",
"Name cannot be empty" : "השם לא יכול להישאר ריק",
"The given name is too long" : "השם שסופק ארוך מדי",
diff --git a/apps/theming/l10n/he.json b/apps/theming/l10n/he.json
index c8f4729d00c..f0fad65f4b2 100644
--- a/apps/theming/l10n/he.json
+++ b/apps/theming/l10n/he.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "תצוגה מקדימה נטענת…",
- "Saved" : "נשמר",
"Admin" : "ניהול",
+ "Saved" : "נשמר",
"a safe home for all your data" : "מקום בטוח לכל הנתונים שלך",
"Name cannot be empty" : "השם לא יכול להישאר ריק",
"The given name is too long" : "השם שסופק ארוך מדי",
diff --git a/apps/theming/l10n/hr.js b/apps/theming/l10n/hr.js
index 90b3bd43ad0..6ec9c6f57ba 100644
--- a/apps/theming/l10n/hr.js
+++ b/apps/theming/l10n/hr.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Učitavanje pretpregleda...",
- "Saved" : "Spremljeno",
"Admin" : "Administrator",
+ "Saved" : "Spremljeno",
"a safe home for all your data" : "siguran dom za sve vaše podatke",
"Name cannot be empty" : "Naziv ne može biti prazan",
"The given name is too long" : "Naziv je predug",
diff --git a/apps/theming/l10n/hr.json b/apps/theming/l10n/hr.json
index 02a814fee6a..222ad1b1d28 100644
--- a/apps/theming/l10n/hr.json
+++ b/apps/theming/l10n/hr.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Učitavanje pretpregleda...",
- "Saved" : "Spremljeno",
"Admin" : "Administrator",
+ "Saved" : "Spremljeno",
"a safe home for all your data" : "siguran dom za sve vaše podatke",
"Name cannot be empty" : "Naziv ne može biti prazan",
"The given name is too long" : "Naziv je predug",
diff --git a/apps/theming/l10n/hu.js b/apps/theming/l10n/hu.js
index 51c55f8baf8..70be5221004 100644
--- a/apps/theming/l10n/hu.js
+++ b/apps/theming/l10n/hu.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Előnézet betöltése…",
- "Saved" : "Mentve",
"Admin" : "Rendszergazda",
+ "Saved" : "Mentve",
"a safe home for all your data" : "biztonságos hely az adatai számára",
"Name cannot be empty" : "A név nem lehet üres",
"The given name is too long" : "A megadott név túl hosszú",
diff --git a/apps/theming/l10n/hu.json b/apps/theming/l10n/hu.json
index befcea66969..2f6afa6908d 100644
--- a/apps/theming/l10n/hu.json
+++ b/apps/theming/l10n/hu.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Előnézet betöltése…",
- "Saved" : "Mentve",
"Admin" : "Rendszergazda",
+ "Saved" : "Mentve",
"a safe home for all your data" : "biztonságos hely az adatai számára",
"Name cannot be empty" : "A név nem lehet üres",
"The given name is too long" : "A megadott név túl hosszú",
diff --git a/apps/theming/l10n/is.js b/apps/theming/l10n/is.js
index b1e26aee575..1741e9849fa 100644
--- a/apps/theming/l10n/is.js
+++ b/apps/theming/l10n/is.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Hleð inn forskoðun...",
- "Saved" : "Vistað",
"Admin" : "Stjórnandi",
+ "Saved" : "Vistað",
"a safe home for all your data" : "öruggur staður fyrir öll gögnin þín",
"Name cannot be empty" : "Heiti má ekki vera tómt",
"The given name is too long" : "Uppgefið nafn er of langt",
diff --git a/apps/theming/l10n/is.json b/apps/theming/l10n/is.json
index 198a1406c10..ac98bee02dc 100644
--- a/apps/theming/l10n/is.json
+++ b/apps/theming/l10n/is.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Hleð inn forskoðun...",
- "Saved" : "Vistað",
"Admin" : "Stjórnandi",
+ "Saved" : "Vistað",
"a safe home for all your data" : "öruggur staður fyrir öll gögnin þín",
"Name cannot be empty" : "Heiti má ekki vera tómt",
"The given name is too long" : "Uppgefið nafn er of langt",
diff --git a/apps/theming/l10n/it.js b/apps/theming/l10n/it.js
index 1383eb6a481..20e1751c31b 100644
--- a/apps/theming/l10n/it.js
+++ b/apps/theming/l10n/it.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Caricamento anteprima...",
- "Saved" : "Salvato",
"Admin" : "Amministratore",
+ "Saved" : "Salvato",
"a safe home for all your data" : "un posto sicuro per tutti i tuoi dati",
"Name cannot be empty" : "Il nome non può essere vuoto",
"The given name is too long" : "Questo nome è troppo lungo",
diff --git a/apps/theming/l10n/it.json b/apps/theming/l10n/it.json
index 547b7eb8c05..d05c36304f7 100644
--- a/apps/theming/l10n/it.json
+++ b/apps/theming/l10n/it.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Caricamento anteprima...",
- "Saved" : "Salvato",
"Admin" : "Amministratore",
+ "Saved" : "Salvato",
"a safe home for all your data" : "un posto sicuro per tutti i tuoi dati",
"Name cannot be empty" : "Il nome non può essere vuoto",
"The given name is too long" : "Questo nome è troppo lungo",
diff --git a/apps/theming/l10n/ja.js b/apps/theming/l10n/ja.js
index 1c22ea2f767..03d1dfcdbd2 100644
--- a/apps/theming/l10n/ja.js
+++ b/apps/theming/l10n/ja.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "プレビューを読み込み中...",
- "Saved" : "保存済み",
"Admin" : "管理者",
+ "Saved" : "保存済み",
"a safe home for all your data" : "あなたのすべてのデータを安全に保管する場所",
"Name cannot be empty" : "名前は空にできません",
"The given name is too long" : "名前が長すぎます",
diff --git a/apps/theming/l10n/ja.json b/apps/theming/l10n/ja.json
index cbff85dd37c..732625eeda0 100644
--- a/apps/theming/l10n/ja.json
+++ b/apps/theming/l10n/ja.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "プレビューを読み込み中...",
- "Saved" : "保存済み",
"Admin" : "管理者",
+ "Saved" : "保存済み",
"a safe home for all your data" : "あなたのすべてのデータを安全に保管する場所",
"Name cannot be empty" : "名前は空にできません",
"The given name is too long" : "名前が長すぎます",
diff --git a/apps/theming/l10n/ko.js b/apps/theming/l10n/ko.js
index 20239d97f5b..ad601859b84 100644
--- a/apps/theming/l10n/ko.js
+++ b/apps/theming/l10n/ko.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "미리 보기 불러오는 중…",
- "Saved" : "저장됨",
"Admin" : "관리자",
+ "Saved" : "저장됨",
"a safe home for all your data" : "내 모든 데이터 안전 저장소",
"Name cannot be empty" : "이름이 비어 있을 수 없음",
"The given name is too long" : "입력한 이름이 너무 김",
diff --git a/apps/theming/l10n/ko.json b/apps/theming/l10n/ko.json
index bf759f60bd4..5ae6f61665c 100644
--- a/apps/theming/l10n/ko.json
+++ b/apps/theming/l10n/ko.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "미리 보기 불러오는 중…",
- "Saved" : "저장됨",
"Admin" : "관리자",
+ "Saved" : "저장됨",
"a safe home for all your data" : "내 모든 데이터 안전 저장소",
"Name cannot be empty" : "이름이 비어 있을 수 없음",
"The given name is too long" : "입력한 이름이 너무 김",
diff --git a/apps/theming/l10n/lt_LT.js b/apps/theming/l10n/lt_LT.js
index d0d53528c1b..1df56d8fc9c 100644
--- a/apps/theming/l10n/lt_LT.js
+++ b/apps/theming/l10n/lt_LT.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Įkeliama peržiūra…",
- "Saved" : "Įrašyta",
"Admin" : "Administravimas",
+ "Saved" : "Įrašyta",
"a safe home for all your data" : "saugūs namai visiems jūsų duomenims",
"Name cannot be empty" : "Pavadinimas negali būti tuščias",
"The given name is too long" : "Nurodytas pavadinimas yra per ilgas",
diff --git a/apps/theming/l10n/lt_LT.json b/apps/theming/l10n/lt_LT.json
index 78649cf2fa3..137a319693c 100644
--- a/apps/theming/l10n/lt_LT.json
+++ b/apps/theming/l10n/lt_LT.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Įkeliama peržiūra…",
- "Saved" : "Įrašyta",
"Admin" : "Administravimas",
+ "Saved" : "Įrašyta",
"a safe home for all your data" : "saugūs namai visiems jūsų duomenims",
"Name cannot be empty" : "Pavadinimas negali būti tuščias",
"The given name is too long" : "Nurodytas pavadinimas yra per ilgas",
diff --git a/apps/theming/l10n/mk.js b/apps/theming/l10n/mk.js
index e91616d90d1..7be50dac690 100644
--- a/apps/theming/l10n/mk.js
+++ b/apps/theming/l10n/mk.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Се вчитува прегледот...",
- "Saved" : "Зачувано",
"Admin" : "Администратор",
+ "Saved" : "Зачувано",
"a safe home for all your data" : "безбеден дом за сите ваши податоци",
"Name cannot be empty" : "Името неможе да биде празно",
"The given name is too long" : "Зададеното име е премногу долго",
diff --git a/apps/theming/l10n/mk.json b/apps/theming/l10n/mk.json
index 328106ce60d..bd867630741 100644
--- a/apps/theming/l10n/mk.json
+++ b/apps/theming/l10n/mk.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Се вчитува прегледот...",
- "Saved" : "Зачувано",
"Admin" : "Администратор",
+ "Saved" : "Зачувано",
"a safe home for all your data" : "безбеден дом за сите ваши податоци",
"Name cannot be empty" : "Името неможе да биде празно",
"The given name is too long" : "Зададеното име е премногу долго",
diff --git a/apps/theming/l10n/nb.js b/apps/theming/l10n/nb.js
index 681599712d3..45a41b5a855 100644
--- a/apps/theming/l10n/nb.js
+++ b/apps/theming/l10n/nb.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Laster forhåndsvisning…",
- "Saved" : "Lagret",
"Admin" : "Admin",
+ "Saved" : "Lagret",
"a safe home for all your data" : "et trygt hjem for alle dine data",
"Name cannot be empty" : "Navn kan ikke være tom",
"The given name is too long" : "Navnet er for langt",
diff --git a/apps/theming/l10n/nb.json b/apps/theming/l10n/nb.json
index 6f0547c4be0..f1cc9a1416c 100644
--- a/apps/theming/l10n/nb.json
+++ b/apps/theming/l10n/nb.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Laster forhåndsvisning…",
- "Saved" : "Lagret",
"Admin" : "Admin",
+ "Saved" : "Lagret",
"a safe home for all your data" : "et trygt hjem for alle dine data",
"Name cannot be empty" : "Navn kan ikke være tom",
"The given name is too long" : "Navnet er for langt",
diff --git a/apps/theming/l10n/nl.js b/apps/theming/l10n/nl.js
index 58929f5e9b0..0b5fd1a1213 100644
--- a/apps/theming/l10n/nl.js
+++ b/apps/theming/l10n/nl.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Laden voorbeeld...",
- "Saved" : "Opgeslagen",
"Admin" : "Beheer",
+ "Saved" : "Opgeslagen",
"a safe home for all your data" : "een veilige plek voor al je gegevens",
"Name cannot be empty" : "Naam mag niet leeg zijn",
"The given name is too long" : "De opgegeven naam is te lang",
diff --git a/apps/theming/l10n/nl.json b/apps/theming/l10n/nl.json
index 0be90d55f82..4f010e4e333 100644
--- a/apps/theming/l10n/nl.json
+++ b/apps/theming/l10n/nl.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Laden voorbeeld...",
- "Saved" : "Opgeslagen",
"Admin" : "Beheer",
+ "Saved" : "Opgeslagen",
"a safe home for all your data" : "een veilige plek voor al je gegevens",
"Name cannot be empty" : "Naam mag niet leeg zijn",
"The given name is too long" : "De opgegeven naam is te lang",
diff --git a/apps/theming/l10n/pl.js b/apps/theming/l10n/pl.js
index 442f4da001b..e565aa230b5 100644
--- a/apps/theming/l10n/pl.js
+++ b/apps/theming/l10n/pl.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Wczytywanie podglądu…",
- "Saved" : "Zapisano",
"Admin" : "Administrator",
+ "Saved" : "Zapisano",
"a safe home for all your data" : "bezpieczny dom dla wszystkich danych",
"Name cannot be empty" : "Nazwa nie może być pusta",
"The given name is too long" : "Podana nazwa jest za długa",
diff --git a/apps/theming/l10n/pl.json b/apps/theming/l10n/pl.json
index be2bf29ab7f..694d79f31da 100644
--- a/apps/theming/l10n/pl.json
+++ b/apps/theming/l10n/pl.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Wczytywanie podglądu…",
- "Saved" : "Zapisano",
"Admin" : "Administrator",
+ "Saved" : "Zapisano",
"a safe home for all your data" : "bezpieczny dom dla wszystkich danych",
"Name cannot be empty" : "Nazwa nie może być pusta",
"The given name is too long" : "Podana nazwa jest za długa",
diff --git a/apps/theming/l10n/pt_BR.js b/apps/theming/l10n/pt_BR.js
index 051d9659ba3..fed2b8587fd 100644
--- a/apps/theming/l10n/pt_BR.js
+++ b/apps/theming/l10n/pt_BR.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Carregando pré-visualização...",
- "Saved" : "Salva",
"Admin" : "Administrador",
+ "Saved" : "Salva",
"a safe home for all your data" : "um lugar seguro para seus dados",
"Name cannot be empty" : "O nome não pode ficar em branco",
"The given name is too long" : "O nome é muito longo",
diff --git a/apps/theming/l10n/pt_BR.json b/apps/theming/l10n/pt_BR.json
index f53e1c7bec2..fff4f241875 100644
--- a/apps/theming/l10n/pt_BR.json
+++ b/apps/theming/l10n/pt_BR.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Carregando pré-visualização...",
- "Saved" : "Salva",
"Admin" : "Administrador",
+ "Saved" : "Salva",
"a safe home for all your data" : "um lugar seguro para seus dados",
"Name cannot be empty" : "O nome não pode ficar em branco",
"The given name is too long" : "O nome é muito longo",
diff --git a/apps/theming/l10n/pt_PT.js b/apps/theming/l10n/pt_PT.js
index fc9d3670d5a..3d8d44a46f9 100644
--- a/apps/theming/l10n/pt_PT.js
+++ b/apps/theming/l10n/pt_PT.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "A carregar pre-visualização...",
- "Saved" : "Guardado",
"Admin" : "Administrador",
+ "Saved" : "Guardado",
"a safe home for all your data" : "Um local seguro para todos os seus dados",
"Name cannot be empty" : "O Nome não pode estar vazio",
"The given name is too long" : "O nome atribuído é demasiado longo",
diff --git a/apps/theming/l10n/pt_PT.json b/apps/theming/l10n/pt_PT.json
index 283a9883ad1..34484c5f3bc 100644
--- a/apps/theming/l10n/pt_PT.json
+++ b/apps/theming/l10n/pt_PT.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "A carregar pre-visualização...",
- "Saved" : "Guardado",
"Admin" : "Administrador",
+ "Saved" : "Guardado",
"a safe home for all your data" : "Um local seguro para todos os seus dados",
"Name cannot be empty" : "O Nome não pode estar vazio",
"The given name is too long" : "O nome atribuído é demasiado longo",
diff --git a/apps/theming/l10n/ru.js b/apps/theming/l10n/ru.js
index 38fc0f723b9..c586872cba3 100644
--- a/apps/theming/l10n/ru.js
+++ b/apps/theming/l10n/ru.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Загружается предпросмотр…",
- "Saved" : "Сохранено",
"Admin" : "Администратор",
+ "Saved" : "Сохранено",
"a safe home for all your data" : "надёжный дом для всех ваших данных",
"Name cannot be empty" : "Имя не может быть пустым",
"The given name is too long" : "Указанное название слишком длинное",
diff --git a/apps/theming/l10n/ru.json b/apps/theming/l10n/ru.json
index 83c1ab24b0e..0afb2128043 100644
--- a/apps/theming/l10n/ru.json
+++ b/apps/theming/l10n/ru.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Загружается предпросмотр…",
- "Saved" : "Сохранено",
"Admin" : "Администратор",
+ "Saved" : "Сохранено",
"a safe home for all your data" : "надёжный дом для всех ваших данных",
"Name cannot be empty" : "Имя не может быть пустым",
"The given name is too long" : "Указанное название слишком длинное",
diff --git a/apps/theming/l10n/sc.js b/apps/theming/l10n/sc.js
index fdd2ac2a388..f7ab864c437 100644
--- a/apps/theming/l10n/sc.js
+++ b/apps/theming/l10n/sc.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Carrigamentu anteprima...",
- "Saved" : "Sarvadu",
"Admin" : "Amministradore",
+ "Saved" : "Sarvadu",
"a safe home for all your data" : "unu logu siguru pro totu is datos tuos",
"Name cannot be empty" : "Su nùmene non podet èssere isbòidu",
"The given name is too long" : "Custu nùmene est tropu longu",
diff --git a/apps/theming/l10n/sc.json b/apps/theming/l10n/sc.json
index 035f319fc1a..ad000c4f997 100644
--- a/apps/theming/l10n/sc.json
+++ b/apps/theming/l10n/sc.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Carrigamentu anteprima...",
- "Saved" : "Sarvadu",
"Admin" : "Amministradore",
+ "Saved" : "Sarvadu",
"a safe home for all your data" : "unu logu siguru pro totu is datos tuos",
"Name cannot be empty" : "Su nùmene non podet èssere isbòidu",
"The given name is too long" : "Custu nùmene est tropu longu",
diff --git a/apps/theming/l10n/sk.js b/apps/theming/l10n/sk.js
index b7556663975..3cf9809d10d 100644
--- a/apps/theming/l10n/sk.js
+++ b/apps/theming/l10n/sk.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Načítavanie ukážky...",
- "Saved" : "Uložené",
"Admin" : "Správca",
+ "Saved" : "Uložené",
"a safe home for all your data" : "bezpečný domov pre všetky vaše dáta",
"Name cannot be empty" : "Názov nemôže byť prázdny",
"The given name is too long" : "Zadané meno je príliš dlhé",
diff --git a/apps/theming/l10n/sk.json b/apps/theming/l10n/sk.json
index 7dd10590c7b..b5a21cc7bea 100644
--- a/apps/theming/l10n/sk.json
+++ b/apps/theming/l10n/sk.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Načítavanie ukážky...",
- "Saved" : "Uložené",
"Admin" : "Správca",
+ "Saved" : "Uložené",
"a safe home for all your data" : "bezpečný domov pre všetky vaše dáta",
"Name cannot be empty" : "Názov nemôže byť prázdny",
"The given name is too long" : "Zadané meno je príliš dlhé",
diff --git a/apps/theming/l10n/sl.js b/apps/theming/l10n/sl.js
index 9f9bc683966..bb0ff59aa0f 100644
--- a/apps/theming/l10n/sl.js
+++ b/apps/theming/l10n/sl.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Poteka nalaganje predogleda ...",
- "Saved" : "Shranjeno",
"Admin" : "Skrbništvo",
+ "Saved" : "Shranjeno",
"a safe home for all your data" : "varno okolje za vaše podatke",
"Name cannot be empty" : "Polje imena ne sme biti prazno",
"The given name is too long" : "Podano ime je predolgo",
diff --git a/apps/theming/l10n/sl.json b/apps/theming/l10n/sl.json
index df1c3e31d93..cc1dbaf89fb 100644
--- a/apps/theming/l10n/sl.json
+++ b/apps/theming/l10n/sl.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Poteka nalaganje predogleda ...",
- "Saved" : "Shranjeno",
"Admin" : "Skrbništvo",
+ "Saved" : "Shranjeno",
"a safe home for all your data" : "varno okolje za vaše podatke",
"Name cannot be empty" : "Polje imena ne sme biti prazno",
"The given name is too long" : "Podano ime je predolgo",
diff --git a/apps/theming/l10n/sr.js b/apps/theming/l10n/sr.js
index c1b71b38f34..bd53ef063b0 100644
--- a/apps/theming/l10n/sr.js
+++ b/apps/theming/l10n/sr.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Учитавам преглед…",
- "Saved" : "Сачувано",
"Admin" : "Администрација",
+ "Saved" : "Сачувано",
"a safe home for all your data" : "сигурно место за све ваше податке",
"Name cannot be empty" : "Назив не може бити празан",
"The given name is too long" : "Назив је предугачак",
diff --git a/apps/theming/l10n/sr.json b/apps/theming/l10n/sr.json
index 029934a898f..3c3e1410582 100644
--- a/apps/theming/l10n/sr.json
+++ b/apps/theming/l10n/sr.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Учитавам преглед…",
- "Saved" : "Сачувано",
"Admin" : "Администрација",
+ "Saved" : "Сачувано",
"a safe home for all your data" : "сигурно место за све ваше податке",
"Name cannot be empty" : "Назив не може бити празан",
"The given name is too long" : "Назив је предугачак",
diff --git a/apps/theming/l10n/sv.js b/apps/theming/l10n/sv.js
index 7cd73b2df5e..46513b6184c 100644
--- a/apps/theming/l10n/sv.js
+++ b/apps/theming/l10n/sv.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Läser in förhandsvisning...",
- "Saved" : "Sparat",
"Admin" : "Admin",
+ "Saved" : "Sparat",
"a safe home for all your data" : "ett säkert hem för all din data",
"Name cannot be empty" : "Namn kan inte vara tom",
"The given name is too long" : "Det angivna namnet är för långt",
diff --git a/apps/theming/l10n/sv.json b/apps/theming/l10n/sv.json
index c609a7145dc..508a2063296 100644
--- a/apps/theming/l10n/sv.json
+++ b/apps/theming/l10n/sv.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Läser in förhandsvisning...",
- "Saved" : "Sparat",
"Admin" : "Admin",
+ "Saved" : "Sparat",
"a safe home for all your data" : "ett säkert hem för all din data",
"Name cannot be empty" : "Namn kan inte vara tom",
"The given name is too long" : "Det angivna namnet är för långt",
diff --git a/apps/theming/l10n/tr.js b/apps/theming/l10n/tr.js
index 7358ac88819..72304e860ba 100644
--- a/apps/theming/l10n/tr.js
+++ b/apps/theming/l10n/tr.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Ön izleme yükleniyor …",
- "Saved" : "Kaydedildi",
"Admin" : "Yönetici",
+ "Saved" : "Kaydedildi",
"a safe home for all your data" : "verileriniz için güvenli bir barınak",
"Name cannot be empty" : "Ad boş olamaz",
"The given name is too long" : "Belirtilen ad çok uzun",
diff --git a/apps/theming/l10n/tr.json b/apps/theming/l10n/tr.json
index aa390f010d4..08a5cdf1c89 100644
--- a/apps/theming/l10n/tr.json
+++ b/apps/theming/l10n/tr.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Ön izleme yükleniyor …",
- "Saved" : "Kaydedildi",
"Admin" : "Yönetici",
+ "Saved" : "Kaydedildi",
"a safe home for all your data" : "verileriniz için güvenli bir barınak",
"Name cannot be empty" : "Ad boş olamaz",
"The given name is too long" : "Belirtilen ad çok uzun",
diff --git a/apps/theming/l10n/uk.js b/apps/theming/l10n/uk.js
index 680d740acfb..acee1990c9e 100644
--- a/apps/theming/l10n/uk.js
+++ b/apps/theming/l10n/uk.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Завантаження попереднього перегляду...",
- "Saved" : "Збережено",
"Admin" : "Адмін",
+ "Saved" : "Збережено",
"a safe home for all your data" : "безпечна домівка для ваших даних",
"Name cannot be empty" : "Ім'я не може бути порожнім",
"The given name is too long" : "Ім'я задовге",
diff --git a/apps/theming/l10n/uk.json b/apps/theming/l10n/uk.json
index c833d098e53..2ea04feaf1c 100644
--- a/apps/theming/l10n/uk.json
+++ b/apps/theming/l10n/uk.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Завантаження попереднього перегляду...",
- "Saved" : "Збережено",
"Admin" : "Адмін",
+ "Saved" : "Збережено",
"a safe home for all your data" : "безпечна домівка для ваших даних",
"Name cannot be empty" : "Ім'я не може бути порожнім",
"The given name is too long" : "Ім'я задовге",
diff --git a/apps/theming/l10n/vi.js b/apps/theming/l10n/vi.js
index a874fdd61e4..7546c432eb2 100644
--- a/apps/theming/l10n/vi.js
+++ b/apps/theming/l10n/vi.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "Đang tải mục xem trước...",
- "Saved" : "Đã lưu",
"Admin" : "Quản trị viên",
+ "Saved" : "Đã lưu",
"a safe home for all your data" : "một ngôi nhà an toàn cho tất cả dữ liệu của bạn",
"Name cannot be empty" : "Tên không thể trống",
"The given name is too long" : "Tên quá dài",
diff --git a/apps/theming/l10n/vi.json b/apps/theming/l10n/vi.json
index 0f02dc08d37..7af458831a5 100644
--- a/apps/theming/l10n/vi.json
+++ b/apps/theming/l10n/vi.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "Đang tải mục xem trước...",
- "Saved" : "Đã lưu",
"Admin" : "Quản trị viên",
+ "Saved" : "Đã lưu",
"a safe home for all your data" : "một ngôi nhà an toàn cho tất cả dữ liệu của bạn",
"Name cannot be empty" : "Tên không thể trống",
"The given name is too long" : "Tên quá dài",
diff --git a/apps/theming/l10n/zh_CN.js b/apps/theming/l10n/zh_CN.js
index d9bc14a39c1..12de4a9bc09 100644
--- a/apps/theming/l10n/zh_CN.js
+++ b/apps/theming/l10n/zh_CN.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "正在加载预览...",
- "Saved" : "已保存",
"Admin" : "管理",
+ "Saved" : "已保存",
"a safe home for all your data" : "给您所有的数据一个安全的家",
"Name cannot be empty" : "名称不能为空",
"The given name is too long" : "输入的名称过长",
diff --git a/apps/theming/l10n/zh_CN.json b/apps/theming/l10n/zh_CN.json
index 42d71e21417..22326184567 100644
--- a/apps/theming/l10n/zh_CN.json
+++ b/apps/theming/l10n/zh_CN.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "正在加载预览...",
- "Saved" : "已保存",
"Admin" : "管理",
+ "Saved" : "已保存",
"a safe home for all your data" : "给您所有的数据一个安全的家",
"Name cannot be empty" : "名称不能为空",
"The given name is too long" : "输入的名称过长",
diff --git a/apps/theming/l10n/zh_HK.js b/apps/theming/l10n/zh_HK.js
index 8e3cf62c97b..2a1854caefc 100644
--- a/apps/theming/l10n/zh_HK.js
+++ b/apps/theming/l10n/zh_HK.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "正在載入預覽...",
- "Saved" : "已儲存",
"Admin" : "管理員",
+ "Saved" : "已儲存",
"a safe home for all your data" : "您資料的安全屋",
"Name cannot be empty" : "名稱不能空白",
"The given name is too long" : "指定的名稱太長",
diff --git a/apps/theming/l10n/zh_HK.json b/apps/theming/l10n/zh_HK.json
index 54a743ed9e1..c249f6f45fb 100644
--- a/apps/theming/l10n/zh_HK.json
+++ b/apps/theming/l10n/zh_HK.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "正在載入預覽...",
- "Saved" : "已儲存",
"Admin" : "管理員",
+ "Saved" : "已儲存",
"a safe home for all your data" : "您資料的安全屋",
"Name cannot be empty" : "名稱不能空白",
"The given name is too long" : "指定的名稱太長",
diff --git a/apps/theming/l10n/zh_TW.js b/apps/theming/l10n/zh_TW.js
index 7f9c1db6891..1424918c44b 100644
--- a/apps/theming/l10n/zh_TW.js
+++ b/apps/theming/l10n/zh_TW.js
@@ -2,8 +2,8 @@ OC.L10N.register(
"theming",
{
"Loading preview…" : "正在載入預覽……",
- "Saved" : "已儲存",
"Admin" : "管理員",
+ "Saved" : "已儲存",
"a safe home for all your data" : "您資料的安全屋",
"Name cannot be empty" : "名稱不能空白",
"The given name is too long" : "指定的名稱太長",
diff --git a/apps/theming/l10n/zh_TW.json b/apps/theming/l10n/zh_TW.json
index 55eab291a04..bfc1feed3b2 100644
--- a/apps/theming/l10n/zh_TW.json
+++ b/apps/theming/l10n/zh_TW.json
@@ -1,7 +1,7 @@
{ "translations": {
"Loading preview…" : "正在載入預覽……",
- "Saved" : "已儲存",
"Admin" : "管理員",
+ "Saved" : "已儲存",
"a safe home for all your data" : "您資料的安全屋",
"Name cannot be empty" : "名稱不能空白",
"The given name is too long" : "指定的名稱太長",
diff --git a/apps/theming/lib/Controller/ThemingController.php b/apps/theming/lib/Controller/ThemingController.php
index a735dfafc2c..94deb2e7376 100644
--- a/apps/theming/lib/Controller/ThemingController.php
+++ b/apps/theming/lib/Controller/ThemingController.php
@@ -37,12 +37,13 @@
*/
namespace OCA\Theming\Controller;
-use OC\Template\SCSSCacher;
use OCA\Theming\ImageManager;
+use OCA\Theming\Service\ThemesService;
use OCA\Theming\ThemingDefaults;
use OCP\App\IAppManager;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\DataDisplayResponse;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\FileDisplayResponse;
use OCP\AppFramework\Http\NotFoundResponse;
@@ -54,6 +55,7 @@ use OCP\IL10N;
use OCP\IRequest;
use OCP\ITempManager;
use OCP\IURLGenerator;
+use ScssPhp\ScssPhp\Compiler;
/**
* Class ThemingController
@@ -63,40 +65,16 @@ use OCP\IURLGenerator;
* @package OCA\Theming\Controller
*/
class ThemingController extends Controller {
- /** @var ThemingDefaults */
- private $themingDefaults;
- /** @var IL10N */
- private $l10n;
- /** @var IConfig */
- private $config;
- /** @var ITempManager */
- private $tempManager;
- /** @var IAppData */
- private $appData;
- /** @var SCSSCacher */
- private $scssCacher;
- /** @var IURLGenerator */
- private $urlGenerator;
- /** @var IAppManager */
- private $appManager;
- /** @var ImageManager */
- private $imageManager;
+ private ThemingDefaults $themingDefaults;
+ private IL10N $l10n;
+ private IConfig $config;
+ private ITempManager $tempManager;
+ private IAppData $appData;
+ private IURLGenerator $urlGenerator;
+ private IAppManager $appManager;
+ private ImageManager $imageManager;
+ private ThemesService $themesService;
- /**
- * ThemingController constructor.
- *
- * @param string $appName
- * @param IRequest $request
- * @param IConfig $config
- * @param ThemingDefaults $themingDefaults
- * @param IL10N $l
- * @param ITempManager $tempManager
- * @param IAppData $appData
- * @param SCSSCacher $scssCacher
- * @param IURLGenerator $urlGenerator
- * @param IAppManager $appManager
- * @param ImageManager $imageManager
- */
public function __construct(
$appName,
IRequest $request,
@@ -105,10 +83,10 @@ class ThemingController extends Controller {
IL10N $l,
ITempManager $tempManager,
IAppData $appData,
- SCSSCacher $scssCacher,
IURLGenerator $urlGenerator,
IAppManager $appManager,
- ImageManager $imageManager
+ ImageManager $imageManager,
+ ThemesService $themesService
) {
parent::__construct($appName, $request);
@@ -117,10 +95,10 @@ class ThemingController extends Controller {
$this->config = $config;
$this->tempManager = $tempManager;
$this->appData = $appData;
- $this->scssCacher = $scssCacher;
$this->urlGenerator = $urlGenerator;
$this->appManager = $appManager;
$this->imageManager = $imageManager;
+ $this->themesService = $themesService;
}
/**
@@ -185,19 +163,12 @@ class ThemingController extends Controller {
$this->themingDefaults->set($setting, $value);
- // reprocess server scss for preview
- $cssCached = $this->scssCacher->process(\OC::$SERVERROOT, 'core/css/css-variables.scss', 'core');
-
- return new DataResponse(
- [
- 'data' =>
- [
- 'message' => $this->l10n->t('Saved'),
- 'serverCssUrl' => $this->urlGenerator->linkTo('', $this->scssCacher->getCachedSCSS('core', '/core/css/css-variables.scss'))
- ],
- 'status' => 'success'
- ]
- );
+ return new DataResponse([
+ 'data' => [
+ 'message' => $this->l10n->t('Saved'),
+ ],
+ 'status' => 'success'
+ ]);
}
/**
@@ -262,7 +233,6 @@ class ThemingController extends Controller {
}
$name = $image['name'];
- $cssCached = $this->scssCacher->process(\OC::$SERVERROOT, 'core/css/css-variables.scss', 'core');
return new DataResponse(
[
@@ -271,7 +241,6 @@ class ThemingController extends Controller {
'name' => $name,
'url' => $this->imageManager->getImageUrl($key),
'message' => $this->l10n->t('Saved'),
- 'serverCssUrl' => $this->urlGenerator->linkTo('', $this->scssCacher->getCachedSCSS('core', '/core/css/css-variables.scss'))
],
'status' => 'success'
]
@@ -288,8 +257,6 @@ class ThemingController extends Controller {
*/
public function undo(string $setting): DataResponse {
$value = $this->themingDefaults->undo($setting);
- // reprocess server scss for preview
- $cssCached = $this->scssCacher->process(\OC::$SERVERROOT, 'core/css/css-variables.scss', 'core');
return new DataResponse(
[
@@ -297,7 +264,6 @@ class ThemingController extends Controller {
[
'value' => $value,
'message' => $this->l10n->t('Saved'),
- 'serverCssUrl' => $this->urlGenerator->linkTo('', $this->scssCacher->getCachedSCSS('core', '/core/css/css-variables.scss'))
],
'status' => 'success'
]
@@ -307,6 +273,7 @@ class ThemingController extends Controller {
/**
* @PublicPage
* @NoCSRFRequired
+ * @NoSameSiteCookieRequired
*
* @param string $key
* @param bool $useSvg
@@ -339,27 +306,37 @@ class ThemingController extends Controller {
* @NoCSRFRequired
* @PublicPage
* @NoSameSiteCookieRequired
+ * @NoTwoFactorRequired
*
- * @return FileDisplayResponse|NotFoundResponse
- * @throws NotPermittedException
- * @throws \Exception
- * @throws \OCP\App\AppPathNotFoundException
+ * @return DataDisplayResponse|NotFoundResponse
*/
- public function getStylesheet() {
- $appPath = $this->appManager->getAppPath('theming');
-
- /* SCSSCacher is required here
- * We cannot rely on automatic caching done by \OC_Util::addStyle,
- * since we need to add the cacheBuster value to the url
- */
- $cssCached = $this->scssCacher->process($appPath, 'css/theming.scss', 'theming');
- if (!$cssCached) {
+ public function getThemeStylesheet(string $themeId, bool $plain = false, bool $withCustomCss = false) {
+ $themes = $this->themesService->getThemes();
+ if (!in_array($themeId, array_keys($themes))) {
return new NotFoundResponse();
}
+ $theme = $themes[$themeId];
+ $customCss = $theme->getCustomCss();
+
+ // Generate variables
+ $variables = '';
+ foreach ($theme->getCSSVariables() as $variable => $value) {
+ $variables .= "$variable:$value; ";
+ };
+
+ // If plain is set, the browser decides of the css priority
+ if ($plain) {
+ $css = ":root { $variables } " . $customCss;
+ } else {
+ // If not set, we'll rely on the body class
+ $compiler = new Compiler();
+ $compiledCss = $compiler->compileString("body[data-theme-$themeId] { $variables $customCss }");
+ $css = $compiledCss->getCss();;
+ }
+
try {
- $cssFile = $this->scssCacher->getCachedCSS('theming', 'theming.css');
- $response = new FileDisplayResponse($cssFile, Http::STATUS_OK, ['Content-Type' => 'text/css']);
+ $response = new DataDisplayResponse($css, Http::STATUS_OK, ['Content-Type' => 'text/css']);
$response->cacheFor(86400);
return $response;
} catch (NotFoundException $e) {
diff --git a/apps/theming/lib/Controller/UserThemeController.php b/apps/theming/lib/Controller/UserThemeController.php
new file mode 100644
index 00000000000..ec379d2e6fa
--- /dev/null
+++ b/apps/theming/lib/Controller/UserThemeController.php
@@ -0,0 +1,111 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2018 John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
+ * @copyright Copyright (c) 2019 Janis Köhr <janiskoehr@icloud.com>
+ *
+ * @author Christoph Wurst <christoph@winzerhof-wurst.at>
+ * @author Daniel Kesselberg <mail@danielkesselberg.de>
+ * @author Janis Köhr <janis.koehr@novatec-gmbh.de>
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @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/>.
+ *
+ */
+namespace OCA\Theming\Controller;
+
+use OCA\Theming\Service\ThemesService;
+use OCP\AppFramework\Http\DataResponse;
+use OCP\AppFramework\OCS\OCSBadRequestException;
+use OCP\AppFramework\OCSController;
+use OCP\IConfig;
+use OCP\IRequest;
+use OCP\IUserSession;
+use OCP\PreConditionNotMetException;
+
+class UserThemeController extends OCSController {
+
+ protected string $userId;
+ private IConfig $config;
+ private IUserSession $userSession;
+ private ThemesService $themesService;
+
+ /**
+ * Config constructor.
+ */
+ public function __construct(string $appName,
+ IRequest $request,
+ IConfig $config,
+ IUserSession $userSession,
+ ThemesService $themesService) {
+ parent::__construct($appName, $request);
+ $this->config = $config;
+ $this->userSession = $userSession;
+ $this->themesService = $themesService;
+ $this->userId = $userSession->getUser()->getUID();
+ }
+
+ /**
+ * @NoAdminRequired
+ *
+ * Enable theme
+ *
+ * @param string $themeId the theme ID
+ * @return DataResponse
+ * @throws OCSBadRequestException|PreConditionNotMetException
+ */
+ public function enableTheme(string $themeId): DataResponse {
+ if ($themeId === '' || !$themeId) {
+ throw new OCSBadRequestException('Invalid theme id: ' . $themeId);
+ }
+
+ $themes = $this->themesService->getThemes();
+ if (!isset($themes[$themeId])) {
+ throw new OCSBadRequestException('Invalid theme id: ' . $themeId);
+ }
+
+ // Enable selected theme
+ $this->themesService->enableTheme($themes[$themeId]);
+ return new DataResponse();
+ }
+
+ /**
+ * @NoAdminRequired
+ *
+ * Disable theme
+ *
+ * @param string $themeId the theme ID
+ * @return DataResponse
+ * @throws OCSBadRequestException|PreConditionNotMetException
+ */
+ public function disableTheme(string $themeId): DataResponse {
+ if ($themeId === '' || !$themeId) {
+ throw new OCSBadRequestException('Invalid theme id: ' . $themeId);
+ }
+
+ $themes = $this->themesService->getThemes();
+ if (!isset($themes[$themeId])) {
+ throw new OCSBadRequestException('Invalid theme id: ' . $themeId);
+ }
+
+ // Enable selected theme
+ $this->themesService->disableTheme($themes[$themeId]);
+ return new DataResponse();
+ }
+}
diff --git a/apps/theming/lib/ITheme.php b/apps/theming/lib/ITheme.php
new file mode 100644
index 00000000000..a5c9cdf26a6
--- /dev/null
+++ b/apps/theming/lib/ITheme.php
@@ -0,0 +1,99 @@
+<?php
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\Theming;
+
+/**
+ * Interface ITheme
+ *
+ * @since 25.0.0
+ */
+interface ITheme {
+
+ const TYPE_THEME = 1;
+ const TYPE_FONT = 2;
+
+ /**
+ * Unique theme id
+ * Will be used to search for ID.png in the img folder
+ *
+ * @since 25.0.0
+ */
+ public function getId(): string;
+
+ /**
+ * Theme type
+ * TYPE_THEME or TYPE_FONT
+ *
+ * @since 25.0.0
+ */
+ public function getType(): int;
+
+ /**
+ * The theme translated title
+ *
+ * @since 25.0.0
+ */
+ public function getTitle(): string;
+
+ /**
+ * The theme enable checkbox translated label
+ *
+ * @since 25.0.0
+ */
+ public function getEnableLabel(): string;
+
+ /**
+ * The theme translated description
+ *
+ * @since 25.0.0
+ */
+ public function getDescription(): string;
+
+ /**
+ * Get the media query triggering this theme
+ * Optional, ignored if falsy
+ *
+ * @return string
+ * @since 25.0.0
+ */
+ public function getMediaQuery(): string;
+
+ /**
+ * Return the list of changed css variables
+ *
+ * @return array
+ * @since 25.0.0
+ */
+ public function getCSSVariables(): array;
+
+ /**
+ * Return the custom css necessary for that app
+ * ⚠️ Warning, should be used slightly.
+ * Theoretically, editing the variables should be enough.
+ *
+ * @return string
+ * @since 25.0.0
+ */
+ public function getCustomCss(): string;
+}
diff --git a/apps/theming/lib/Listener/BeforeTemplateRenderedListener.php b/apps/theming/lib/Listener/BeforeTemplateRenderedListener.php
index 10a9434835c..185289f6ff8 100644
--- a/apps/theming/lib/Listener/BeforeTemplateRenderedListener.php
+++ b/apps/theming/lib/Listener/BeforeTemplateRenderedListener.php
@@ -27,6 +27,8 @@ namespace OCA\Theming\Listener;
use OCA\Theming\AppInfo\Application;
use OCA\Theming\Service\JSDataService;
+use OCA\Theming\Service\ThemeInjectionService;
+use OCA\Theming\Service\ThemesService;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\IConfig;
@@ -36,25 +38,18 @@ use OCP\IURLGenerator;
class BeforeTemplateRenderedListener implements IEventListener {
- /** @var IInitialStateService */
- private $initialStateService;
- /** @var IURLGenerator */
- private $urlGenerator;
- /** @var IConfig */
- private $config;
- /** @var IServerContainer */
- private $serverContainer;
+ private IInitialStateService $initialStateService;
+ private IServerContainer $serverContainer;
+ private ThemeInjectionService $themeInjectionService;
public function __construct(
IInitialStateService $initialStateService,
- IURLGenerator $urlGenerator,
- IConfig $config,
- IServerContainer $serverContainer
+ IServerContainer $serverContainer,
+ ThemeInjectionService $themeInjectionService
) {
$this->initialStateService = $initialStateService;
- $this->urlGenerator = $urlGenerator;
- $this->config = $config;
$this->serverContainer = $serverContainer;
+ $this->themeInjectionService = $themeInjectionService;
}
public function handle(Event $event): void {
@@ -63,19 +58,7 @@ class BeforeTemplateRenderedListener implements IEventListener {
return $serverContainer->query(JSDataService::class);
});
- $linkToCSS = $this->urlGenerator->linkToRoute(
- 'theming.Theming.getStylesheet',
- [
- 'v' => $this->config->getAppValue('theming', 'cachebuster', '0'),
- ]
- );
- \OCP\Util::addHeader(
- 'link',
- [
- 'rel' => 'stylesheet',
- 'href' => $linkToCSS,
- ]
- );
+ $this->themeInjectionService->injectHeaders();
// Making sure to inject just after core
\OCP\Util::addScript('theming', 'theming', 'core');
diff --git a/apps/theming/lib/Migration/MigrateUserConfig.php b/apps/theming/lib/Migration/MigrateUserConfig.php
new file mode 100644
index 00000000000..0f8d982dfa7
--- /dev/null
+++ b/apps/theming/lib/Migration/MigrateUserConfig.php
@@ -0,0 +1,123 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2019 Janis Köhr <janiskoehr@icloud.com>
+ *
+ * @author Christoph Wurst <christoph@winzerhof-wurst.at>
+ * @author Janis Köhr <janis.koehr@novatec-gmbh.de>
+ *
+ * @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/>.
+ *
+ */
+namespace OCA\Theming\Migration;
+
+use OCA\Theming\AppInfo\Application;
+use OCA\Theming\Service\ThemesService;
+use OCA\Theming\Themes\DarkHighContrastTheme;
+use OCA\Theming\Themes\DarkTheme;
+use OCA\Theming\Themes\DyslexiaFont;
+use OCA\Theming\Themes\HighContrastTheme;
+use OCP\IConfig;
+use OCP\IUser;
+use OCP\IUserManager;
+use OCP\Migration\IOutput;
+use OCP\Migration\IRepairStep;
+
+class MigrateUserConfig implements IRepairStep {
+
+ protected IUserManager $userManager;
+ protected IConfig $config;
+ protected ThemesService $themesService;
+ protected DarkTheme $darkTheme;
+ protected DarkHighContrastTheme $darkHighContrastTheme;
+ protected HighContrastTheme $highContrastTheme;
+ protected DyslexiaFont $dyslexiaFont;
+
+ /**
+ * MigrateUserConfig constructor.
+ */
+ public function __construct(IConfig $config,
+ IUserManager $userManager,
+ ThemesService $themesService,
+ DarkTheme $darkTheme,
+ DarkHighContrastTheme $darkHighContrastTheme,
+ HighContrastTheme $highContrastTheme,
+ DyslexiaFont $dyslexiaFont) {
+ $this->config = $config;
+ $this->userManager = $userManager;
+ $this->themesService = $themesService;
+
+ $this->darkTheme = $darkTheme;
+ $this->darkHighContrastTheme = $darkHighContrastTheme;
+ $this->highContrastTheme = $highContrastTheme;
+ $this->dyslexiaFont = $dyslexiaFont;
+ }
+
+ /**
+ * Returns the step's name
+ *
+ * @return string
+ * @since 25.0.0
+ */
+ public function getName() {
+ return 'Migrate old user accessibility config';
+ }
+
+ /**
+ * Run repair step.
+ * Must throw exception on error.
+ *
+ * @param IOutput $output
+ * @throws \Exception in case of failure
+ * @since 25.0.0
+ */
+ public function run(IOutput $output) {
+ $output->startProgress();
+ $this->userManager->callForSeenUsers(function (IUser $user) use ($output) {
+ $config = [];
+
+ $font = $this->config->getUserValue($user->getUID(), 'accessibility', 'font', false);
+ $highcontrast = $this->config->getUserValue($user->getUID(), 'accessibility', 'highcontrast', false);
+ $theme = $this->config->getUserValue($user->getUID(), 'accessibility', 'theme', false);
+
+ if ($highcontrast || $theme) {
+ if ($theme === 'dark' && $highcontrast === 'highcontrast') {
+ $config[] = $this->darkHighContrastTheme->getId();
+ } else if ($theme === 'dark') {
+ $config[] = $this->darkTheme->getId();
+ } else if ($highcontrast === 'highcontrast') {
+ $config[] = $this->highContrastTheme->getId();
+ }
+ }
+
+ if ($font === 'fontdyslexic') {
+ $config[] = $this->dyslexiaFont->getId();
+ }
+
+ if (!empty($config)) {
+ $this->config->setUserValue($user->getUID(), Application::APP_ID, 'enabled-themes', json_encode(array_unique($config)));
+ }
+
+ $output->advance();
+ });
+
+ $this->config->deleteAppFromAllUsers('accessibility');
+
+ $output->finishProgress();
+ }
+}
diff --git a/apps/theming/lib/Service/JSDataService.php b/apps/theming/lib/Service/JSDataService.php
index 1c4d138a764..fdc85ea445a 100644
--- a/apps/theming/lib/Service/JSDataService.php
+++ b/apps/theming/lib/Service/JSDataService.php
@@ -32,22 +32,21 @@ use OCA\Theming\Util;
use OCP\IConfig;
class JSDataService implements \JsonSerializable {
-
- /** @var ThemingDefaults */
- private $themingDefaults;
- /** @var Util */
- private $util;
- /** @var IConfig */
- private $appConfig;
+ private ThemingDefaults $themingDefaults;
+ private Util $util;
+ private IConfig $appConfig;
+ private ThemesService $themesService;
public function __construct(
ThemingDefaults $themingDefaults,
Util $util,
- IConfig $appConfig
+ IConfig $appConfig,
+ ThemesService $themesService
) {
$this->themingDefaults = $themingDefaults;
$this->util = $util;
$this->appConfig = $appConfig;
+ $this->themesService = $themesService;
}
public function jsonSerialize(): array {
@@ -60,6 +59,7 @@ class JSDataService implements \JsonSerializable {
'privacyUrl' => $this->themingDefaults->getPrivacyUrl(),
'inverted' => $this->util->invertTextColor($this->themingDefaults->getColorPrimary()),
'cacheBuster' => $this->appConfig->getAppValue(Application::APP_ID, 'cachebuster', '0'),
+ 'enabledThemes' => $this->themesService->getEnabledThemes(),
];
}
}
diff --git a/apps/theming/lib/Service/ThemeInjectionService.php b/apps/theming/lib/Service/ThemeInjectionService.php
new file mode 100644
index 00000000000..fec14de96cf
--- /dev/null
+++ b/apps/theming/lib/Service/ThemeInjectionService.php
@@ -0,0 +1,88 @@
+<?php
+/**
+ * @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @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/>.
+ *
+ */
+namespace OCA\Theming\Service;
+
+use OCA\Theming\Themes\DefaultTheme;
+use OCP\IURLGenerator;
+use OCP\Util;
+
+class ThemeInjectionService {
+
+ private IURLGenerator $urlGenerator;
+ private ThemesService $themesService;
+ private DefaultTheme $defaultTheme;
+
+ public function __construct(IURLGenerator $urlGenerator,
+ ThemesService $themesService,
+ DefaultTheme $defaultTheme) {
+ $this->urlGenerator = $urlGenerator;
+ $this->themesService = $themesService;
+ $this->defaultTheme = $defaultTheme;
+ }
+
+ public function injectHeaders() {
+ $themes = $this->themesService->getThemes();
+ $defaultTheme = $themes[$this->defaultTheme->getId()];
+ $mediaThemes = array_filter($themes, function($theme) {
+ // Check if the theme provides a media query
+ return (bool)$theme->getMediaQuery();
+ });
+
+ // Default theme fallback
+ $this->addThemeHeader($defaultTheme->getId());
+
+ // Themes applied by media queries
+ foreach($mediaThemes as $theme) {
+ $this->addThemeHeader($theme->getId(), true, $theme->getMediaQuery());
+ }
+
+ // Themes
+ foreach($this->themesService->getThemes() as $theme) {
+ // Ignore default theme as already processed first
+ if ($theme->getId() === $this->defaultTheme->getId()) {
+ continue;
+ }
+ $this->addThemeHeader($theme->getId(), false);
+ }
+ }
+
+ /**
+ * Inject theme header into rendered page
+ *
+ * @param string $themeId the theme ID
+ * @param bool $plain request the :root syntax
+ * @param string $media media query to use in the <link> element
+ */
+ private function addThemeHeader(string $themeId, bool $plain = true, string $media = null) {
+ $linkToCSS = $this->urlGenerator->linkToRoute('theming.Theming.getThemeStylesheet', [
+ 'themeId' => $themeId,
+ 'plain' => $plain,
+ ]);
+ Util::addHeader('link', [
+ 'rel' => 'stylesheet',
+ 'media' => $media,
+ 'href' => $linkToCSS,
+ 'class' => 'theme'
+ ]);
+ }
+}
diff --git a/apps/theming/lib/Service/ThemesService.php b/apps/theming/lib/Service/ThemesService.php
new file mode 100644
index 00000000000..01ebc3fb504
--- /dev/null
+++ b/apps/theming/lib/Service/ThemesService.php
@@ -0,0 +1,172 @@
+<?php
+/**
+ * @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @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/>.
+ *
+ */
+namespace OCA\Theming\Service;
+
+use OCA\Theming\AppInfo\Application;
+use OCA\Theming\ITheme;
+use OCA\Theming\Themes\DarkHighContrastTheme;
+use OCA\Theming\Themes\DarkTheme;
+use OCA\Theming\Themes\DefaultTheme;
+use OCA\Theming\Themes\DyslexiaFont;
+use OCA\Theming\Themes\HighContrastTheme;
+use OCP\IConfig;
+use OCP\IUser;
+use OCP\IUserSession;
+
+class ThemesService {
+ private IUserSession $userSession;
+ private IConfig $config;
+
+ /** @var ITheme[] */
+ private array $themesProviders;
+
+ public function __construct(IUserSession $userSession,
+ IConfig $config,
+ DefaultTheme $defaultTheme,
+ DarkTheme $darkTheme,
+ HighContrastTheme $highContrastTheme,
+ DarkHighContrastTheme $darkHighContrastTheme,
+ DyslexiaFont $dyslexiaFont) {
+ $this->userSession = $userSession;
+ $this->config = $config;
+
+ // Register themes
+ $this->themesProviders = [
+ $defaultTheme->getId() => $defaultTheme,
+ $darkTheme->getId() => $darkTheme,
+ $highContrastTheme->getId() => $highContrastTheme,
+ $darkHighContrastTheme->getId() => $darkHighContrastTheme,
+ $dyslexiaFont->getId() => $dyslexiaFont,
+ ];
+ }
+
+ /**
+ * Get the list of all registered themes
+ *
+ * @return ITheme[]
+ */
+ public function getThemes(): array {
+ return $this->themesProviders;
+ }
+
+ /**
+ * Enable a theme for the logged-in user
+ *
+ * @param ITheme $theme the theme to enable
+ * @return string[] the enabled themes
+ */
+ public function enableTheme(ITheme $theme): array {
+ $themesIds = $this->getEnabledThemes();
+
+ // If already enabled, ignore
+ if (in_array($theme->getId(), $themesIds)) {
+ return $themesIds;
+ }
+
+ /** @var ITheme[] */
+ $themes = array_map(function($themeId) {
+ return $this->getThemes()[$themeId];
+ }, $themesIds);
+
+ // Filtering all themes with the same type
+ $filteredThemes = array_filter($themes, function(ITheme $t) use ($theme) {
+ return $theme->getType() === $t->getType();
+ });
+
+ // Retrieve IDs only
+ /** @var string[] */
+ $filteredThemesIds = array_map(function(ITheme $t) {
+ return $t->getId();
+ }, array_values($filteredThemes));
+
+ $enabledThemes = array_merge(array_diff($themesIds, $filteredThemesIds), [$theme->getId()]);
+ $this->setEnabledThemes($enabledThemes);
+
+ return $enabledThemes;
+ }
+
+ /**
+ * Disable a theme for the logged-in user
+ *
+ * @param ITheme $theme the theme to disable
+ * @return string[] the enabled themes
+ */
+ public function disableTheme(ITheme $theme): array {
+ $themesIds = $this->getEnabledThemes();
+
+ // If enabled, removing it
+ if (in_array($theme->getId(), $themesIds)) {
+ $enabledThemes = array_diff($themesIds, [$theme->getId()]);
+ $this->setEnabledThemes($enabledThemes);
+ return $enabledThemes;
+ }
+
+ return $themesIds;
+ }
+
+ /**
+ * Check whether a theme is enabled or not
+ * for the logged-in user
+ *
+ * @return bool
+ */
+ public function isEnabled(ITheme $theme): bool {
+ $user = $this->userSession->getUser();
+ if ($user instanceof IUser) {
+ // Using keys as it's faster
+ $themes = $this->getEnabledThemes();
+ return in_array($theme->getId(), $themes);
+ }
+ return false;
+ }
+
+ /**
+ * Get the list of all enabled themes IDs
+ * for the logged-in user
+ *
+ * @return string[]
+ */
+ public function getEnabledThemes(): array {
+ $user = $this->userSession->getUser();
+ if ($user === null) {
+ return [];
+ }
+
+ try {
+ return json_decode($this->config->getUserValue($user->getUID(), Application::APP_ID, 'enabled-themes', '[]'));
+ } catch (\Exception $e) {
+ return [];
+ }
+ }
+
+ /**
+ * Set the list of enabled themes
+ * for the logged-in user
+ *
+ * @param string[] $themes the list of enabled themes IDs
+ */
+ private function setEnabledThemes(array $themes): void {
+ $user = $this->userSession->getUser();
+ $this->config->setUserValue($user->getUID(), Application::APP_ID, 'enabled-themes', json_encode(array_unique(array_values($themes))));
+ }
+}
diff --git a/apps/theming/lib/Settings/Admin.php b/apps/theming/lib/Settings/Admin.php
index 045f0b3fe77..6caa174d99b 100644
--- a/apps/theming/lib/Settings/Admin.php
+++ b/apps/theming/lib/Settings/Admin.php
@@ -36,22 +36,20 @@ use OCP\IURLGenerator;
use OCP\Settings\IDelegatedSettings;
class Admin implements IDelegatedSettings {
- /** @var IConfig */
- private $config;
- /** @var IL10N */
- private $l;
- /** @var ThemingDefaults */
- private $themingDefaults;
- /** @var IURLGenerator */
- private $urlGenerator;
- /** @var ImageManager */
- private $imageManager;
+ private string $appName;
+ private IConfig $config;
+ private IL10N $l;
+ private ThemingDefaults $themingDefaults;
+ private IURLGenerator $urlGenerator;
+ private ImageManager $imageManager;
- public function __construct(IConfig $config,
+ public function __construct(string $appName,
+ IConfig $config,
IL10N $l,
ThemingDefaults $themingDefaults,
IURLGenerator $urlGenerator,
ImageManager $imageManager) {
+ $this->appName = $appName;
$this->config = $config;
$this->l = $l;
$this->themingDefaults = $themingDefaults;
@@ -86,14 +84,14 @@ class Admin implements IDelegatedSettings {
'privacyUrl' => $this->themingDefaults->getPrivacyUrl(),
];
- return new TemplateResponse('theming', 'settings-admin', $parameters, '');
+ return new TemplateResponse($this->appName, 'settings-admin', $parameters, '');
}
/**
* @return string the section ID, e.g. 'sharing'
*/
public function getSection(): string {
- return 'theming';
+ return $this->appName;
}
/**
@@ -113,7 +111,7 @@ class Admin implements IDelegatedSettings {
public function getAuthorizedAppConfig(): array {
return [
- 'theming' => '/.*/',
+ $this->appName => '/.*/',
];
}
}
diff --git a/apps/theming/lib/Settings/Section.php b/apps/theming/lib/Settings/AdminSection.php
index fe2cc9243bb..2fcc81a9279 100644
--- a/apps/theming/lib/Settings/Section.php
+++ b/apps/theming/lib/Settings/AdminSection.php
@@ -26,17 +26,13 @@ use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\Settings\IIconSection;
-class Section implements IIconSection {
- /** @var IL10N */
- private $l;
- /** @var IURLGenerator */
- private $url;
+class AdminSection implements IIconSection {
+ private string $appName;
+ private IL10N $l;
+ private IURLGenerator $url;
- /**
- * @param IURLGenerator $url
- * @param IL10N $l
- */
- public function __construct(IURLGenerator $url, IL10N $l) {
+ public function __construct(string $appName, IURLGenerator $url, IL10N $l) {
+ $this->appName = $appName;
$this->url = $url;
$this->l = $l;
}
@@ -48,7 +44,7 @@ class Section implements IIconSection {
* @returns string
*/
public function getID() {
- return 'theming';
+ return $this->appName;
}
/**
@@ -76,6 +72,6 @@ class Section implements IIconSection {
* {@inheritdoc}
*/
public function getIcon() {
- return $this->url->imagePath('theming', 'app-dark.svg');
+ return $this->url->imagePath($this->appName, 'app-dark.svg');
}
}
diff --git a/apps/theming/lib/Settings/Personal.php b/apps/theming/lib/Settings/Personal.php
new file mode 100644
index 00000000000..6dd865b9cf6
--- /dev/null
+++ b/apps/theming/lib/Settings/Personal.php
@@ -0,0 +1,93 @@
+<?php
+/**
+ * @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com>
+ * @copyright Copyright (c) 2019 Janis Köhr <janiskoehr@icloud.com>
+ *
+ * @author Christoph Wurst <christoph@winzerhof-wurst.at>
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @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/>.
+ *
+ */
+namespace OCA\Theming\Settings;
+
+use OCA\Theming\Service\ThemesService;
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\AppFramework\Services\IInitialState;
+use OCP\IConfig;
+use OCP\IUserSession;
+use OCP\Settings\ISettings;
+use OCP\Util;
+
+class Personal implements ISettings {
+
+ protected string $appName;
+ private IConfig $config;
+ private IUserSession $userSession;
+ private ThemesService $themesService;
+ private IInitialState $initialStateService;
+
+ public function __construct(string $appName,
+ IConfig $config,
+ IUserSession $userSession,
+ ThemesService $themesService,
+ IInitialState $initialStateService) {
+ $this->appName = $appName;
+ $this->config = $config;
+ $this->userSession = $userSession;
+ $this->themesService = $themesService;
+ $this->initialStateService = $initialStateService;
+ }
+
+ public function getForm(): TemplateResponse {
+ $themes = array_map(function($theme) {
+ return [
+ 'id' => $theme->getId(),
+ 'type' => $theme->getType(),
+ 'title' => $theme->getTitle(),
+ 'enableLabel' => $theme->getEnableLabel(),
+ 'description' => $theme->getDescription(),
+ 'enabled' => $this->themesService->isEnabled($theme),
+ ];
+ }, $this->themesService->getThemes());
+
+ $this->initialStateService->provideInitialState('themes', array_values($themes));
+ Util::addScript($this->appName, 'theming-settings');
+
+ return new TemplateResponse($this->appName, 'settings-personal');
+ }
+
+ /**
+ * @return string the section ID, e.g. 'sharing'
+ * @since 9.1
+ */
+ public function getSection(): string {
+ return $this->appName;
+ }
+
+ /**
+ * @return int whether the form should be rather on the top or bottom of
+ * the admin section. The forms are arranged in ascending order of the
+ * priority values. It is required to return a value between 0 and 100.
+ *
+ * E.g.: 70
+ * @since 9.1
+ */
+ public function getPriority(): int {
+ return 40;
+ }
+}
diff --git a/apps/theming/lib/Settings/PersonalSection.php b/apps/theming/lib/Settings/PersonalSection.php
new file mode 100644
index 00000000000..821708e3970
--- /dev/null
+++ b/apps/theming/lib/Settings/PersonalSection.php
@@ -0,0 +1,100 @@
+<?php
+/**
+ * @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @author Christoph Wurst <christoph@winzerhof-wurst.at>
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @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/>.
+ *
+ */
+namespace OCA\Theming\Settings;
+
+use OCP\IL10N;
+use OCP\IURLGenerator;
+use OCP\Settings\IIconSection;
+
+class PersonalSection implements IIconSection {
+
+ /** @var string */
+ protected $appName;
+
+ /** @var IURLGenerator */
+ private $urlGenerator;
+
+ /** @var IL10N */
+ private $l;
+
+ /**
+ * Personal Section constructor.
+ *
+ * @param string $appName
+ * @param IURLGenerator $urlGenerator
+ * @param IL10N $l
+ */
+ public function __construct(string $appName,
+ IURLGenerator $urlGenerator,
+ IL10N $l) {
+ $this->appName = $appName;
+ $this->urlGenerator = $urlGenerator;
+ $this->l = $l;
+ }
+
+ /**
+ * returns the relative path to an 16*16 icon describing the section.
+ * e.g. '/core/img/places/files.svg'
+ *
+ * @returns string
+ * @since 13.0.0
+ */
+ public function getIcon() {
+ return $this->urlGenerator->imagePath($this->appName, 'app-dark.svg');
+ }
+
+ /**
+ * returns the ID of the section. It is supposed to be a lower case string,
+ * e.g. 'ldap'
+ *
+ * @returns string
+ * @since 9.1
+ */
+ public function getID() {
+ return $this->appName;
+ }
+
+ /**
+ * returns the translated name as it should be displayed, e.g. 'LDAP / AD
+ * integration'. Use the L10N service to translate it.
+ *
+ * @return string
+ * @since 9.1
+ */
+ public function getName() {
+ return $this->l->t('Appearance and accessibility');
+ }
+
+ /**
+ * @return int whether the form should be rather on the top or bottom of
+ * the settings navigation. The sections are arranged in ascending order of
+ * the priority values. It is required to return a value between 0 and 99.
+ *
+ * E.g.: 70
+ * @since 9.1
+ */
+ public function getPriority() {
+ return 15;
+ }
+}
diff --git a/apps/theming/lib/Themes/DarkHighContrastTheme.php b/apps/theming/lib/Themes/DarkHighContrastTheme.php
new file mode 100644
index 00000000000..62b7d4f6358
--- /dev/null
+++ b/apps/theming/lib/Themes/DarkHighContrastTheme.php
@@ -0,0 +1,98 @@
+<?php
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2022 Joas Schilling <coding@schilljs.com>
+ *
+ * @author Joas Schilling <coding@schilljs.com>
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @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/>.
+ *
+ */
+namespace OCA\Theming\Themes;
+
+use OCA\Theming\ITheme;
+
+class DarkHighContrastTheme extends DarkTheme implements ITheme {
+
+ public function getId(): string {
+ return 'dark-highcontrast';
+ }
+
+ public function getMediaQuery(): string {
+ return '(prefers-color-scheme: dark) and (prefers-contrast: more)';
+ }
+
+ public function getTitle(): string {
+ return $this->l->t('Dark theme with high contrast mode');
+ }
+
+ public function getEnableLabel(): string {
+ return $this->l->t('Enable dark high contrast mode');
+ }
+
+ public function getDescription(): string {
+ return $this->l->t('Similar to the high contrast mode, but with dark colours.');
+ }
+
+ /**
+ * Try to keep this consistent with HighContrastTheme
+ */
+ public function getCSSVariables(): array {
+ $variables = parent::getCSSVariables();
+ $colorMainText = '#ffffff';
+ $colorMainBackground = '#000000';
+ $colorBoxShadowRGB = join(',', $this->util->hexToRGB($colorMainText));
+
+ $variables['--color-main-background'] = $colorMainBackground;
+ $variables['--color-main-text'] = $colorMainText;
+
+ $variables['--color-background-dark'] = $this->util->lighten($colorMainBackground, 30);
+ $variables['--color-background-darker'] = $this->util->lighten($colorMainBackground, 30);
+
+ $variables['--color-placeholder-light'] = $this->util->lighten($colorMainBackground, 30);
+ $variables['--color-placeholder-dark'] = $this->util->lighten($colorMainBackground, 45);
+
+ $variables['--color-text-maxcontrast'] = $colorMainText;
+ $variables['--color-text-light'] = $colorMainText;
+ $variables['--color-text-lighter'] = $colorMainText;
+
+ // used for the icon loading animation
+ $variables['--color-loading-light'] = '#000000';
+ $variables['--color-loading-dark'] = '#dddddd';
+
+
+ $variables['--color-box-shadow-rgb'] = $colorBoxShadowRGB;
+ $variables['--color-box-shadow'] = $colorBoxShadowRGB;
+
+
+ $variables['--color-border'] = $this->util->lighten($colorMainBackground, 50);
+ $variables['--color-border-dark'] = $this->util->lighten($colorMainBackground, 50);
+
+ return $variables;
+ }
+
+ public function getCustomCss(): string {
+ return "
+ [class^='icon-'], [class*=' icon-'],
+ .action,
+ #appmenu li a,
+ .menutoggle {
+ opacity: 1 !important;
+ }
+ ";
+ }
+}
diff --git a/apps/theming/lib/Themes/DarkTheme.php b/apps/theming/lib/Themes/DarkTheme.php
new file mode 100644
index 00000000000..f0cacfc1696
--- /dev/null
+++ b/apps/theming/lib/Themes/DarkTheme.php
@@ -0,0 +1,87 @@
+<?php
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2022 Joas Schilling <coding@schilljs.com>
+ *
+ * @author Joas Schilling <coding@schilljs.com>
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @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/>.
+ *
+ */
+namespace OCA\Theming\Themes;
+
+use OCA\Theming\ITheme;
+
+class DarkTheme extends DefaultTheme implements ITheme {
+
+ public function getId(): string {
+ return 'dark';
+ }
+
+ public function getMediaQuery(): string {
+ return '(prefers-color-scheme: dark)';
+ }
+
+ public function getTitle(): string {
+ return $this->l->t('Dark theme');
+ }
+
+ public function getEnableLabel(): string {
+ return $this->l->t('Enable dark theme');
+ }
+
+ public function getDescription(): string {
+ return $this->l->t('A dark theme to ease your eyes by reducing the overall luminosity and brightness. It is still under development, so please report any issues you may find.');
+ }
+
+ public function getCSSVariables(): array {
+ $defaultVariables = parent::getCSSVariables();
+
+ $colorMainText = '#D8D8D8';
+ $colorMainBackground = '#171717';
+ $colorMainBackgroundRGB = join(',', $this->util->hexToRGB($colorMainBackground));
+ $colorBoxShadow = $this->util->darken($colorMainBackground, 70);
+ $colorBoxShadowRGB = join(',', $this->util->hexToRGB($colorBoxShadow));
+
+ return array_merge($defaultVariables, [
+ '--color-main-text' => $colorMainText,
+ '--color-main-background' => $colorMainBackground,
+ '--color-main-background-rgb' => $colorMainBackgroundRGB,
+
+ '--color-background-hover' => $this->util->lighten($colorMainBackground, 4),
+ '--color-background-dark' => $this->util->lighten($colorMainBackground, 7),
+ '--color-background-darker' => $this->util->lighten($colorMainBackground, 14),
+
+ '--color-placeholder-light' => $this->util->lighten($colorMainBackground, 10),
+ '--color-placeholder-dark' => $this->util->lighten($colorMainBackground, 20),
+
+ '--color-text-maxcontrast' => $this->util->darken($colorMainText, 30),
+ '--color-text-light' => $this->util->darken($colorMainText, 10),
+ '--color-text-lighter' => $this->util->darken($colorMainText, 20),
+
+ '--color-loading-light' => '#777',
+ '--color-loading-dark' => '#CCC',
+
+ '--color-box-shadow-rgb' => $colorBoxShadowRGB,
+
+ '--color-border' => $this->util->lighten($colorMainBackground, 7),
+ '--color-border-dark' => $this->util->lighten($colorMainBackground, 14),
+
+ '--background-invert-if-dark' => 'invert(100%)',
+ ]);
+ }
+}
diff --git a/apps/theming/lib/Themes/DefaultTheme.php b/apps/theming/lib/Themes/DefaultTheme.php
new file mode 100644
index 00000000000..7efd8f133d7
--- /dev/null
+++ b/apps/theming/lib/Themes/DefaultTheme.php
@@ -0,0 +1,203 @@
+<?php
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2022 Joas Schilling <coding@schilljs.com>
+ *
+ * @author Joas Schilling <coding@schilljs.com>
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @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/>.
+ *
+ */
+namespace OCA\Theming\Themes;
+
+use OCA\Theming\ImageManager;
+use OCA\Theming\ThemingDefaults;
+use OCA\Theming\Util;
+use OCA\Theming\ITheme;
+use OCP\IConfig;
+use OCP\IL10N;
+use OCP\IURLGenerator;
+
+class DefaultTheme implements ITheme {
+ public Util $util;
+ public ThemingDefaults $themingDefaults;
+ public IURLGenerator $urlGenerator;
+ public ImageManager $imageManager;
+ public IConfig $config;
+ public IL10N $l;
+
+ public string $primaryColor;
+
+ public function __construct(Util $util,
+ ThemingDefaults $themingDefaults,
+ IURLGenerator $urlGenerator,
+ ImageManager $imageManager,
+ IConfig $config,
+ IL10N $l) {
+ $this->util = $util;
+ $this->themingDefaults = $themingDefaults;
+ $this->urlGenerator = $urlGenerator;
+ $this->imageManager = $imageManager;
+ $this->config = $config;
+ $this->l = $l;
+
+ $this->primaryColor = $this->themingDefaults->getColorPrimary();
+ }
+
+ public function getId(): string {
+ return 'default';
+ }
+
+ public function getType(): int {
+ return ITheme::TYPE_THEME;
+ }
+
+ public function getTitle(): string {
+ return $this->l->t('Light theme');
+ }
+
+ public function getEnableLabel(): string {
+ return $this->l->t('Enable the default light theme');
+ }
+
+ public function getDescription(): string {
+ return $this->l->t('The default light appearance.');
+ }
+
+ public function getMediaQuery(): string {
+ return '';
+ }
+
+ public function getCSSVariables(): array {
+ $colorMainText = '#222222';
+ $colorMainBackground = '#ffffff';
+ $colorMainBackgroundRGB = join(',', $this->util->hexToRGB($colorMainBackground));
+ $colorBoxShadow = $this->util->darken($colorMainBackground, 70);
+ $colorBoxShadowRGB = join(',', $this->util->hexToRGB($colorBoxShadow));
+
+ $variables = [
+ '--color-main-background' => $colorMainBackground,
+ '--color-main-background-rgb' => $colorMainBackgroundRGB,
+ '--color-main-background-translucent' => 'rgba(var(--color-main-background-rgb), .97)',
+
+ // to use like this: background-image: linear-gradient(0, var('--gradient-main-background));
+ '--gradient-main-background' => 'var(--color-main-background) 0%, var(--color-main-background-translucent) 85%, transparent 100%',
+
+ // used for different active/hover/focus/disabled states
+ '--color-background-hover' => $this->util->darken($colorMainBackground, 4),
+ '--color-background-dark' => $this->util->darken($colorMainBackground, 7),
+ '--color-background-darker' => $this->util->darken($colorMainBackground, 14),
+
+ '--color-placeholder-light' => $this->util->darken($colorMainBackground, 10),
+ '--color-placeholder-dark' => $this->util->darken($colorMainBackground, 20),
+
+ // primary related colours
+ '--color-primary' => $this->primaryColor,
+ '--color-primary-text' => $this->util->invertTextColor($this->primaryColor) ? '#000000' : '#ffffff',
+ '--color-primary-hover' => $this->util->mix($this->primaryColor, $colorMainBackground, 80),
+ '--color-primary-light' => $this->util->mix($this->primaryColor, $colorMainBackground, 10),
+ '--color-primary-light-text' => $this->primaryColor,
+ '--color-primary-light-hover' => $this->util->mix($this->primaryColor, $colorMainText, 10),
+ '--color-primary-text-dark' => $this->util->darken($this->util->invertTextColor($this->primaryColor) ? '#000000' : '#ffffff', 7),
+ // used for buttons, inputs...
+ '--color-primary-element' => $this->util->elementColor($this->primaryColor),
+ '--color-primary-element-hover' => $this->util->mix($this->util->elementColor($this->primaryColor), $colorMainBackground, 80),
+ '--color-primary-element-light' => $this->util->lighten($this->util->elementColor($this->primaryColor), 15),
+ '--color-primary-element-lighter' => $this->util->mix($this->util->elementColor($this->primaryColor), $colorMainBackground, 15),
+
+ // max contrast for WCAG compliance
+ '--color-main-text' => $colorMainText,
+ '--color-text-maxcontrast' => $this->util->lighten($colorMainText, 33),
+ '--color-text-light' => $colorMainText,
+ '--color-text-lighter' => $this->util->lighten($colorMainText, 33),
+
+ // info/warning/success feedback colours
+ '--color-error' => '#e9322d',
+ '--color-error-hover' => $this->util->mix('#e9322d', $colorMainBackground, 80),
+ '--color-warning' => '#eca700',
+ '--color-warning-hover' => $this->util->mix('#eca700', $colorMainBackground, 80),
+ '--color-success' => '#46ba61',
+ '--color-success-hover' => $this->util->mix('#46ba61', $colorMainBackground, 80),
+
+ // used for the icon loading animation
+ '--color-loading-light' => '#cccccc',
+ '--color-loading-dark' => '#444444',
+
+ '--color-box-shadow-rgb' => $colorBoxShadowRGB,
+ '--color-box-shadow' => "rgba(var(--color-box-shadow-rgb), 0.5)",
+
+ '--color-border' => $this->util->darken($colorMainBackground, 7),
+ '--color-border-dark' => $this->util->darken($colorMainBackground, 14),
+
+ '--font-face' => "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Cantarell, Ubuntu, 'Helvetica Neue', Arial, sans-serif, 'Noto Color Emoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'",
+ '--default-font-size' => '15px',
+
+ // TODO: support "(prefers-reduced-motion)"
+ '--animation-quick' => '100ms',
+ '--animation-slow' => '300ms',
+
+ // Default variables --------------------------------------------
+ '--border-radius' => '3px',
+ '--border-radius-large' => '10px',
+ // pill-style button, value is large so big buttons also have correct roundness
+ '--border-radius-pill' => '100px',
+
+ '--default-line-height' => '24px',
+
+ // various structure data
+ '--header-height' => '50px',
+ '--navigation-width' => '300px',
+ '--sidebar-min-width' => '300px',
+ '--sidebar-max-width' => '500px',
+ '--list-min-width' => '200px',
+ '--list-max-width' => '300px',
+ '--header-menu-item-height' => '44px',
+ '--header-menu-profile-item-height' => '66px',
+
+ // mobile. Keep in sync with core/js/js.js
+ '--breakpoint-mobile' => '1024px',
+
+ // invert filter if primary is too bright
+ // to be used for legacy reasons only. Use inline
+ // svg with proper css variable instead or material
+ // design icons.
+ '--primary-invert-if-bright' => $this->util->invertTextColor($this->primaryColor) ? 'invert(100%)' : 'unset',
+ '--background-invert-if-dark' => 'unset',
+ ];
+
+ // Register image variables only if custom-defined
+ $backgroundDeleted = $this->config->getAppValue('theming', 'backgroundMime', '') === 'backgroundColor';
+ foreach(['logo', 'logoheader', 'favicon', 'background'] as $image) {
+ if ($this->imageManager->hasImage($image)) {
+ // If primary as background has been request, let's not define the background image
+ if ($image === 'background' && $backgroundDeleted) {
+ $variables["--image-background-plain"] = true;
+ continue;
+ } else if ($image === 'background') {
+ $variables['--image-background-size'] = 'cover';
+ }
+ $variables["--image-$image"] = "url('".$this->imageManager->getImageUrl($image)."')";
+ }
+ }
+
+ return $variables;
+ }
+
+ public function getCustomCss(): string {
+ return '';
+ }
+}
diff --git a/apps/theming/lib/Themes/DyslexiaFont.php b/apps/theming/lib/Themes/DyslexiaFont.php
new file mode 100644
index 00000000000..2629ac588c6
--- /dev/null
+++ b/apps/theming/lib/Themes/DyslexiaFont.php
@@ -0,0 +1,91 @@
+<?php
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2022 Joas Schilling <coding@schilljs.com>
+ *
+ * @author Joas Schilling <coding@schilljs.com>
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @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/>.
+ *
+ */
+namespace OCA\Theming\Themes;
+
+use OCA\Theming\ITheme;
+
+class DyslexiaFont extends DefaultTheme implements ITheme {
+
+ public function getId(): string {
+ return 'opendyslexic';
+ }
+
+ public function getType(): int {
+ return ITheme::TYPE_FONT;
+ }
+
+ public function getTitle(): string {
+ return $this->l->t('Dyslexia font');
+ }
+
+ public function getEnableLabel(): string {
+ return $this->l->t('Enable dyslexia font');
+ }
+
+ public function getDescription(): string {
+ return $this->l->t('OpenDyslexic is a free typeface/font designed to mitigate some of the common reading errors caused by dyslexia.');
+ }
+
+ public function getCSSVariables(): array {
+ $variables = parent::getCSSVariables();
+ $originalFontFace = $variables['--font-face'];
+
+ $variables = [
+ '--font-face' => 'OpenDyslexic, ' . $originalFontFace
+ ];
+
+ return $variables;
+ }
+
+ public function getCustomCss(): string {
+ $fontPathWoff = $this->urlGenerator->linkTo('theming', 'fonts/OpenDyslexic-Regular.woff');
+ $fontPathOtf = $this->urlGenerator->linkTo('theming', 'fonts/OpenDyslexic-Regular.otf');
+ $fontPathTtf = $this->urlGenerator->linkTo('theming', 'fonts/OpenDyslexic-Regular.ttf');
+ $boldFontPathWoff = $this->urlGenerator->linkTo('theming', 'fonts/OpenDyslexic-Bold.woff');
+ $boldFontPathOtf = $this->urlGenerator->linkTo('theming', 'fonts/OpenDyslexic-Bold.otf');
+ $boldFontPathTtf = $this->urlGenerator->linkTo('theming', 'fonts/OpenDyslexic-Bold.ttf');
+
+ return "
+ @font-face {
+ font-family: 'OpenDyslexic';
+ font-style: normal;
+ font-weight: 400;
+ src: url('$fontPathWoff') format('woff'),
+ url('$fontPathOtf') format('opentype'),
+ url('$fontPathTtf') format('truetype');
+ }
+
+ @font-face {
+ font-family: 'OpenDyslexic';
+ font-style: normal;
+ font-weight: 700;
+ src: url('$boldFontPathWoff') format('woff'),
+ url('$boldFontPathOtf') format('opentype'),
+ url('$boldFontPathTtf') format('truetype');
+ }
+ ";
+ }
+}
+
diff --git a/apps/theming/lib/Themes/HighContrastTheme.php b/apps/theming/lib/Themes/HighContrastTheme.php
new file mode 100644
index 00000000000..77239f2076c
--- /dev/null
+++ b/apps/theming/lib/Themes/HighContrastTheme.php
@@ -0,0 +1,91 @@
+<?php
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2022 Joas Schilling <coding@schilljs.com>
+ *
+ * @author Joas Schilling <coding@schilljs.com>
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @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/>.
+ *
+ */
+namespace OCA\Theming\Themes;
+
+use OCA\Theming\ITheme;
+
+class HighContrastTheme extends DefaultTheme implements ITheme {
+
+ public function getId(): string {
+ return 'highcontrast';
+ }
+
+ public function getMediaQuery(): string {
+ return '(prefers-contrast: more)';
+ }
+
+ public function getTitle(): string {
+ return $this->l->t('High contrast mode');
+ }
+
+ public function getEnableLabel(): string {
+ return $this->l->t('Enable high contrast mode');
+ }
+
+ public function getDescription(): string {
+ return $this->l->t('A high contrast mode to ease your navigation. Visual quality will be reduced but clarity will be increased.');
+ }
+
+ public function getCSSVariables(): array {
+ $variables = parent::getCSSVariables();
+ $colorMainText = '#000000';
+ $colorMainBackground = '#ffffff';
+
+ $variables['--color-main-background'] = $colorMainBackground;
+ $variables['--color-main-text'] = $colorMainText;
+
+ $variables['--color-background-dark'] = $this->util->darken($colorMainBackground, 30);
+ $variables['--color-background-darker'] = $this->util->darken($colorMainBackground, 30);
+
+ $variables['--color-placeholder-light'] = $this->util->darken($colorMainBackground, 30);
+ $variables['--color-placeholder-dark'] = $this->util->darken($colorMainBackground, 45);
+
+ $variables['--color-text-maxcontrast'] = 'var(--color-main-text)';
+ $variables['--color-text-light'] = 'var(--color-main-text)';
+ $variables['--color-text-lighter'] = 'var(--color-main-text)';
+
+ // used for the icon loading animation
+ $variables['--color-loading-light'] = '#dddddd';
+ $variables['--color-loading-dark'] = '#000000';
+
+ $variables['--color-box-shadow'] = 'var(--color-main-text)';
+
+ $variables['--color-border'] = $this->util->darken($colorMainBackground, 50);
+ $variables['--color-border-dark'] = $this->util->darken($colorMainBackground, 50);
+
+ return $variables;
+ }
+
+ public function getCustomCss(): string {
+ return "
+ [class^='icon-'], [class*=' icon-'],
+ .action,
+ #appmenu li a,
+ .menutoggle {
+ opacity: 1 !important;
+ }
+ ";
+ }
+}
diff --git a/apps/theming/lib/Util.php b/apps/theming/lib/Util.php
index 05b954d5059..9a00bd1d5b1 100644
--- a/apps/theming/lib/Util.php
+++ b/apps/theming/lib/Util.php
@@ -34,17 +34,13 @@ use OCP\Files\IAppData;
use OCP\Files\NotFoundException;
use OCP\Files\SimpleFS\ISimpleFile;
use OCP\IConfig;
+use Mexitek\PHPColors\Color;
class Util {
- /** @var IConfig */
- private $config;
-
- /** @var IAppManager */
- private $appManager;
-
- /** @var IAppData */
- private $appData;
+ private IConfig $config;
+ private IAppManager $appManager;
+ private IAppData $appData;
/**
* Util constructor.
@@ -95,59 +91,52 @@ class Util {
return $color;
}
+ public function mix(string $color1, string $color2, int $factor): string {
+ $color = new Color($color1);
+ return '#' . $color->mix($color2, $factor);
+ }
+
+ public function lighten(string $color, int $factor): string {
+ $color = new Color($color);
+ return '#' . $color->lighten($factor);
+ }
+
+ public function darken(string $color, int $factor): string {
+ $color = new Color($color);
+ return '#' . $color->darken($factor);
+ }
+
/**
* Convert RGB to HSL
*
* Copied from cssphp, copyright Leaf Corcoran, licensed under MIT
*
- * @param integer $red
- * @param integer $green
- * @param integer $blue
+ * @param int $red
+ * @param int $green
+ * @param int $blue
*
- * @return array
+ * @return float[]
*/
- public function toHSL($red, $green, $blue) {
- $min = min($red, $green, $blue);
- $max = max($red, $green, $blue);
- $l = $min + $max;
- $d = $max - $min;
-
- if ((int) $d === 0) {
- $h = $s = 0;
- } else {
- if ($l < 255) {
- $s = $d / $l;
- } else {
- $s = $d / (510 - $l);
- }
-
- if ($red == $max) {
- $h = 60 * ($green - $blue) / $d;
- } elseif ($green == $max) {
- $h = 60 * ($blue - $red) / $d + 120;
- } else {
- $h = 60 * ($red - $green) / $d + 240;
- }
- }
-
- return [fmod($h, 360), $s * 100, $l / 5.1];
+ public function toHSL(int $red, int $green, int $blue): array {
+ $color = new Color(Color::rgbToHex(['R' => $red, 'G' => $green, 'B' => $blue]));
+ return array_values($color->getHsl());
}
/**
* @param string $color rgb color value
* @return float
*/
- public function calculateLuminance($color) {
+ public function calculateLuminance(string $color): float {
[$red, $green, $blue] = $this->hexToRGB($color);
$hsl = $this->toHSL($red, $green, $blue);
- return $hsl[2] / 100;
+ return $hsl[2];
}
/**
* @param string $color rgb color value
* @return float
*/
- public function calculateLuma($color) {
+ public function calculateLuma(string $color): float {
[$red, $green, $blue] = $this->hexToRGB($color);
return (0.2126 * $red + 0.7152 * $green + 0.0722 * $blue) / 255;
}
@@ -157,19 +146,9 @@ class Util {
* @return int[]
* @psalm-return array{0: int, 1: int, 2: int}
*/
- public function hexToRGB($color) {
- $hex = preg_replace("/[^0-9A-Fa-f]/", '', $color);
- if (strlen($hex) === 3) {
- $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
- }
- if (strlen($hex) !== 6) {
- return [0, 0, 0];
- }
- return [
- hexdec(substr($hex, 0, 2)),
- hexdec(substr($hex, 2, 2)),
- hexdec(substr($hex, 4, 2))
- ];
+ public function hexToRGB(string $color): array {
+ $color = new Color($color);
+ return array_values($color->getRgb());
}
/**
diff --git a/apps/theming/src/UserThemes.vue b/apps/theming/src/UserThemes.vue
new file mode 100644
index 00000000000..1fd6cb20866
--- /dev/null
+++ b/apps/theming/src/UserThemes.vue
@@ -0,0 +1,186 @@
+<template>
+ <SettingsSection class="theming" :title="t('themes', 'Appearance and accessibility')">
+ <p v-html="description" />
+ <p v-html="descriptionDetail" />
+
+ <div class="theming__preview-list">
+ <ItemPreview v-for="theme in themes"
+ :key="theme.id"
+ :theme="theme"
+ :selected="selectedTheme.id === theme.id"
+ :themes="themes"
+ type="theme"
+ @change="changeTheme" />
+ <ItemPreview v-for="theme in fonts"
+ :key="theme.id"
+ :theme="theme"
+ :selected="theme.enabled"
+ :themes="fonts"
+ type="font"
+ @change="changeFont" />
+ </div>
+ </SettingsSection>
+</template>
+
+<script>
+import { generateOcsUrl } from '@nextcloud/router'
+import { loadState } from '@nextcloud/initial-state'
+import axios from '@nextcloud/axios'
+import SettingsSection from '@nextcloud/vue/dist/Components/SettingsSection'
+
+import ItemPreview from './components/ItemPreview'
+
+const availableThemes = loadState('theming', 'themes', [])
+
+console.debug('Available themes', availableThemes)
+
+export default {
+ name: 'UserThemes',
+ components: {
+ ItemPreview,
+ SettingsSection,
+ },
+
+ data() {
+ return {
+ availableThemes,
+ }
+ },
+
+ computed: {
+ themes() {
+ return this.availableThemes.filter(theme => theme.type === 1)
+ },
+ fonts() {
+ return this.availableThemes.filter(theme => theme.type === 2)
+ },
+
+ // Selected theme, fallback on first (default) if none
+ selectedTheme() {
+ return this.themes.find(theme => theme.enabled === true) || this.themes[0]
+ },
+
+ description() {
+ // using the `t` replace method escape html, we have to do it manually :/
+ return t(
+ 'theming',
+ 'Universal access is very important to us. We follow web standards and check to make everything usable also without mouse, and assistive software such as screenreaders. We aim to be compliant with the {guidelines}Web Content Accessibility Guidelines{linkend} 2.1 on AA level, with the high contrast theme even on AAA level.'
+ )
+ .replace('{guidelines}', this.guidelinesLink)
+ .replace('{linkend}', '</a>')
+ },
+ guidelinesLink() {
+ return '<a target="_blank" href="https://www.w3.org/WAI/standards-guidelines/wcag/" rel="noreferrer nofollow">'
+ },
+ descriptionDetail() {
+ return t(
+ 'theming',
+ 'If you find any issues, don’t hesitate to report them on {issuetracker}our issue tracker{linkend}. And if you want to get involved, come join {designteam}our design team{linkend}!'
+ )
+ .replace('{issuetracker}', this.issuetrackerLink)
+ .replace('{designteam}', this.designteamLink)
+ .replace(/\{linkend\}/g, '</a>')
+ },
+ issuetrackerLink() {
+ return '<a target="_blank" href="https://github.com/nextcloud/server/issues/" rel="noreferrer nofollow">'
+ },
+ designteamLink() {
+ return '<a target="_blank" href="https://nextcloud.com/design" rel="noreferrer nofollow">'
+ },
+ },
+ methods: {
+ changeTheme({ enabled, id }) {
+ // Reset selected and select new one
+ this.themes.forEach(theme => {
+ if (theme.id === id && enabled) {
+ theme.enabled = true
+ document.body.setAttribute(`data-theme-${theme.id}`, true)
+ return
+ }
+ theme.enabled = false
+ document.body.removeAttribute(`data-theme-${theme.id}`)
+ })
+
+ this.selectItem(enabled, id)
+ },
+ changeFont({ enabled, id }) {
+ // Reset selected and select new one
+ this.fonts.forEach(font => {
+ if (font.id === id && enabled) {
+ font.enabled = true
+ document.body.setAttribute(`data-theme-${font.id}`, true)
+ return
+ }
+ font.enabled = false
+ document.body.removeAttribute(`data-theme-${font.id}`)
+ })
+
+ this.selectItem(enabled, id)
+ },
+
+ /**
+ * Commit a change and force reload css
+ * Fetching the file again will trigger the server update
+ *
+ * @param {boolean} enabled the theme state
+ * @param {string} themeId the theme ID to change
+ */
+ async selectItem(enabled, themeId) {
+ try {
+ if (enabled) {
+ await axios({
+ url: generateOcsUrl('apps/theming/api/v1/theme/{themeId}/enable', { themeId }),
+ method: 'PUT',
+ })
+ } else {
+ await axios({
+ url: generateOcsUrl('apps/theming/api/v1/theme/{themeId}', { themeId }),
+ method: 'DELETE',
+ })
+ }
+
+ } catch (err) {
+ console.error(err, err.response)
+ OC.Notification.showTemporary(t('theming', err.response.data.ocs.meta.message + '. Unable to apply the setting.'))
+ }
+ },
+ },
+}
+</script>
+<style lang="scss" scoped>
+
+.theming {
+ // Limit width of settings sections for readability
+ p {
+ max-width: 800px;
+ }
+
+ // Proper highlight for links and focus feedback
+ &::v-deep a {
+ font-weight: bold;
+
+ &:hover,
+ &:focus {
+ text-decoration: underline;
+ }
+ }
+
+ &__preview-list {
+ --gap: 30px;
+
+ display: grid;
+ margin-top: var(--gap);
+ column-gap: var(--gap);
+ row-gap: var(--gap);
+ grid-template-columns: 1fr 1fr;
+ }
+}
+
+@media (max-width: 1440px) {
+ .theming__preview-list {
+ display: flex;
+ flex-direction: column;
+ }
+}
+
+</style>
diff --git a/apps/theming/src/components/ItemPreview.vue b/apps/theming/src/components/ItemPreview.vue
new file mode 100644
index 00000000000..82d588059a2
--- /dev/null
+++ b/apps/theming/src/components/ItemPreview.vue
@@ -0,0 +1,124 @@
+<template>
+ <div class="theming__preview">
+ <div class="theming__preview-image" :style="{ backgroundImage: 'url(' + img + ')' }" />
+ <div class="theming__preview-description">
+ <h3>{{ theme.title }}</h3>
+ <p>{{ theme.description }}</p>
+ <CheckboxRadioSwitch class="theming__preview-toggle"
+ :checked.sync="checked"
+ :name="name"
+ :type="switchType">
+ {{ theme.enableLabel }}
+ </CheckboxRadioSwitch>
+ </div>
+ </div>
+</template>
+
+<script>
+import { generateFilePath } from '@nextcloud/router'
+import CheckboxRadioSwitch from '@nextcloud/vue/dist/Components/CheckboxRadioSwitch'
+
+export default {
+ name: 'ItemPreview',
+ components: {
+ CheckboxRadioSwitch,
+ },
+ props: {
+ theme: {
+ type: Object,
+ required: true,
+ },
+ selected: {
+ type: Boolean,
+ default: false,
+ },
+ type: {
+ type: String,
+ default: '',
+ },
+ themes: {
+ type: Array,
+ default: () => [],
+ },
+ },
+ computed: {
+ switchType() {
+ return this.themes.length === 1 ? 'switch' : 'radio'
+ },
+
+ name() {
+ return this.switchType === 'radio' ? this.type : null
+ },
+
+ img() {
+ return generateFilePath('theming', 'img', this.theme.id + '.jpg')
+ },
+
+ checked: {
+ get() {
+ return this.selected
+ },
+ set(checked) {
+ console.debug('Selecting theme', this.theme, checked)
+
+ // If this is a radio, we can only enable
+ if (this.switchType === 'radio') {
+ this.$emit('change', { enabled: true, id: this.theme.id })
+ return
+ }
+
+ // If this is a switch, we can disable the theme
+ this.$emit('change', { enabled: checked === true, id: this.theme.id })
+ },
+ },
+ },
+}
+</script>
+<style lang="scss" scoped>
+// We make previews on 16/10 screens
+$ratio: 16;
+
+.theming__preview {
+ --ratio: 16;
+ position: relative;
+ display: flex;
+ justify-content: flex-start;
+ max-width: 800px;
+
+ &,
+ * {
+ user-select: none;
+ }
+
+ &-image {
+ flex-basis: calc(16px * var(--ratio));
+ flex-shrink: 0;
+ height: calc(10px * var(--ratio));
+ margin-right: var(--gap);
+ border-radius: var(--border-radius);
+ background-repeat: no-repeat;
+ background-position: top left;
+ background-size: cover;
+ }
+
+ &-description {
+ display: flex;
+ flex-direction: column;
+
+ label {
+ padding: 12px 0;
+ }
+ }
+}
+
+@media (max-width: (1024px / 1.5)) {
+ .theming__preview {
+ flex-direction: column;
+
+ &-image {
+ margin: 0;
+ }
+ }
+}
+
+</style>
diff --git a/apps/theming/src/settings.js b/apps/theming/src/settings.js
new file mode 100644
index 00000000000..45a75e53ea5
--- /dev/null
+++ b/apps/theming/src/settings.js
@@ -0,0 +1,32 @@
+/**
+ * @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @license AGPL-3.0-or-later
+ *
+ * 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/>.
+ *
+ */
+
+import Vue from 'vue'
+import App from './UserThemes.vue'
+
+// bind to window
+Vue.prototype.OC = OC
+Vue.prototype.t = t
+
+const View = Vue.extend(App)
+const theming = new View()
+theming.$mount('#theming')
diff --git a/apps/theming/templates/settings-admin.php b/apps/theming/templates/settings-admin.php
index d38ca0fc44c..6014f0e8579 100644
--- a/apps/theming/templates/settings-admin.php
+++ b/apps/theming/templates/settings-admin.php
@@ -116,7 +116,7 @@ style('theming', 'settings-admin');
<label for="upload-login-logoheader"><span><?php p($l->t('Header logo')) ?></span></label>
<input id="upload-login-logoheader" class="fileupload" name="image" type="file">
<label for="upload-login-logoheader" class="button icon-upload svg" id="upload-login-logoheader" title="<?php p($l->t("Upload new header logo")) ?>"></label>
- <div class="image-preview"></div>
+ <div id="theming-preview-logoheader" class="image-preview"></div>
<div data-setting="logoheaderMime" data-toggle="tooltip" data-original-title="<?php p($l->t('Reset to default')); ?>" class="theme-undo icon icon-history"></div>
</form>
</div>
@@ -127,7 +127,7 @@ style('theming', 'settings-admin');
<label for="upload-login-favicon"><span><?php p($l->t('Favicon')) ?></span></label>
<input id="upload-login-favicon" class="fileupload" name="image" type="file">
<label for="upload-login-favicon" class="button icon-upload svg" id="upload-login-favicon" title="<?php p($l->t("Upload new favicon")) ?>"></label>
- <div class="image-preview"></div>
+ <div id="theming-preview-favicon" class="image-preview"></div>
<div data-setting="faviconMime" data-toggle="tooltip" data-original-title="<?php p($l->t('Reset to default')); ?>" class="theme-undo icon icon-history"></div>
</form>
</div>
diff --git a/apps/theming/templates/settings-personal.php b/apps/theming/templates/settings-personal.php
new file mode 100644
index 00000000000..4ba1aa47e6f
--- /dev/null
+++ b/apps/theming/templates/settings-personal.php
@@ -0,0 +1,26 @@
+<?php
+/**
+ * @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @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/>.
+ *
+ */
+
+?>
+
+<span id="theming"></span> \ No newline at end of file
diff --git a/apps/theming/tests/Controller/ThemingControllerTest.php b/apps/theming/tests/Controller/ThemingControllerTest.php
index cff2028809d..511f7a6d528 100644
--- a/apps/theming/tests/Controller/ThemingControllerTest.php
+++ b/apps/theming/tests/Controller/ThemingControllerTest.php
@@ -34,8 +34,8 @@
namespace OCA\Theming\Tests\Controller;
use OC\L10N\L10N;
-use OC\Template\SCSSCacher;
use OCA\Theming\Controller\ThemingController;
+use OCA\Theming\Service\ThemesService;
use OCA\Theming\ImageManager;
use OCA\Theming\ThemingDefaults;
use OCP\App\IAppManager;
@@ -72,10 +72,10 @@ class ThemingControllerTest extends TestCase {
private $appData;
/** @var ImageManager|MockObject */
private $imageManager;
- /** @var SCSSCacher */
- private $scssCacher;
- /** @var IURLGenerator */
+ /** @var IURLGenerator|MockObject */
private $urlGenerator;
+ /** @var ThemeService|MockObject */
+ private $themesService;
protected function setUp(): void {
$this->request = $this->createMock(IRequest::class);
@@ -85,9 +85,9 @@ class ThemingControllerTest extends TestCase {
$this->appData = $this->createMock(IAppData::class);
$this->appManager = $this->createMock(IAppManager::class);
$this->tempManager = \OC::$server->getTempManager();
- $this->scssCacher = $this->createMock(SCSSCacher::class);
$this->urlGenerator = $this->createMock(IURLGenerator::class);
$this->imageManager = $this->createMock(ImageManager::class);
+ $this->themesService = $this->createMock(ThemesService::class);
$timeFactory = $this->createMock(ITimeFactory::class);
$timeFactory->expects($this->any())
@@ -104,10 +104,10 @@ class ThemingControllerTest extends TestCase {
$this->l10n,
$this->tempManager,
$this->appData,
- $this->scssCacher,
$this->urlGenerator,
$this->appManager,
- $this->imageManager
+ $this->imageManager,
+ $this->themesService,
);
parent::setUp();
@@ -144,23 +144,12 @@ class ThemingControllerTest extends TestCase {
->willReturnCallback(function ($str) {
return $str;
});
- $this->scssCacher
- ->expects($this->once())
- ->method('getCachedSCSS')
- ->with('core', '/core/css/css-variables.scss')
- ->willReturn('/core/css/someHash-css-variables.scss');
- $this->urlGenerator
- ->expects($this->once())
- ->method('linkTo')
- ->with('', '/core/css/someHash-css-variables.scss')
- ->willReturn('/nextcloudWebroot/core/css/someHash-css-variables.scss');
$expected = new DataResponse(
[
'data' =>
[
'message' => $message,
- 'serverCssUrl' => '/nextcloudWebroot/core/css/someHash-css-variables.scss',
],
'status' => 'success',
]
@@ -382,9 +371,6 @@ class ThemingControllerTest extends TestCase {
return $str;
});
- $this->urlGenerator->expects($this->once())
- ->method('linkTo')
- ->willReturn('serverCss');
$this->imageManager->expects($this->once())
->method('getImageUrl')
->with('logo')
@@ -400,7 +386,6 @@ class ThemingControllerTest extends TestCase {
'name' => 'logo.svg',
'message' => 'Saved',
'url' => 'imageUrl',
- 'serverCssUrl' => 'serverCss'
],
'status' => 'success'
]
@@ -440,9 +425,6 @@ class ThemingControllerTest extends TestCase {
$this->imageManager->expects($this->once())
->method('updateImage');
- $this->urlGenerator->expects($this->once())
- ->method('linkTo')
- ->willReturn('serverCss');
$this->imageManager->expects($this->once())
->method('getImageUrl')
->with('background')
@@ -454,7 +436,6 @@ class ThemingControllerTest extends TestCase {
'name' => 'logo.svg',
'message' => 'Saved',
'url' => 'imageUrl',
- 'serverCssUrl' => 'serverCss'
],
'status' => 'success'
]
@@ -607,24 +588,13 @@ class ThemingControllerTest extends TestCase {
->method('undo')
->with('MySetting')
->willReturn('MyValue');
- $this->scssCacher
- ->expects($this->once())
- ->method('getCachedSCSS')
- ->with('core', '/core/css/css-variables.scss')
- ->willReturn('/core/css/someHash-css-variables.scss');
- $this->urlGenerator
- ->expects($this->once())
- ->method('linkTo')
- ->with('', '/core/css/someHash-css-variables.scss')
- ->willReturn('/nextcloudWebroot/core/css/someHash-css-variables.scss');
$expected = new DataResponse(
[
'data' =>
[
'value' => 'MyValue',
- 'message' => 'Saved',
- 'serverCssUrl' => '/nextcloudWebroot/core/css/someHash-css-variables.scss',
+ 'message' => 'Saved'
],
'status' => 'success'
]
@@ -651,16 +621,6 @@ class ThemingControllerTest extends TestCase {
->method('undo')
->with($value)
->willReturn($value);
- $this->scssCacher
- ->expects($this->once())
- ->method('getCachedSCSS')
- ->with('core', '/core/css/css-variables.scss')
- ->willReturn('/core/css/someHash-css-variables.scss');
- $this->urlGenerator
- ->expects($this->once())
- ->method('linkTo')
- ->with('', '/core/css/someHash-css-variables.scss')
- ->willReturn('/nextcloudWebroot/core/css/someHash-css-variables.scss');
$expected = new DataResponse(
[
@@ -668,7 +628,6 @@ class ThemingControllerTest extends TestCase {
[
'value' => $value,
'message' => 'Saved',
- 'serverCssUrl' => '/nextcloudWebroot/core/css/someHash-css-variables.scss',
],
'status' => 'success'
]
@@ -743,53 +702,6 @@ class ThemingControllerTest extends TestCase {
@$this->assertEquals($expected, $this->themingController->getImage('background'));
}
-
- public function testGetStylesheet() {
- $this->appManager->expects($this->once())->method('getAppPath')->with('theming')->willReturn(\OC::$SERVERROOT . '/theming');
- $file = $this->createMock(ISimpleFile::class);
- $file->expects($this->any())->method('getName')->willReturn('theming.css');
- $file->expects($this->any())->method('getMTime')->willReturn(42);
- $file->expects($this->any())->method('getContent')->willReturn('compiled');
- $this->scssCacher->expects($this->once())->method('process')->willReturn(true);
- $this->scssCacher->expects($this->once())->method('getCachedCSS')->willReturn($file);
-
- $response = new Http\FileDisplayResponse($file, Http::STATUS_OK, ['Content-Type' => 'text/css']);
- $response->cacheFor(86400);
-
- $actual = $this->themingController->getStylesheet();
- $this->assertEquals($response, $actual);
- }
-
- public function testGetStylesheetFails() {
- $this->appManager->expects($this->once())->method('getAppPath')->with('theming')->willReturn(\OC::$SERVERROOT . '/theming');
- $file = $this->createMock(ISimpleFile::class);
- $file->expects($this->any())->method('getName')->willReturn('theming.css');
- $file->expects($this->any())->method('getMTime')->willReturn(42);
- $file->expects($this->any())->method('getContent')->willReturn('compiled');
- $this->scssCacher->expects($this->once())->method('process')->willReturn(true);
- $this->scssCacher->expects($this->once())->method('getCachedCSS')->willThrowException(new NotFoundException());
- $response = new Http\NotFoundResponse();
-
- $actual = $this->themingController->getStylesheet();
- $this->assertEquals($response, $actual);
- }
-
- public function testGetStylesheetOutsideServerroot() {
- $this->appManager->expects($this->once())->method('getAppPath')->with('theming')->willReturn('/outside/serverroot/theming');
- $file = $this->createMock(ISimpleFile::class);
- $file->expects($this->any())->method('getName')->willReturn('theming.css');
- $file->expects($this->any())->method('getMTime')->willReturn(42);
- $file->expects($this->any())->method('getContent')->willReturn('compiled');
- $this->scssCacher->expects($this->once())->method('process')->with('/outside/serverroot/theming', 'css/theming.scss', 'theming')->willReturn(true);
- $this->scssCacher->expects($this->once())->method('getCachedCSS')->willReturn($file);
-
- $response = new Http\FileDisplayResponse($file, Http::STATUS_OK, ['Content-Type' => 'text/css']);
- $response->cacheFor(86400);
-
- $actual = $this->themingController->getStylesheet();
- $this->assertEquals($response, $actual);
- }
-
public function testGetManifest() {
$this->config
->expects($this->once())
diff --git a/apps/theming/tests/Controller/UserThemeControllerTest.php b/apps/theming/tests/Controller/UserThemeControllerTest.php
new file mode 100644
index 00000000000..b925085bf41
--- /dev/null
+++ b/apps/theming/tests/Controller/UserThemeControllerTest.php
@@ -0,0 +1,142 @@
+<?php
+/**
+ * @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @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/>.
+ *
+ */
+namespace OCA\Theming\Tests\Controller;
+
+use OCA\Theming\Controller\UserThemeController;
+use OCA\Theming\ITheme;
+use OCA\Theming\Themes\DarkHighContrastTheme;
+use OCA\Theming\Themes\DarkTheme;
+use OCA\Theming\Themes\DefaultTheme;
+use OCA\Theming\Themes\DyslexiaFont;
+use OCA\Theming\Themes\HighContrastTheme;
+use OCA\Theming\Service\ThemesService;
+use OCP\AppFramework\Http\DataResponse;
+use OCP\AppFramework\OCS\OCSBadRequestException;
+use OCP\IConfig;
+use OCP\IRequest;
+use OCP\IUser;
+use OCP\IUserSession;
+use PHPUnit\Framework\MockObject\MockObject;
+use Test\TestCase;
+
+class UserThemeControllerTest extends TestCase {
+ /** @var UserThemeController */
+ private $userThemeController;
+
+ /** @var IRequest|MockObject */
+ private $request;
+ /** @var IConfig|MockObject */
+ private $config;
+ /** @var IUserSession|MockObject */
+ private $userSession;
+ /** @var ThemeService|MockObject */
+ private $themesService;
+
+ /** @var ITheme[] */
+ private $themes;
+
+ protected function setUp(): void {
+ $this->request = $this->createMock(IRequest::class);
+ $this->config = $this->createMock(IConfig::class);
+ $this->userSession = $this->createMock(IUserSession::class);
+ $this->themesService = $this->createMock(ThemesService::class);
+
+ $this->themes = [
+ 'default' => $this->createMock(DefaultTheme::class),
+ 'dark' => $this->createMock(DarkTheme::class),
+ 'highcontrast' => $this->createMock(HighContrastTheme::class),
+ 'dark-highcontrast' => $this->createMock(DarkHighContrastTheme::class),
+ 'opendyslexic' => $this->createMock(DyslexiaFont::class),
+ ];
+
+ $user = $this->createMock(IUser::class);
+ $this->userSession->expects($this->any())
+ ->method('getUser')
+ ->willReturn($user);
+ $user->expects($this->any())
+ ->method('getUID')
+ ->willReturn('user');
+
+ $this->userThemeController = new UserThemeController(
+ 'theming',
+ $this->request,
+ $this->config,
+ $this->userSession,
+ $this->themesService,
+ );
+
+ parent::setUp();
+ }
+
+ public function dataTestThemes() {
+ return [
+ ['default'],
+ ['dark'],
+ ['highcontrast'],
+ ['dark-highcontrast'],
+ ['opendyslexic'],
+ ['', OCSBadRequestException::class],
+ ['badTheme', OCSBadRequestException::class],
+ ];
+ }
+
+ /**
+ * @dataProvider dataTestThemes
+ *
+ * @param string $themeId
+ * @param string $exception
+ */
+ public function testEnableTheme($themeId, string $exception = null) {
+ $this->themesService
+ ->expects($this->any())
+ ->method('getThemes')
+ ->willReturn($this->themes);
+
+ if ($exception) {
+ $this->expectException($exception);
+ }
+
+ $expected = new DataResponse();
+ $this->assertEquals($expected, $this->userThemeController->enableTheme($themeId));
+ }
+
+ /**
+ * @dataProvider dataTestThemes
+ *
+ * @param string $themeId
+ * @param string $exception
+ */
+ public function testDisableTheme($themeId, string $exception = null) {
+ $this->themesService
+ ->expects($this->any())
+ ->method('getThemes')
+ ->willReturn($this->themes);
+
+ if ($exception) {
+ $this->expectException($exception);
+ }
+
+ $expected = new DataResponse();
+ $this->assertEquals($expected, $this->userThemeController->disableTheme($themeId));
+ }
+}
diff --git a/apps/theming/tests/Service/ThemesServiceTest.php b/apps/theming/tests/Service/ThemesServiceTest.php
new file mode 100644
index 00000000000..56f96d29637
--- /dev/null
+++ b/apps/theming/tests/Service/ThemesServiceTest.php
@@ -0,0 +1,244 @@
+<?php
+/**
+ * @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @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/>.
+ *
+ */
+namespace OCA\Theming\Tests\Service;
+
+use OCA\Theming\AppInfo\Application;
+use OCA\Theming\ImageManager;
+use OCA\Theming\ITheme;
+use OCA\Theming\Themes\DarkHighContrastTheme;
+use OCA\Theming\Themes\DarkTheme;
+use OCA\Theming\Themes\DefaultTheme;
+use OCA\Theming\Themes\DyslexiaFont;
+use OCA\Theming\Themes\HighContrastTheme;
+use OCA\Theming\Service\ThemesService;
+use OCA\Theming\ThemingDefaults;
+use OCA\Theming\Util;
+use OCP\AppFramework\Http\DataResponse;
+use OCP\AppFramework\OCS\OCSBadRequestException;
+use OCP\IConfig;
+use OCP\IL10N;
+use OCP\IRequest;
+use OCP\IURLGenerator;
+use OCP\IUser;
+use OCP\IUserSession;
+use PHPUnit\Framework\MockObject\MockObject;
+use Test\TestCase;
+
+class ThemesServiceTest extends TestCase {
+ /** @var ThemesService */
+ private $themesService;
+
+ /** @var IUserSession|MockObject */
+ private $userSession;
+ /** @var IConfig|MockObject */
+ private $config;
+ /** @var ThemingDefaults|MockObject */
+ private $themingDefaults;
+
+ /** @var ITheme[] */
+ private $themes;
+
+ protected function setUp(): void {
+ $this->userSession = $this->createMock(IUserSession::class);
+ $this->config = $this->createMock(IConfig::class);
+ $this->themingDefaults = $this->createMock(ThemingDefaults::class);
+
+ $this->themingDefaults->expects($this->any())
+ ->method('getColorPrimary')
+ ->willReturn('#0082c9');
+
+ $this->initThemes();
+
+ $this->themesService = new ThemesService(
+ $this->userSession,
+ $this->config,
+ ...array_values($this->themes)
+ );
+
+ parent::setUp();
+ }
+
+ public function testGetThemes() {
+ $expected = [
+ 'default',
+ 'dark',
+ 'highcontrast',
+ 'dark-highcontrast',
+ 'opendyslexic',
+ ];
+ $this->assertEquals($expected, array_keys($this->themesService->getThemes()));
+ }
+
+
+ public function dataTestEnableTheme() {
+ return [
+ ['dark', [], ['dark']],
+ ['dark', ['dark'], ['dark']],
+ ['opendyslexic', ['dark'], ['dark', 'opendyslexic']],
+ ['dark', ['highcontrast', 'opendyslexic'], ['opendyslexic', 'dark']],
+ ];
+ }
+
+ /**
+ * @dataProvider dataTestEnableTheme
+ *
+ * @param string $toEnable
+ * @param string[] $enabledThemes
+ * @param string[] $expectedEnabled
+ */
+ public function testEnableTheme(string $toEnable, array $enabledThemes, array $expectedEnabled) {
+ $user = $this->createMock(IUser::class);
+ $this->userSession->expects($this->any())
+ ->method('getUser')
+ ->willReturn($user);
+ $user->expects($this->any())
+ ->method('getUID')
+ ->willReturn('user');
+
+ $this->config->expects($this->once())
+ ->method('getUserValue')
+ ->with('user', Application::APP_ID, 'enabled-themes', '[]')
+ ->willReturn(json_encode($enabledThemes));
+
+ $this->assertEquals($expectedEnabled, $this->themesService->enableTheme($this->themes[$toEnable]));
+ }
+
+
+ public function dataTestDisableTheme() {
+ return [
+ ['dark', [], []],
+ ['dark', ['dark'], []],
+ ['opendyslexic', ['dark', 'opendyslexic'], ['dark'], ],
+ ['highcontrast', ['opendyslexic'], ['opendyslexic']],
+ ];
+ }
+
+ /**
+ * @dataProvider dataTestDisableTheme
+ *
+ * @param string $toEnable
+ * @param string[] $enabledThemes
+ * @param string[] $expectedEnabled
+ */
+ public function testDisableTheme(string $toDisable, array $enabledThemes, array $expectedEnabled) {
+ $user = $this->createMock(IUser::class);
+ $this->userSession->expects($this->any())
+ ->method('getUser')
+ ->willReturn($user);
+ $user->expects($this->any())
+ ->method('getUID')
+ ->willReturn('user');
+
+ $this->config->expects($this->once())
+ ->method('getUserValue')
+ ->with('user', Application::APP_ID, 'enabled-themes', '[]')
+ ->willReturn(json_encode($enabledThemes));
+
+
+ $this->assertEquals($expectedEnabled, $this->themesService->disableTheme($this->themes[$toDisable]));
+ }
+
+
+ public function dataTestIsEnabled() {
+ return [
+ ['dark', [], false],
+ ['dark', ['dark'], true],
+ ['opendyslexic', ['dark', 'opendyslexic'], true],
+ ['highcontrast', ['opendyslexic'], false],
+ ];
+ }
+
+ /**
+ * @dataProvider dataTestIsEnabled
+ *
+ * @param string $toEnable
+ * @param string[] $enabledThemes
+ */
+ public function testisEnabled(string $themeId, array $enabledThemes, $expected) {
+ $user = $this->createMock(IUser::class);
+ $this->userSession->expects($this->any())
+ ->method('getUser')
+ ->willReturn($user);
+ $user->expects($this->any())
+ ->method('getUID')
+ ->willReturn('user');
+
+ $this->config->expects($this->once())
+ ->method('getUserValue')
+ ->with('user', Application::APP_ID, 'enabled-themes', '[]')
+ ->willReturn(json_encode($enabledThemes));
+
+
+ $this->assertEquals($expected, $this->themesService->isEnabled($this->themes[$themeId]));
+ }
+
+ private function initThemes() {
+ $util = $this->createMock(Util::class);
+ $urlGenerator = $this->createMock(IURLGenerator::class);
+ $imageManager = $this->createMock(ImageManager::class);
+ $l10n = $this->createMock(IL10N::class);
+
+ $this->themes = [
+ 'default' => new DefaultTheme(
+ $util,
+ $this->themingDefaults,
+ $urlGenerator,
+ $imageManager,
+ $this->config,
+ $l10n,
+ ),
+ 'dark' => new DarkTheme(
+ $util,
+ $this->themingDefaults,
+ $urlGenerator,
+ $imageManager,
+ $this->config,
+ $l10n,
+ ),
+ 'highcontrast' => new HighContrastTheme(
+ $util,
+ $this->themingDefaults,
+ $urlGenerator,
+ $imageManager,
+ $this->config,
+ $l10n,
+ ),
+ 'dark-highcontrast' => new DarkHighContrastTheme(
+ $util,
+ $this->themingDefaults,
+ $urlGenerator,
+ $imageManager,
+ $this->config,
+ $l10n,
+ ),
+ 'opendyslexic' => new DyslexiaFont(
+ $util,
+ $this->themingDefaults,
+ $urlGenerator,
+ $imageManager,
+ $this->config,
+ $l10n,
+ ),
+ ];
+ }
+}
diff --git a/apps/theming/tests/ServicesTest.php b/apps/theming/tests/ServicesTest.php
index fdd49deddd8..441e5d55044 100644
--- a/apps/theming/tests/ServicesTest.php
+++ b/apps/theming/tests/ServicesTest.php
@@ -28,7 +28,7 @@ namespace OCA\Theming\Tests;
use OCA\Theming\Capabilities;
use OCA\Theming\Controller\ThemingController;
use OCA\Theming\Settings\Admin;
-use OCA\Theming\Settings\Section;
+use OCA\Theming\Settings\PersonalSection;
use OCA\Theming\ThemingDefaults;
use OCA\Theming\Util;
use OCP\AppFramework\App;
@@ -74,8 +74,8 @@ class ServicesTest extends TestCase {
// Settings
[Admin::class],
[Admin::class, ISettings::class],
- [Section::class],
- [Section::class, IIconSection::class],
+ [PersonalSection::class],
+ [PersonalSection::class, IIconSection::class],
];
}
diff --git a/apps/theming/tests/Settings/AdminTest.php b/apps/theming/tests/Settings/AdminTest.php
index 50b95fe7e99..b10196a1ac5 100644
--- a/apps/theming/tests/Settings/AdminTest.php
+++ b/apps/theming/tests/Settings/AdminTest.php
@@ -27,6 +27,7 @@
*/
namespace OCA\Theming\Tests\Settings;
+use OCA\Theming\AppInfo\Application;
use OCA\Theming\ImageManager;
use OCA\Theming\Settings\Admin;
use OCA\Theming\ThemingDefaults;
@@ -59,6 +60,7 @@ class AdminTest extends TestCase {
$this->imageManager = $this->createMock(ImageManager::class);
$this->admin = new Admin(
+ Application::APP_ID,
$this->config,
$this->l10n,
$this->themingDefaults,
diff --git a/apps/theming/tests/Settings/SectionTest.php b/apps/theming/tests/Settings/SectionTest.php
index 496c24b3e52..c168f13728d 100644
--- a/apps/theming/tests/Settings/SectionTest.php
+++ b/apps/theming/tests/Settings/SectionTest.php
@@ -23,7 +23,8 @@
*/
namespace OCA\Theming\Tests\Settings;
-use OCA\Theming\Settings\Section;
+use OCA\Theming\AppInfo\Application;
+use OCA\Theming\Settings\AdminSection;
use OCP\IL10N;
use OCP\IURLGenerator;
use Test\TestCase;
@@ -33,7 +34,7 @@ class SectionTest extends TestCase {
private $url;
/** @var IL10N|\PHPUnit\Framework\MockObject\MockObject */
private $l;
- /** @var Section */
+ /** @var AdminSection */
private $section;
protected function setUp(): void {
@@ -41,7 +42,8 @@ class SectionTest extends TestCase {
$this->url = $this->createMock(IURLGenerator::class);
$this->l = $this->createMock(IL10N::class);
- $this->section = new Section(
+ $this->section = new AdminSection(
+ Application::APP_ID,
$this->url,
$this->l
);
diff --git a/apps/theming/tests/Themes/DefaultThemeTest.php b/apps/theming/tests/Themes/DefaultThemeTest.php
new file mode 100644
index 00000000000..486e7c7a1e5
--- /dev/null
+++ b/apps/theming/tests/Themes/DefaultThemeTest.php
@@ -0,0 +1,135 @@
+<?php
+/**
+ * @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @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/>.
+ *
+ */
+namespace OCA\Theming\Tests\Service;
+
+use OC\App\AppManager;
+use OCA\Theming\ImageManager;
+use OCA\Theming\ITheme;
+use OCA\Theming\Themes\DefaultTheme;
+use OCA\Theming\ThemingDefaults;
+use OCA\Theming\Util;
+use OCP\Files\IAppData;
+use OCP\IConfig;
+use OCP\IL10N;
+use OCP\IURLGenerator;
+use PHPUnit\Framework\MockObject\MockObject;
+use Test\TestCase;
+
+
+class DefaultThemeTest extends TestCase {
+ /** @var ThemingDefaults|MockObject */
+ private $themingDefaults;
+ /** @var IURLGenerator|MockObject */
+ private $urlGenerator;
+ /** @var ImageManager|MockObject */
+ private $imageManager;
+ /** @var IConfig|MockObject */
+ private $config;
+ /** @var IL10N|MockObject */
+ private $l10n;
+
+ private DefaultTheme $defaultTheme;
+
+ protected function setUp(): void {
+ $this->themingDefaults = $this->createMock(ThemingDefaults::class);
+ $this->urlGenerator = $this->createMock(IURLGenerator::class);
+ $this->imageManager = $this->createMock(ImageManager::class);
+ $this->config = $this->createMock(IConfig::class);
+ $this->l10n = $this->createMock(IL10N::class);
+
+ $util = new Util(
+ $this->config,
+ $this->createMock(AppManager::class),
+ $this->createMock(IAppData::class)
+ );
+
+ $this->themingDefaults
+ ->expects($this->any())
+ ->method('getColorPrimary')
+ ->willReturn('#0082c9');
+
+ $this->l10n
+ ->expects($this->any())
+ ->method('t')
+ ->willReturnCallback(function ($text, $parameters = []) {
+ return vsprintf($text, $parameters);
+ });
+
+ $this->defaultTheme = new DefaultTheme(
+ $util,
+ $this->themingDefaults,
+ $this->urlGenerator,
+ $this->imageManager,
+ $this->config,
+ $this->l10n,
+ );
+
+ parent::setUp();
+ }
+
+
+ public function testGetId() {
+ $this->assertEquals('default', $this->defaultTheme->getId());
+ }
+
+ public function testGetType() {
+ $this->assertEquals(ITheme::TYPE_THEME, $this->defaultTheme->getType());
+ }
+
+ public function testGetTitle() {
+ $this->assertEquals('Light theme', $this->defaultTheme->getTitle());
+ }
+
+ public function testGetEnableLabel() {
+ $this->assertEquals('Enable the default light theme', $this->defaultTheme->getEnableLabel());
+ }
+
+ public function testGetDescription() {
+ $this->assertEquals('The default light appearance.', $this->defaultTheme->getDescription());
+ }
+
+ public function testGetMediaQuery() {
+ $this->assertEquals('', $this->defaultTheme->getMediaQuery());
+ }
+
+ public function testGetCustomCss() {
+ $this->assertEquals('', $this->defaultTheme->getCustomCss());
+ }
+
+ /**
+ * Ensure parity between the default theme and the static generated file
+ * @see ThemingController.php:313
+ */
+ public function testThemindDisabledFallbackCss() {
+ // Generate variables
+ $variables = '';
+ foreach ($this->defaultTheme->getCSSVariables() as $variable => $value) {
+ $variables .= " $variable: $value;" . PHP_EOL;
+ };
+
+ $css = ":root { " . PHP_EOL . "$variables}" . PHP_EOL;
+ $fallbackCss = file_get_contents(__DIR__ . '/../../css/default.css');
+
+ $this->assertEquals($css, $fallbackCss);
+ }
+}
diff --git a/apps/theming/tests/Themes/DyslexiaFontTest.php b/apps/theming/tests/Themes/DyslexiaFontTest.php
new file mode 100644
index 00000000000..30472aeb6f2
--- /dev/null
+++ b/apps/theming/tests/Themes/DyslexiaFontTest.php
@@ -0,0 +1,162 @@
+<?php
+/**
+ * @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @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/>.
+ *
+ */
+namespace OCA\Theming\Tests\Service;
+
+use OC\App\AppManager;
+use OC\Route\Router;
+use OCA\Theming\ImageManager;
+use OCA\Theming\ITheme;
+use OCA\Theming\Themes\DyslexiaFont;
+use OCA\Theming\ThemingDefaults;
+use OCA\Theming\Util;
+use OCP\Files\IAppData;
+use OCP\ICacheFactory;
+use OCP\IConfig;
+use OCP\IL10N;
+use OCP\IRequest;
+use OCP\IURLGenerator;
+use OCP\IUserSession;
+use PHPUnit\Framework\MockObject\MockObject;
+use Test\TestCase;
+
+
+class DyslexiaFontTest extends TestCase {
+ /** @var ThemingDefaults|MockObject */
+ private $themingDefaults;
+ /** @var IURLGenerator|MockObject */
+ private $urlGenerator;
+ /** @var ImageManager|MockObject */
+ private $imageManager;
+ /** @var IConfig|MockObject */
+ private $config;
+ /** @var IL10N|MockObject */
+ private $l10n;
+
+ private DyslexiaFont $dyslexiaFont;
+
+ protected function setUp(): void {
+ $this->themingDefaults = $this->createMock(ThemingDefaults::class);
+ $this->imageManager = $this->createMock(ImageManager::class);
+ $this->config = $this->createMock(IConfig::class);
+ $this->l10n = $this->createMock(IL10N::class);
+
+ $util = new Util(
+ $this->config,
+ $this->createMock(AppManager::class),
+ $this->createMock(IAppData::class)
+ );
+
+ $userSession = $this->createMock(IUserSession::class);
+ $cacheFactory = $this->createMock(ICacheFactory::class);
+ $request = $this->createMock(IRequest::class);
+ $router = $this->createMock(Router::class);
+ $this->urlGenerator = new \OC\URLGenerator(
+ $this->config,
+ $userSession,
+ $cacheFactory,
+ $request,
+ $router
+ );
+
+ $this->themingDefaults
+ ->expects($this->any())
+ ->method('getColorPrimary')
+ ->willReturn('#0082c9');
+
+ $this->l10n
+ ->expects($this->any())
+ ->method('t')
+ ->willReturnCallback(function ($text, $parameters = []) {
+ return vsprintf($text, $parameters);
+ });
+
+ $this->dyslexiaFont = new DyslexiaFont(
+ $util,
+ $this->themingDefaults,
+ $this->urlGenerator,
+ $this->imageManager,
+ $this->config,
+ $this->l10n,
+ );
+
+ parent::setUp();
+ }
+
+
+ public function testGetId() {
+ $this->assertEquals('opendyslexic', $this->dyslexiaFont->getId());
+ }
+
+ public function testGetType() {
+ $this->assertEquals(ITheme::TYPE_FONT, $this->dyslexiaFont->getType());
+ }
+
+ public function testGetTitle() {
+ $this->assertNotEmpty($this->dyslexiaFont->getTitle());
+ }
+
+ public function testGetEnableLabel() {
+ $this->assertNotEmpty($this->dyslexiaFont->getEnableLabel());
+ }
+
+ public function testGetDescription() {
+ $this->assertNotEmpty($this->dyslexiaFont->getDescription());
+ }
+
+ public function testGetMediaQuery() {
+ $this->assertEquals('', $this->dyslexiaFont->getMediaQuery());
+ }
+
+ public function testGetCSSVariables() {
+ $this->assertStringStartsWith('OpenDyslexic', $this->dyslexiaFont->getCSSVariables()['--font-face']);
+ }
+
+ public function dataTestGetCustomCss() {
+ return [
+ ['', true],
+ ['', false],
+ ['/subfolder', true],
+ ['/subfolder', false],
+ ];
+ }
+
+ /**
+ * @dataProvider dataTestGetCustomCss
+ *
+ * Ensure the fonts are always loaded from the web root
+ * despite having url rewriting enabled or not
+ *
+ * @param string $webRoot
+ * @param bool $prettyUrlsEnabled
+ */
+ public function testGetCustomCss($webRoot, $prettyUrlsEnabled) {
+ \OC::$WEBROOT = $webRoot;
+ $this->config->expects($this->any())
+ ->method('getSystemValue')
+ ->with('htaccess.IgnoreFrontController', false)
+ ->willReturn($prettyUrlsEnabled);
+
+ $this->assertStringContainsString("'$webRoot/apps/theming/fonts/OpenDyslexic-Regular.woff'", $this->dyslexiaFont->getCustomCss());
+ $this->assertStringNotContainsString('index.php', $this->dyslexiaFont->getCustomCss());
+ }
+}
diff --git a/apps/theming/tests/UtilTest.php b/apps/theming/tests/UtilTest.php
index f805d083e27..914ad8b073f 100644
--- a/apps/theming/tests/UtilTest.php
+++ b/apps/theming/tests/UtilTest.php
@@ -90,14 +90,15 @@ class UtilTest extends TestCase {
$luminance = $this->util->calculateLuminance('#000');
$this->assertEquals(0, $luminance);
}
+
public function testInvertTextColorInvalid() {
- $invert = $this->util->invertTextColor('aaabbbcccddd123');
- $this->assertEquals(false, $invert);
+ $this->expectException(\Exception::class);
+ $this->util->invertTextColor('aaabbbcccddd123');
}
public function testInvertTextColorEmpty() {
- $invert = $this->util->invertTextColor('');
- $this->assertEquals(false, $invert);
+ $this->expectException(\Exception::class);
+ $this->util->invertTextColor('');
}
public function testElementColorDefault() {