Merge pull request #25507 from GursheenK/announcement-widget-navbar

feat: navbar announcement widget
This commit is contained in:
Ankush Menat 2024-04-02 22:38:52 +05:30 committed by GitHub
commit d1210f7edc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 191 additions and 139 deletions

View file

@ -1,7 +1,4 @@
// Copyright (c) 2020, Frappe Technologies and contributors
// For license information, please see license.txt
frappe.ui.form.on("Navbar Settings", {
// refresh: function(frm) {
// }
});
frappe.ui.form.on("Navbar Settings", {});

View file

@ -11,7 +11,9 @@
"logo_width",
"section_break_2",
"settings_dropdown",
"help_dropdown"
"help_dropdown",
"announcements_section",
"announcement_widget"
],
"fields": [
{
@ -49,11 +51,23 @@
"fieldname": "logo_width",
"fieldtype": "Int",
"label": "Logo Width"
},
{
"fieldname": "announcements_section",
"fieldtype": "Section Break",
"label": "Announcements"
},
{
"description": "These announcements will appear inside a dismissible alert below the Navbar.",
"fieldname": "announcement_widget",
"fieldtype": "Text Editor",
"label": "Announcement Widget",
"max_height": "10em"
}
],
"issingle": 1,
"links": [],
"modified": "2024-03-23 16:03:30.561647",
"modified": "2024-03-23 17:03:30.561647",
"modified_by": "Administrator",
"module": "Core",
"name": "Navbar Settings",
@ -75,4 +89,4 @@
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View file

@ -16,6 +16,7 @@ class NavbarSettings(Document):
from frappe.core.doctype.navbar_item.navbar_item import NavbarItem
from frappe.types import DF
announcement_widget: DF.TextEditor | None
app_logo: DF.AttachImage | None
help_dropdown: DF.Table[NavbarItem]
logo_width: DF.Int

View file

