Co-authored-by: Louis Chemineau <louis@chmn.me> Signed-off-by: Julius Härtl <jus@bitgrid.net>tags/v22.0.0rc1
@@ -178,6 +178,7 @@ class ViewControllerTest extends TestCase { | |||
'icon' => '', | |||
'type' => 'link', | |||
'classes' => '', | |||
'unread' => 0, | |||
], | |||
'recent' => [ | |||
'id' => 'recent', | |||
@@ -189,6 +190,7 @@ class ViewControllerTest extends TestCase { | |||
'icon' => '', | |||
'type' => 'link', | |||
'classes' => '', | |||
'unread' => 0, | |||
], | |||
'favorites' => [ | |||
'id' => 'favorites', | |||
@@ -247,7 +249,8 @@ class ViewControllerTest extends TestCase { | |||
], | |||
], | |||
'defaultExpandedState' => false, | |||
'expandedState' => 'show_Quick_Access' | |||
'expandedState' => 'show_Quick_Access', | |||
'unread' => 0, | |||
], | |||
'systemtagsfilter' => [ | |||
'id' => 'systemtagsfilter', | |||
@@ -259,6 +262,7 @@ class ViewControllerTest extends TestCase { | |||
'icon' => '', | |||
'type' => 'link', | |||
'classes' => '', | |||
'unread' => 0, | |||
], | |||
'trashbin' => [ | |||
'id' => 'trashbin', | |||
@@ -270,6 +274,7 @@ class ViewControllerTest extends TestCase { | |||
'icon' => '', | |||
'type' => 'link', | |||
'classes' => 'pinned', | |||
'unread' => 0, | |||
], | |||
'shareoverview' => [ | |||
'id' => 'shareoverview', | |||
@@ -320,6 +325,7 @@ class ViewControllerTest extends TestCase { | |||
'type' => 'link', | |||
'expandedState' => 'show_sharing_menu', | |||
'defaultExpandedState' => false, | |||
'unread' => 0, | |||
] | |||
]); | |||
@@ -612,6 +612,25 @@ nav[role='navigation'] { | |||
} | |||
} | |||
.unread-counter { | |||
display: none; | |||
} | |||
#apps .app-icon-notification, | |||
#appmenu .app-icon-notification { | |||
fill: var(--color-error); | |||
} | |||
#apps svg:not(.has-unread), | |||
#appmenu svg:not(.has-unread) { | |||
.app-icon-notification-mask { | |||
display: none; | |||
} | |||
.app-icon-notification { | |||
display: none; | |||
} | |||
} | |||
/* Skip navigation links – show only on keyboard focus */ | |||
.skip-navigation { | |||
padding: 11px; |
@@ -32,6 +32,26 @@ import OC from '../OC' | |||
* If the screen is bigger, the main menu is not a toggle any more. | |||
*/ | |||
export const setUp = () => { | |||
Object.assign(OC, { | |||
setNavigationCounter(id, counter) { | |||
const appmenuElement = document.getElementById('appmenu').querySelector('[data-id="' + id + '"] svg') | |||
const appsElement = document.getElementById('apps').querySelector('[data-id="' + id + '"] svg') | |||
if (counter === 0) { | |||
appmenuElement.classList.remove('has-unread') | |||
appsElement.classList.remove('has-unread') | |||
appmenuElement.getElementsByTagName('image')[0].style.mask = '' | |||
appsElement.getElementsByTagName('image')[0].style.mask = '' | |||
} else { | |||
appmenuElement.classList.add('has-unread') | |||
appsElement.classList.add('has-unread') | |||
appmenuElement.getElementsByTagName('image')[0].style.mask = 'url(#hole)' | |||
appsElement.getElementsByTagName('image')[0].style.mask = 'url(#hole)' | |||
} | |||
document.getElementById('appmenu').querySelector('[data-id="' + id + '"] .unread-counter').textContent = counter | |||
document.getElementById('apps').querySelector('[data-id="' + id + '"] .unread-counter').textContent = counter | |||
}, | |||
}) | |||
// init the more-apps menu | |||
OC.registerMenu($('#more-apps > a'), $('#navigation')) | |||
@@ -57,12 +57,18 @@ | |||
<a href="<?php print_unescaped($entry['href']); ?>" | |||
<?php if ($entry['active']): ?> class="active"<?php endif; ?> | |||
aria-label="<?php p($entry['name']); ?>"> | |||
<svg width="20" height="20" viewBox="0 0 20 20" alt=""> | |||
<?php if ($_['themingInvertMenu']) { ?> | |||
<defs><filter id="invertMenuMain-<?php p($entry['id']); ?>"><feColorMatrix in="SourceGraphic" type="matrix" values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0" /></filter></defs> | |||
<?php } ?> | |||
<image x="0" y="0" width="20" height="20" preserveAspectRatio="xMinYMin meet"<?php if ($_['themingInvertMenu']) { ?> filter="url(#invertMenuMain-<?php p($entry['id']); ?>)"<?php } ?> xlink:href="<?php print_unescaped($entry['icon'] . '?v=' . $_['versionHash']); ?>" class="app-icon"></image> | |||
<svg width="24" height="20" viewBox="0 0 24 20" alt=""<?php if ($entry['unread'] !== 0) { ?> class="has-unread"<?php } ?>> | |||
<defs> | |||
<?php if ($_['themingInvertMenu']) { ?><filter id="invertMenuMain-<?php p($entry['id']); ?>"><feColorMatrix in="SourceGraphic" type="matrix" values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0" /></filter><?php } ?> | |||
<mask id="hole"> | |||
<rect width="100%" height="100%" fill="white"/> | |||
<circle r="4.5" cx="21" cy="3" fill="black"/> | |||
</mask> | |||
</defs> | |||
<image x="2" y="0" width="20" height="20" preserveAspectRatio="xMinYMin meet"<?php if ($_['themingInvertMenu']) { ?> filter="url(#invertMenuMain-<?php p($entry['id']); ?>)"<?php } ?> xlink:href="<?php print_unescaped($entry['icon'] . '?v=' . $_['versionHash']); ?>" style="<?php if ($entry['unread'] !== 0) { ?>mask: url("#hole");<?php } ?>" class="app-icon"></image> | |||
<circle class="app-icon-notification" r="3" cx="21" cy="3" fill="red"/> | |||
</svg> | |||
<div class="unread-counter" aria-hidden="true"><?php p($entry['unread']); ?></div> | |||
<span> | |||
<?php p($entry['name']); ?> | |||
</span> | |||
@@ -87,11 +93,19 @@ | |||
<a href="<?php print_unescaped($entry['href']); ?>" | |||
<?php if ($entry['active']): ?> class="active"<?php endif; ?> | |||
aria-label="<?php p($entry['name']); ?>"> | |||
<svg width="16" height="16" viewBox="0 0 16 16" alt=""> | |||
<defs><filter id="invertMenuMore-<?php p($entry['id']); ?>"><feColorMatrix in="SourceGraphic" type="matrix" values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"></feColorMatrix></filter></defs> | |||
<image x="0" y="0" width="16" height="16" preserveAspectRatio="xMinYMin meet" filter="url(#invertMenuMore-<?php p($entry['id']); ?>)" xlink:href="<?php print_unescaped($entry['icon'] . '?v=' . $_['versionHash']); ?>" class="app-icon"></image> | |||
<svg width="20" height="20" viewBox="0 0 20 20" alt=""<?php if ($entry['unread'] !== 0) { ?> class="has-unread"<?php } ?>> | |||
<defs> | |||
<filter id="invertMenuMore-<?php p($entry['id']); ?>"><feColorMatrix in="SourceGraphic" type="matrix" values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"></feColorMatrix></filter> | |||
<mask id="hole"> | |||
<rect width="100%" height="100%" fill="white"/> | |||
<circle r="4.5" cx="17" cy="3" fill="black"/> | |||
</mask> | |||
</defs> | |||
<image x="0" y="0" width="16" height="16" preserveAspectRatio="xMinYMin meet" filter="url(#invertMenuMore-<?php p($entry['id']); ?>)" xlink:href="<?php print_unescaped($entry['icon'] . '?v=' . $_['versionHash']); ?>" style="<?php if ($entry['unread'] !== 0) { ?>mask: url("#hole");<?php } ?>" class="app-icon"></image> | |||
<circle class="app-icon-notification" r="3" cx="17" cy="3" fill="red"/> | |||
</svg> | |||
<span><?php p($entry['name']); ?></span> | |||
<div class="unread-counter" aria-hidden="true"><?php p($entry['unread']); ?></div> | |||
<span class="app-title"><?php p($entry['name']); ?></span> | |||
</a> | |||
</li> | |||
<?php endforeach; ?> |
@@ -49,6 +49,8 @@ class NavigationManager implements INavigationManager { | |||
protected $entries = []; | |||
protected $closureEntries = []; | |||
protected $activeEntry; | |||
protected $unreadCounters = []; | |||
/** @var bool */ | |||
protected $init = false; | |||
/** @var IAppManager|AppManager */ | |||
@@ -97,7 +99,11 @@ class NavigationManager implements INavigationManager { | |||
if (!isset($entry['type'])) { | |||
$entry['type'] = 'link'; | |||
} | |||
$this->entries[$entry['id']] = $entry; | |||
$id = $entry['id']; | |||
$entry['unread'] = isset($this->unreadCounters[$id]) ? $this->unreadCounters[$id] : 0; | |||
$this->entries[$id] = $entry; | |||
} | |||
/** | |||
@@ -319,4 +325,8 @@ class NavigationManager implements INavigationManager { | |||
} | |||
return false; | |||
} | |||
public function setUnreadCounter(string $id, int $unreadCounter): void { | |||
$this->unreadCounters[$id] = $unreadCounter; | |||
} | |||
} |
@@ -90,4 +90,13 @@ interface INavigationManager { | |||
* @since 14.0.0 | |||
*/ | |||
public function getAll(string $type = self::TYPE_APPS): array; | |||
/** | |||
* Set an unread counter for navigation entries | |||
* | |||
* @param string $id id of the navigation entry | |||
* @param int $unreadCounter Number of unread entries (0 to hide the counter which is the default) | |||
* @since 22.0.0 | |||
*/ | |||
public function setUnreadCounter(string $id, int $unreadCounter): void; | |||
} |
@@ -72,7 +72,8 @@ class NavigationManagerTest extends TestCase { | |||
'icon' => 'optional', | |||
'href' => 'url', | |||
'type' => 'settings', | |||
'classes' => '' | |||
'classes' => '', | |||
'unread' => 0 | |||
], | |||
'entry id2' => [ | |||
'id' => 'entry id', | |||
@@ -82,7 +83,8 @@ class NavigationManagerTest extends TestCase { | |||
'href' => 'url', | |||
'active' => false, | |||
'type' => 'settings', | |||
'classes' => '' | |||
'classes' => '', | |||
'unread' => 0 | |||
] | |||
], | |||
[ | |||
@@ -92,7 +94,8 @@ class NavigationManagerTest extends TestCase { | |||
'order' => 1, | |||
//'icon' => 'optional', | |||
'href' => 'url', | |||
'active' => true | |||
'active' => true, | |||
'unread' => 0 | |||
], | |||
'entry id2' => [ | |||
'id' => 'entry id', | |||
@@ -102,7 +105,8 @@ class NavigationManagerTest extends TestCase { | |||
'href' => 'url', | |||
'active' => false, | |||
'type' => 'link', | |||
'classes' => '' | |||
'classes' => '', | |||
'unread' => 0 | |||
] | |||
] | |||
]; | |||
@@ -250,7 +254,8 @@ class NavigationManagerTest extends TestCase { | |||
'name' => 'Apps', | |||
'active' => false, | |||
'type' => 'settings', | |||
'classes' => '' | |||
'classes' => '', | |||
'unread' => 0 | |||
] | |||
]; | |||
$defaults = [ | |||
@@ -262,7 +267,8 @@ class NavigationManagerTest extends TestCase { | |||
'name' => 'Settings', | |||
'active' => false, | |||
'type' => 'settings', | |||
'classes' => '' | |||
'classes' => '', | |||
'unread' => 0 | |||
], | |||
'logout' => [ | |||
'id' => 'logout', | |||
@@ -272,7 +278,8 @@ class NavigationManagerTest extends TestCase { | |||
'name' => 'Log out', | |||
'active' => false, | |||
'type' => 'settings', | |||
'classes' => '' | |||
'classes' => '', | |||
'unread' => 0 | |||
] | |||
]; | |||
@@ -288,7 +295,8 @@ class NavigationManagerTest extends TestCase { | |||
'name' => 'Test', | |||
'active' => false, | |||
'type' => 'link', | |||
'classes' => '' | |||
'classes' => '', | |||
'unread' => 0 | |||
]], | |||
['logout' => $defaults['logout']] | |||
), | |||
@@ -309,7 +317,8 @@ class NavigationManagerTest extends TestCase { | |||
'name' => 'Test', | |||
'active' => false, | |||
'type' => 'settings', | |||
'classes' => '' | |||
'classes' => '', | |||
'unread' => 0 | |||
]], | |||
['logout' => $defaults['logout']] | |||
), | |||
@@ -331,7 +340,8 @@ class NavigationManagerTest extends TestCase { | |||
'name' => 'Test', | |||
'active' => false, | |||
'type' => 'link', | |||
'classes' => '' | |||
'classes' => '', | |||
'unread' => 0 | |||
]], | |||
['logout' => $defaults['logout']] | |||
), |