@@ -71,6 +71,14 @@ interface ITheme { | |||
*/ | |||
public function getDescription(): string; | |||
/** | |||
* Get the meta attribute matching the theme | |||
* e.g. https://html.spec.whatwg.org/multipage/semantics.html#meta-color-scheme | |||
* @return array{name?: string, content?: string}[] | |||
* @since 29.0.0 | |||
*/ | |||
public function getMeta(): array; | |||
/** | |||
* Get the media query triggering this theme | |||
* Optional, ignored if falsy |
@@ -22,6 +22,7 @@ | |||
*/ | |||
namespace OCA\Theming\Service; | |||
use OCA\Theming\ITheme; | |||
use OCA\Theming\Themes\DefaultTheme; | |||
use OCA\Theming\Util; | |||
use OCP\IConfig; | |||
@@ -48,6 +49,7 @@ class ThemeInjectionService { | |||
$this->defaultTheme = $defaultTheme; | |||
$this->util = $util; | |||
$this->config = $config; | |||
if ($userSession->getUser() !== null) { | |||
$this->userId = $userSession->getUser()->getUID(); | |||
} else { | |||
@@ -55,7 +57,7 @@ class ThemeInjectionService { | |||
} | |||
} | |||
public function injectHeaders() { | |||
public function injectHeaders(): void { | |||
$themes = $this->themesService->getThemes(); | |||
$defaultTheme = $themes[$this->defaultTheme->getId()]; | |||
$mediaThemes = array_filter($themes, function ($theme) { | |||
@@ -64,11 +66,11 @@ class ThemeInjectionService { | |||
}); | |||
// Default theme fallback | |||
$this->addThemeHeader($defaultTheme->getId()); | |||
$this->addThemeHeaders($defaultTheme); | |||
// Themes applied by media queries | |||
foreach($mediaThemes as $theme) { | |||
$this->addThemeHeader($theme->getId(), true, $theme->getMediaQuery()); | |||
$this->addThemeHeaders($theme, true, $theme->getMediaQuery()); | |||
} | |||
// Themes | |||
@@ -77,20 +79,23 @@ class ThemeInjectionService { | |||
if ($theme->getId() === $this->defaultTheme->getId()) { | |||
continue; | |||
} | |||
$this->addThemeHeader($theme->getId(), false); | |||
$this->addThemeHeaders($theme, false); | |||
} | |||
// Meta headers | |||
$this->addThemeMetaHeaders($themes); | |||
} | |||
/** | |||
* Inject theme header into rendered page | |||
* | |||
* @param string $themeId the theme ID | |||
* @param ITheme $theme the theme | |||
* @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) { | |||
private function addThemeHeaders(ITheme $theme, bool $plain = true, string $media = null): void { | |||
$linkToCSS = $this->urlGenerator->linkToRoute('theming.Theming.getThemeStylesheet', [ | |||
'themeId' => $themeId, | |||
'themeId' => $theme->getId(), | |||
'plain' => $plain, | |||
'v' => $this->util->getCacheBuster(), | |||
]); | |||
@@ -101,4 +106,36 @@ class ThemeInjectionService { | |||
'class' => 'theme' | |||
]); | |||
} | |||
/** | |||
* Inject meta headers into rendered page | |||
* | |||
* @param ITheme[] $themes the theme | |||
*/ | |||
private function addThemeMetaHeaders(array $themes): void { | |||
$metaHeaders = []; | |||
// Meta headers | |||
foreach($this->themesService->getThemes() as $theme) { | |||
if (!empty($theme->getMeta())) { | |||
foreach($theme->getMeta() as $meta) { | |||
if (!isset($meta['name']) || !isset($meta['content'])) { | |||
continue; | |||
} | |||
if (!isset($metaHeaders[$meta['name']])) { | |||
$metaHeaders[$meta['name']] = []; | |||
} | |||
$metaHeaders[$meta['name']][] = $meta['content']; | |||
} | |||
} | |||
} | |||
foreach($metaHeaders as $name => $content) { | |||
\OCP\Util::addHeader('meta', [ | |||
'name' => $name, | |||
'content' => join(' ', array_unique($content)), | |||
]); | |||
} | |||
} | |||
} |
@@ -33,10 +33,6 @@ class DarkHighContrastTheme extends DarkTheme implements ITheme { | |||
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'); | |||
} | |||
@@ -49,6 +45,10 @@ class DarkHighContrastTheme extends DarkTheme implements ITheme { | |||
return $this->l->t('Similar to the high contrast mode, but with dark colours.'); | |||
} | |||
public function getMediaQuery(): string { | |||
return '(prefers-color-scheme: dark) and (prefers-contrast: more)'; | |||
} | |||
/** | |||
* Keep this consistent with other HighContrast Themes | |||
*/ |
@@ -33,10 +33,6 @@ class DarkTheme extends DefaultTheme implements ITheme { | |||
return 'dark'; | |||
} | |||
public function getMediaQuery(): string { | |||
return '(prefers-color-scheme: dark)'; | |||
} | |||
public function getTitle(): string { | |||
return $this->l->t('Dark theme'); | |||
} | |||
@@ -49,6 +45,18 @@ class DarkTheme extends DefaultTheme implements ITheme { | |||
return $this->l->t('A dark theme to ease your eyes by reducing the overall luminosity and brightness.'); | |||
} | |||
public function getMediaQuery(): string { | |||
return '(prefers-color-scheme: dark)'; | |||
} | |||
public function getMeta(): array { | |||
// https://html.spec.whatwg.org/multipage/semantics.html#meta-color-scheme | |||
return [[ | |||
'name' => 'color-scheme', | |||
'content' => 'dark', | |||
]]; | |||
} | |||
public function getCSSVariables(): array { | |||
$defaultVariables = parent::getCSSVariables(); | |||
@@ -101,6 +101,10 @@ class DefaultTheme implements ITheme { | |||
return ''; | |||
} | |||
public function getMeta(): array { | |||
return []; | |||
} | |||
public function getCSSVariables(): array { | |||
$colorMainText = '#222222'; | |||
$colorMainTextRgb = join(',', $this->util->hexToRGB($colorMainText)); |
@@ -33,10 +33,6 @@ class HighContrastTheme extends DefaultTheme implements ITheme { | |||
return 'light-highcontrast'; | |||
} | |||
public function getMediaQuery(): string { | |||
return '(prefers-contrast: more)'; | |||
} | |||
public function getTitle(): string { | |||
return $this->l->t('High contrast mode'); | |||
} | |||
@@ -49,6 +45,10 @@ class HighContrastTheme extends DefaultTheme implements ITheme { | |||
return $this->l->t('A high contrast mode to ease your navigation. Visual quality will be reduced but clarity will be increased.'); | |||
} | |||
public function getMediaQuery(): string { | |||
return '(prefers-contrast: more)'; | |||
} | |||
/** | |||
* Keep this consistent with other HighContrast Themes | |||
*/ |
@@ -33,10 +33,6 @@ class LightTheme extends DefaultTheme implements ITheme { | |||
return 'light'; | |||
} | |||
public function getType(): int { | |||
return ITheme::TYPE_THEME; | |||
} | |||
public function getTitle(): string { | |||
return $this->l->t('Light theme'); | |||
} | |||
@@ -52,4 +48,12 @@ class LightTheme extends DefaultTheme implements ITheme { | |||
public function getMediaQuery(): string { | |||
return '(prefers-color-scheme: light)'; | |||
} | |||
public function getMeta(): array { | |||
// https://html.spec.whatwg.org/multipage/semantics.html#meta-color-scheme | |||
return [[ | |||
'name' => 'color-scheme', | |||
'content' => 'light', | |||
]]; | |||
} | |||
} |