@ -1,139 +1,153 @@
<header class="navbar navbar-expand sticky-top" role="navigation">
<div class="container">
<a class="navbar-brand navbar-home" href="/app">
<img
class="app-logo"
style="width: {{ navbar_settings.logo_width || 28 }}px"
src="{{ frappe.boot.app_logo_url }}"
alt="{{ __("App Logo") }}"
>
</a>
<ul class="nav navbar-nav d-none d-sm-flex" id="navbar-breadcrumbs"></ul>
<div class="collapse navbar-collapse justify-content-end">
<form class="form-inline fill-width justify-content-end" role="search" onsubmit="return false;">
{% if (frappe.boot.read_only) { %}
<span class="indicator-pill yellow no-indicator-dot read-only-banner" title="{%= __("Your site is undergoing maintenance or being updated.") %}">
{%= __("Read Only Mode") %}
</span>
{% } %}
{% if (frappe.boot.user.impersonated_by) { %}
<span class="indicator-pill red no-indicator-dot" title="{%= __("You are impersonating as another user.") %}">
{%= __("Impersonating {0}", [frappe.boot.user.name]) %}
</span>
{% } %}
<div class="input-group search-bar text-muted hidden">
<input
id="navbar-search"
type="text"
class="form-control"
placeholder="{%= __('Search or type a command ({0})', [frappe.utils.is_mac() ? '⌘ + G' : 'Ctrl + G']) %}"
aria-haspopup="true"
>
<span class="search-icon">
<svg class="icon icon-sm"><use href="#icon-search"></use></svg>
</span>
</div>
</form>
<ul class="navbar-nav">
<li class="nav-item dropdown dropdown-notifications dropdown-mobile hidden">
<button
class="btn-reset nav-link notifications-icon text-muted"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
<span class="notifications-seen">
<span class="sr-only">{{ __("No new notifications") }}</span>
<svg class="es-icon icon-sm" style="stroke:none;"><use href="#es-line-notifications"></use></svg>
<div class="sticky-top">
<header class="navbar navbar-expand" role="navigation">
<div class="container">
<a class="navbar-brand navbar-home" href="/app">
<img
class="app-logo"
style="width: {{ navbar_settings.logo_width || 28 }}px"
src="{{ frappe.boot.app_logo_url }}"
alt="{{ __("App Logo") }}"
>
</a>
<ul class="nav navbar-nav d-none d-sm-flex" id="navbar-breadcrumbs"></ul>
<div class="collapse navbar-collapse justify-content-end">
<form class="form-inline fill-width justify-content-end" role="search" onsubmit="return false;">
{% if (frappe.boot.read_only) { %}
<span class="indicator-pill yellow no-indicator-dot read-only-banner" title="{%= __("Your site is undergoing maintenance or being updated.") %}">
{%= __("Read Only Mode") %}
</span>
<span class="notifications-unseen">
<span class="sr-only">{{ __("You have unseen notifications") }}</span>
<svg class="es-icon icon-sm"><use href="#es-line-notifications-unseen"></use></svg>
{% } %}
{% if (frappe.boot.user.impersonated_by) { %}
<span class="indicator-pill red no-indicator-dot" title="{%= __("You are impersonating as another user.") %}">
{%= __("Impersonating {0}", [frappe.boot.user.name]) %}
</span>
</button>
<div class="dropdown-menu notifications-list dropdown-menu-right" role="menu">
<div class="notification-list-header">
<div class="header-items"></div>
<div class="header-actions"></div>
{% } %}
<div class="input-group search-bar text-muted hidden">
<input
id="navbar-search"
type="text"
class="form-control"
placeholder="{%= __('Search or type a command ({0})', [frappe.utils.is_mac() ? '⌘ + G' : 'Ctrl + G']) %}"
aria-haspopup="true"
>
<span class="search-icon">
<svg class="icon icon-sm"><use href="#icon-search"></use></svg>
</span>
</div>
</form>
<ul class="navbar-nav">
<li class="nav-item dropdown dropdown-notifications dropdown-mobile hidden">
<button
class="btn-reset nav-link notifications-icon text-muted"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
<span class="notifications-seen">
<span class="sr-only">{{ __("No new notifications") }}</span>
<svg class="es-icon icon-sm" style="stroke:none;"><use href="#es-line-notifications"></use></svg>
</span>
<span class="notifications-unseen">
<span class="sr-only">{{ __("You have unseen notifications") }}</span>
<svg class="es-icon icon-sm"><use href="#es-line-notifications-unseen"></use></svg>
</span>
</button>
<div class="dropdown-menu notifications-list dropdown-menu-right" role="menu">
<div class="notification-list-header">
<div class="header-items"></div>
<div class="header-actions"></div>
</div>
<div class="notification-list-body">
<div class="panel-notifications"></div>
<div class="panel-events"></div>
</div>
</div>
<div class="notification-list-body">
<div class="panel-notifications"></div>
<div class="panel-events"></div>
</li>
<li class="nav-item dropdown dropdown-message dropdown-mobile hidden">
<button
class="btn-reset nav-link notifications-icon text-muted"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="true"
>
<span>
<svg class="es-icon icon-sm"><use href="#es-line-chat-alt"></use></svg>
</span>
</button>
</li>
<li class="vertical-bar d-none d-sm-block"></li>
<li class="nav-item dropdown dropdown-help dropdown-mobile d-none d-lg-block">
<button
class="btn-reset nav-link"
data-toggle="dropdown"
aria-controls="toolbar-help"
aria-label="{{ __("Help Dropdown") }}"
>
<span>
{{ __("Help") }}
<svg class="es-icon icon-xs"><use href="#es-line-down"></use></svg>
</span>
</button>
<div class="dropdown-menu dropdown-menu-right" id="toolbar-help" role="menu">
<div id="help-links"></div>
<div class="dropdown-divider documentation-links"></div>
{% for item in navbar_settings.help_dropdown %}
{% if (!item.hidden) { %}
{% if (item.route) { %}
<a class="dropdown-item" href="{{ item.route }}">
{%= __(item.item_label) %}
</a>
{% } else if (item.action) { %}
<button class="btn-reset dropdown-item" onclick="return {{ item.action }}">
{%= __(item.item_label) %}
</button>
{% } else { %}
<div class="dropdown-divider"></div>
{% } %}
{% } %}
{% endfor %}
</div>
</div>
</li>
<li class="nav-item dropdown dropdown-message dropdown-mobile hidden">
<button
class="btn-reset nav-link notifications-icon text-muted"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="true"
>
<span>
<svg class="es-icon icon-sm"><use href="#es-line-chat-alt"></use></svg>
</span>
</button>
</li>
<li class="vertical-bar d-none d-sm-block"></li>
<li class="nav-item dropdown dropdown-help dropdown-mobile d-none d-lg-block">
<button
class="btn-reset nav-link"
data-toggle="dropdown"
aria-controls="toolbar-help"
aria-label="{{ __("Help Dropdown") }}"
>
<span>
{{ __("Help") }}
<svg class="es-icon icon-xs"><use href="#es-line-down"></use></svg>
</span>
</button>
<div class="dropdown-menu dropdown-menu-right" id="toolbar-help" role="menu">
<div id="help-links"></div>
<div class="dropdown-divider documentation-links"></div>
{% for item in navbar_settings.help_dropdown %}
{% if (!item.hidden) { %}
{% if (item.route) { %}
<a class="dropdown-item" href="{{ item.route }}">
{%= __(item.item_label) %}
</a>
{% } else if (item.action) { %}
<button class="btn-reset dropdown-item" onclick="return {{ item.action }}">
{%= __(item.item_label) %}
</button>
{% } else { %}
<div class="dropdown-divider"></div>
</li>
<li class="nav-item dropdown dropdown-navbar-user dropdown-mobile">
<button
class="btn-reset nav-link"
data-toggle="dropdown"
aria-label="{{ __("User Menu") }}"
>
{{ avatar }}
</button>
<div class="dropdown-menu dropdown-menu-right" id="toolbar-user" role="menu">
{% for item in navbar_settings.settings_dropdown %}
{% if (!item.hidden) { %}
{% if (item.route) { %}
<a class="dropdown-item" href="{{ item.route }}">
{%= __(item.item_label) %}
</a>
{% } else if (item.action) { %}
<button class="btn-reset dropdown-item" onclick="return {{ item.action }}">
{%= __(item.item_label) %}
</button>
{% } else { %}
<div class="dropdown-divider"></div>
{% } %}
{% } %}
{% } %}
{% endfor %}
</div>
</li>
<li class="nav-item dropdown dropdown-navbar-user dropdown-mobile">
<button
class="btn-reset nav-link"
data-toggle="dropdown"
aria-label="{{ __("User Menu") }}"
>
{{ avatar }}
</button>
<div class="dropdown-menu dropdown-menu-right" id="toolbar-user" role="menu">
{% for item in navbar_settings.settings_dropdown %}
{% if (!item.hidden) { %}
{% if (item.route) { %}
<a class="dropdown-item" href="{{ item.route }}">
{%= __(item.item_label) %}
</a>
{% } else if (item.action) { %}
<button class="btn-reset dropdown-item" onclick="return {{ item.action }}">
{%= __(item.item_label) %}
</button>
{% } else { %}
<div class="dropdown-divider"></div>
{% } %}
{% } %}
{% endfor %}
</div>
</li>
</ul>
{% endfor %}
</div>
</li>
</ul>
</div>
</div>
</header>
{% if !localStorage.getItem("dismissed_announcement_widget") && strip_html(navbar_settings.announcement_widget) != '' %}
<div class="announcement-widget form-message p-2 m-0" style="position: relative; z-index: -1; border-radius: 0; background-color: var(--bg-blue);">
<div class="container flex justify-between align-center mx-auto">
{{ navbar_settings.announcement_widget }}
<div class="close-message p-0 mr-2" style="position: relative;">
{{ frappe.utils.icon("close") }}
</div>
</div>
</div>
</header>
{% endif %}
</div>

View file

@ -21,6 +21,7 @@ frappe.ui.toolbar.Toolbar = class {
this.setup_notifications();
this.setup_help();
this.setup_read_only_mode();
this.setup_announcement_widget();
this.make();
}
@ -56,6 +57,30 @@ frappe.ui.toolbar.Toolbar = class {
});
}
setup_announcement_widget() {
let current_announcement = frappe.boot.navbar_settings.announcement_widget;
if (!current_announcement) return;
// If an unseen announcement is added, overlook dismiss flag
if (current_announcement != localStorage.getItem("announcement_widget")) {
localStorage.removeItem("dismissed_announcement_widget");
localStorage.setItem("announcement_widget", current_announcement);
}
// When an announcement is closed, add dismiss flag
if (!localStorage.getItem("dismissed_announcement_widget")) {
let announcement_widget = $(".announcement-widget");
let close_message = announcement_widget.find(".close-message");
close_message.on(
"click",
() =>
localStorage.setItem("dismissed_announcement_widget", true) ||
announcement_widget.addClass("hidden")
);
}
}
setup_help() {
if (!frappe.boot.desk_settings.notifications) {
// hide the help section

View file

@ -167,6 +167,7 @@ def get():
bootinfo["desk_theme"] = frappe.db.get_value("User", frappe.session.user, "desk_theme") or "Light"
bootinfo["user"]["impersonated_by"] = frappe.session.data.get("impersonated_by")
bootinfo["navbar_settings"] = frappe.get_cached_doc("Navbar Settings")
return bootinfo