diff --git a/conf/manifest.json b/conf/manifest.json
new file mode 100644
index 0000000000..891bd79fbf
--- /dev/null
+++ b/conf/manifest.json
@@ -0,0 +1,3 @@
+{
+ "display": "standalone"
+}
diff --git a/data/media/wiki/dokuwiki.svg b/data/media/wiki/dokuwiki.svg
new file mode 100644
index 0000000000..6e522c8b27
--- /dev/null
+++ b/data/media/wiki/dokuwiki.svg
@@ -0,0 +1,586 @@
+
+
+
+
diff --git a/inc/Manifest.php b/inc/Manifest.php
new file mode 100644
index 0000000000..0df9c2b819
--- /dev/null
+++ b/inc/Manifest.php
@@ -0,0 +1,80 @@
+cssStyleini($conf['template']);
+ $replacements = $styleIni['replacements'];
+
+ if (empty($manifest['background_color'])) {
+ $manifest['background_color'] = $replacements['__background__'];
+ }
+
+ if (empty($manifest['theme_color'])) {
+ $manifest['theme_color'] = !empty($replacements['__theme_color__']) ? $replacements['__theme_color__'] : $replacements['__background_alt__'];
+ }
+
+ if (empty($manifest['icons'])) {
+ $manifest['icons'] = [];
+ if (file_exists(mediaFN(':wiki:favicon.ico'))) {
+ $url = ml(':wiki:favicon.ico', '', true, '', true);
+ $manifest['icons'][] = [
+ 'src' => $url,
+ 'sizes' => '16x16',
+ ];
+ }
+
+ $look = [
+ ':wiki:logo.svg',
+ ':logo.svg',
+ ':wiki:dokuwiki.svg'
+ ];
+
+ foreach ($look as $svgLogo) {
+
+ $svgLogoFN = mediaFN($svgLogo);
+
+ if (file_exists($svgLogoFN)) {
+ $url = ml($svgLogo, '', true, '', true);
+ $manifest['icons'][] = [
+ 'src' => $url,
+ 'sizes' => '17x17 512x512',
+ 'type' => 'image/svg+xml',
+ ];
+ break;
+ };
+ }
+ }
+
+ trigger_event('MANIFEST_SEND', $manifest);
+
+ header('Content-Type: application/manifest+json');
+ echo json_encode($manifest);
+ }
+}
diff --git a/inc/StyleUtils.php b/inc/StyleUtils.php
new file mode 100644
index 0000000000..494ed8e0ca
--- /dev/null
+++ b/inc/StyleUtils.php
@@ -0,0 +1,107 @@
+
+ *
+ * @param string $tpl the used template
+ * @param bool $preview load preview replacements
+ * @return array with keys 'stylesheets' and 'replacements'
+ */
+ public function cssStyleini($tpl, $preview=false) {
+ global $conf;
+
+ $stylesheets = array(); // mode, file => base
+ // guaranteed placeholder => value
+ $replacements = array(
+ '__text__' => "#000",
+ '__background__' => "#fff",
+ '__text_alt__' => "#999",
+ '__background_alt__' => "#eee",
+ '__text_neu__' => "#666",
+ '__background_neu__' => "#ddd",
+ '__border__' => "#ccc",
+ '__highlight__' => "#ff9",
+ '__link__' => "#00f",
+ );
+
+ // load template's style.ini
+ $incbase = tpl_incdir($tpl);
+ $webbase = tpl_basedir($tpl);
+ $ini = $incbase.'style.ini';
+ if(file_exists($ini)){
+ $data = parse_ini_file($ini, true);
+
+ // stylesheets
+ if(is_array($data['stylesheets'])) foreach($data['stylesheets'] as $file => $mode){
+ $stylesheets[$mode][$incbase.$file] = $webbase;
+ }
+
+ // replacements
+ if(is_array($data['replacements'])){
+ $replacements = array_merge($replacements, $this->cssFixreplacementurls($data['replacements'],$webbase));
+ }
+ }
+
+ // load configs's style.ini
+ $webbase = DOKU_BASE;
+ $ini = DOKU_CONF."tpl/$tpl/style.ini";
+ $incbase = dirname($ini).'/';
+ if(file_exists($ini)){
+ $data = parse_ini_file($ini, true);
+
+ // stylesheets
+ if(isset($data['stylesheets']) && is_array($data['stylesheets'])) foreach($data['stylesheets'] as $file => $mode){
+ $stylesheets[$mode][$incbase.$file] = $webbase;
+ }
+
+ // replacements
+ if(isset($data['replacements']) && is_array($data['replacements'])){
+ $replacements = array_merge($replacements, $this->cssFixreplacementurls($data['replacements'],$webbase));
+ }
+ }
+
+ // allow replacement overwrites in preview mode
+ if($preview) {
+ $webbase = DOKU_BASE;
+ $ini = $conf['cachedir'].'/preview.ini';
+ if(file_exists($ini)) {
+ $data = parse_ini_file($ini, true);
+ // replacements
+ if(is_array($data['replacements'])) {
+ $replacements = array_merge($replacements, $this->cssFixreplacementurls($data['replacements'], $webbase));
+ }
+ }
+ }
+
+ return array(
+ 'stylesheets' => $stylesheets,
+ 'replacements' => $replacements
+ );
+ }
+
+
+ /**
+ * Amend paths used in replacement relative urls, refer FS#2879
+ *
+ * @author Chris Smith
+ *
+ * @param array $replacements with key-value pairs
+ * @param string $location
+ * @return array
+ */
+ protected function cssFixreplacementurls($replacements, $location) {
+ foreach($replacements as $key => $value) {
+ $replacements[$key] = preg_replace('#(url\([ \'"]*)(?!/|data:|http://|https://| |\'|")#','\\1'.$location,$value);
+ }
+ return $replacements;
+ }
+}
diff --git a/inc/config_cascade.php b/inc/config_cascade.php
index 2466290bc2..f0aa6cc7e4 100644
--- a/inc/config_cascade.php
+++ b/inc/config_cascade.php
@@ -28,6 +28,10 @@
'default' => array(DOKU_CONF . 'license.php'),
'local' => array(DOKU_CONF . 'license.local.php'),
),
+ 'manifest' => array(
+ 'default' => array(DOKU_CONF . 'manifest.json'),
+ 'local' => array(DOKU_CONF . 'manifest.local.json'),
+ ),
'mediameta' => array(
'default' => array(DOKU_CONF . 'mediameta.php'),
'local' => array(DOKU_CONF . 'mediameta.local.php'),
diff --git a/inc/confutils.php b/inc/confutils.php
index f753b18c2a..59147010f1 100644
--- a/inc/confutils.php
+++ b/inc/confutils.php
@@ -255,6 +255,25 @@ function confToHash($file,$lower=false) {
return linesToHash($lines, $lower);
}
+/**
+ * Read a json config file into an array
+ *
+ * @param string $file
+ * @return array
+ */
+function jsonToArray($file)
+{
+ $json = file_get_contents($file);
+
+ $conf = json_decode($json, true);
+
+ if ($conf === null) {
+ return [];
+ }
+
+ return $conf;
+}
+
/**
* Retrieve the requested configuration information
*
diff --git a/inc/template.php b/inc/template.php
index c44cd064ce..659d590493 100644
--- a/inc/template.php
+++ b/inc/template.php
@@ -242,6 +242,17 @@ function tpl_metaheaders($alt = true) {
);
}
+ if (actionOK('manifest')) {
+ $head['link'][] = array('rel'=> 'manifest', 'href'=> DOKU_BASE.'lib/exe/manifest.php');
+ }
+
+ $styleUtil = new \dokuwiki\StyleUtils();
+ $styleIni = $styleUtil->cssStyleini($conf['template']);
+ $replacements = $styleIni['replacements'];
+ if (!empty($replacements['__theme_color__'])) {
+ $head['meta'][] = array('name' => 'theme-color', 'content' => $replacements['__theme_color__']);
+ }
+
if($alt) {
if(actionOK('rss')) {
$head['link'][] = array(
diff --git a/lib/exe/css.php b/lib/exe/css.php
index ae51d9fc0a..40eaf99a66 100644
--- a/lib/exe/css.php
+++ b/lib/exe/css.php
@@ -45,7 +45,8 @@ function css_out(){
if(!$tpl) $tpl = $conf['template'];
// load style.ini
- $styleini = css_styleini($tpl, $INPUT->bool('preview'));
+ $styleUtil = new \dokuwiki\StyleUtils();
+ $styleini = $styleUtil->cssStyleini($tpl, $INPUT->bool('preview'));
// cache influencers
$tplinc = tpl_incdir($tpl);
@@ -264,106 +265,6 @@ function css_applystyle($css, $replacements) {
return $css;
}
-/**
- * Load style ini contents
- *
- * Loads and merges style.ini files from template and config and prepares
- * the stylesheet modes
- *
- * @author Andreas Gohr
- *
- * @param string $tpl the used template
- * @param bool $preview load preview replacements
- * @return array with keys 'stylesheets' and 'replacements'
- */
-function css_styleini($tpl, $preview=false) {
- global $conf;
-
- $stylesheets = array(); // mode, file => base
- // guaranteed placeholder => value
- $replacements = array(
- '__text__' => "#000",
- '__background__' => "#fff",
- '__text_alt__' => "#999",
- '__background_alt__' => "#eee",
- '__text_neu__' => "#666",
- '__background_neu__' => "#ddd",
- '__border__' => "#ccc",
- '__highlight__' => "#ff9",
- '__link__' => "#00f",
- );
-
- // load template's style.ini
- $incbase = tpl_incdir($tpl);
- $webbase = tpl_basedir($tpl);
- $ini = $incbase.'style.ini';
- if(file_exists($ini)){
- $data = parse_ini_file($ini, true);
-
- // stylesheets
- if(is_array($data['stylesheets'])) foreach($data['stylesheets'] as $file => $mode){
- $stylesheets[$mode][$incbase.$file] = $webbase;
- }
-
- // replacements
- if(is_array($data['replacements'])){
- $replacements = array_merge($replacements, css_fixreplacementurls($data['replacements'],$webbase));
- }
- }
-
- // load configs's style.ini
- $webbase = DOKU_BASE;
- $ini = DOKU_CONF."tpl/$tpl/style.ini";
- $incbase = dirname($ini).'/';
- if(file_exists($ini)){
- $data = parse_ini_file($ini, true);
-
- // stylesheets
- if(isset($data['stylesheets']) && is_array($data['stylesheets'])) foreach($data['stylesheets'] as $file => $mode){
- $stylesheets[$mode][$incbase.$file] = $webbase;
- }
-
- // replacements
- if(isset($data['replacements']) && is_array($data['replacements'])){
- $replacements = array_merge($replacements, css_fixreplacementurls($data['replacements'],$webbase));
- }
- }
-
- // allow replacement overwrites in preview mode
- if($preview) {
- $webbase = DOKU_BASE;
- $ini = $conf['cachedir'].'/preview.ini';
- if(file_exists($ini)) {
- $data = parse_ini_file($ini, true);
- // replacements
- if(is_array($data['replacements'])) {
- $replacements = array_merge($replacements, css_fixreplacementurls($data['replacements'], $webbase));
- }
- }
- }
-
- return array(
- 'stylesheets' => $stylesheets,
- 'replacements' => $replacements
- );
-}
-
-/**
- * Amend paths used in replacement relative urls, refer FS#2879
- *
- * @author Chris Smith
- *
- * @param array $replacements with key-value pairs
- * @param string $location
- * @return array
- */
-function css_fixreplacementurls($replacements, $location) {
- foreach($replacements as $key => $value) {
- $replacements[$key] = preg_replace('#(url\([ \'"]*)(?!/|data:|http://|https://| |\'|")#','\\1'.$location,$value);
- }
- return $replacements;
-}
-
/**
* Wrapper for the files, content and mediatype for the event CSS_STYLES_INCLUDED
*
diff --git a/lib/exe/manifest.php b/lib/exe/manifest.php
new file mode 100644
index 0000000000..e9a35289c0
--- /dev/null
+++ b/lib/exe/manifest.php
@@ -0,0 +1,14 @@
+sendManifest();
diff --git a/lib/plugins/styling/admin.php b/lib/plugins/styling/admin.php
index c747c3130e..055ac2279a 100644
--- a/lib/plugins/styling/admin.php
+++ b/lib/plugins/styling/admin.php
@@ -57,9 +57,9 @@ public function html() {
public function form() {
global $conf;
global $ID;
- define('SIMPLE_TEST', 1); // hack, ideally certain functions should be moved out of css.php
- require_once(DOKU_INC.'lib/exe/css.php');
- $styleini = css_styleini($conf['template'], true);
+
+ $styleUtil = new \dokuwiki\StyleUtils();
+ $styleini = $styleUtil->cssStyleini($conf['template'], true);
$replacements = $styleini['replacements'];
if($this->ispopup) {
diff --git a/lib/tpl/dokuwiki/lang/en/lang.php b/lib/tpl/dokuwiki/lang/en/lang.php
index b7b3e7fa11..7c890c6513 100644
--- a/lib/tpl/dokuwiki/lang/en/lang.php
+++ b/lib/tpl/dokuwiki/lang/en/lang.php
@@ -10,3 +10,4 @@
$lang['__sidebar_width__'] = 'The width of the sidebar, if any (can be any length unit: %, px, em, ...)';
$lang['__tablet_width__'] = 'Below screensizes of this width, the site switches to tablet mode';
$lang['__phone_width__'] = 'Below screensizes of this width, the site switches to phone mode';
+$lang['__theme_color__'] = 'Theme color of the web app';
diff --git a/lib/tpl/dokuwiki/style.ini b/lib/tpl/dokuwiki/style.ini
index 892a6a6e54..ca6de6aa1e 100644
--- a/lib/tpl/dokuwiki/style.ini
+++ b/lib/tpl/dokuwiki/style.ini
@@ -85,3 +85,5 @@ __sidebar_width__ = "16em" ; @ini_sidebar_width
; cut off points for mobile devices
__tablet_width__ = "800px" ; @ini_tablet_width
__phone_width__ = "480px" ; @ini_phone_width
+
+__theme_color__ = "#008800" ; @_ini_theme_color: theme_color of the web app