mirror of
https://github.com/go-gitea/gitea.git
synced 2026-02-21 20:08:11 +01:00
feat: add dismiss functionality to instance notice banner
This commit is contained in:
parent
824d846f79
commit
b92f648555
@ -3299,6 +3299,7 @@
|
||||
"admin.config.instance_notice.delete": "Delete banner",
|
||||
"admin.config.instance_notice.delete_success": "Instance banner deleted.",
|
||||
"admin.config.instance_notice.edit_hint": "Edit this banner",
|
||||
"admin.config.instance_notice.dismiss": "Dismiss",
|
||||
"admin.config.session_config": "Session Configuration",
|
||||
"admin.config.session_provider": "Session Provider",
|
||||
"admin.config.provider_config": "Provider Config",
|
||||
|
||||
@ -5,6 +5,8 @@ package common
|
||||
|
||||
import (
|
||||
goctx "context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
@ -78,7 +80,8 @@ type pageGlobalDataType struct {
|
||||
}
|
||||
|
||||
type InstanceNoticeBannerTmplInfo struct {
|
||||
Message string
|
||||
Message string
|
||||
DismissKey string
|
||||
}
|
||||
|
||||
func getInstanceNoticeBanner(ctx *context.Context) *InstanceNoticeBannerTmplInfo {
|
||||
@ -86,8 +89,11 @@ func getInstanceNoticeBanner(ctx *context.Context) *InstanceNoticeBannerTmplInfo
|
||||
if !notice.IsActive(int64(timeutil.TimeStampNow())) {
|
||||
return nil
|
||||
}
|
||||
h := sha256.Sum256([]byte(notice.Message))
|
||||
dismissKey := hex.EncodeToString(h[:])[:16]
|
||||
return &InstanceNoticeBannerTmplInfo{
|
||||
Message: notice.Message,
|
||||
Message: notice.Message,
|
||||
DismissKey: dismissKey,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -178,9 +178,12 @@
|
||||
</nav>
|
||||
{{if and .PageGlobalData .PageGlobalData.InstanceNoticeBanner}}
|
||||
{{$banner := .PageGlobalData.InstanceNoticeBanner}}
|
||||
<div class="ui info attached message tw-m-0 tw-rounded-none">
|
||||
<div class="tw-flex tw-items-center tw-justify-center">
|
||||
<div class="render-content markup tw-text-center">{{ctx.RenderUtils.MarkdownToHtml $banner.Message}}</div>
|
||||
<div id="instance-notice-banner" class="ui info attached message tw-m-0 tw-rounded-none" data-dismiss-key="{{$banner.DismissKey}}">
|
||||
<div class="tw-flex tw-items-center tw-justify-center tw-gap-3">
|
||||
<div class="render-content markup tw-text-center tw-flex-1">{{ctx.RenderUtils.MarkdownToHtml $banner.Message}}</div>
|
||||
<button type="button" class="ui mini icon button instance-notice-dismiss tw-shrink-0" aria-label="{{ctx.Locale.Tr "admin.config.instance_notice.dismiss"}}">
|
||||
{{svg "octicon-x"}}
|
||||
</button>
|
||||
</div>
|
||||
{{if .PageGlobalData.IsSiteAdmin}}
|
||||
<div class="tw-mt-2 tw-text-center">
|
||||
|
||||
84
web_src/js/features/instance-notice.test.ts
Normal file
84
web_src/js/features/instance-notice.test.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import {beforeEach, describe, expect, test, vi} from 'vitest';
|
||||
import {initInstanceNotice} from './instance-notice.ts';
|
||||
import {localUserSettings} from '../modules/user-settings.ts';
|
||||
|
||||
vi.mock('../modules/user-settings.ts', () => ({
|
||||
localUserSettings: {
|
||||
getString: vi.fn(),
|
||||
setString: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
function createBannerDOM(dismissKey: string) {
|
||||
document.body.innerHTML = `
|
||||
<div id="instance-notice-banner" class="ui info attached message" data-dismiss-key="${dismissKey}">
|
||||
<div class="tw-flex tw-items-center tw-justify-center tw-gap-3">
|
||||
<div class="render-content markup tw-text-center tw-flex-1">Maintenance in progress</div>
|
||||
<button type="button" class="ui mini icon button instance-notice-dismiss">X</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
describe('Instance Notice Banner Dismiss', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
document.body.innerHTML = '';
|
||||
});
|
||||
|
||||
test('no banner in DOM does nothing', () => {
|
||||
initInstanceNotice();
|
||||
expect(localUserSettings.getString).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('hides banner when dismiss key matches stored value', () => {
|
||||
createBannerDOM('abc123');
|
||||
vi.mocked(localUserSettings.getString).mockReturnValue('abc123');
|
||||
|
||||
initInstanceNotice();
|
||||
|
||||
const banner = document.querySelector<HTMLElement>('#instance-notice-banner')!;
|
||||
expect(banner.style.display).toBe('none');
|
||||
expect(localUserSettings.setString).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('does not hide banner when stored key differs', () => {
|
||||
createBannerDOM('abc123');
|
||||
vi.mocked(localUserSettings.getString).mockReturnValue('old-key');
|
||||
|
||||
initInstanceNotice();
|
||||
|
||||
const banner = document.querySelector<HTMLElement>('#instance-notice-banner')!;
|
||||
expect(banner.style.display).not.toBe('none');
|
||||
});
|
||||
|
||||
test('clicking dismiss button stores key and hides banner', () => {
|
||||
createBannerDOM('abc123');
|
||||
vi.mocked(localUserSettings.getString).mockReturnValue('');
|
||||
|
||||
initInstanceNotice();
|
||||
|
||||
const banner = document.querySelector<HTMLElement>('#instance-notice-banner')!;
|
||||
expect(banner.style.display).not.toBe('none');
|
||||
|
||||
const dismissBtn = banner.querySelector<HTMLButtonElement>('.instance-notice-dismiss')!;
|
||||
dismissBtn.click();
|
||||
|
||||
expect(localUserSettings.setString).toHaveBeenCalledWith('instance_notice_dismissed', 'abc123');
|
||||
expect(banner.style.display).toBe('none');
|
||||
});
|
||||
|
||||
test('banner without data-dismiss-key does nothing', () => {
|
||||
document.body.innerHTML = `
|
||||
<div id="instance-notice-banner" class="ui info attached message">
|
||||
<div>Some message</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
initInstanceNotice();
|
||||
|
||||
const banner = document.querySelector<HTMLElement>('#instance-notice-banner')!;
|
||||
expect(banner.style.display).not.toBe('none');
|
||||
expect(localUserSettings.getString).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
28
web_src/js/features/instance-notice.ts
Normal file
28
web_src/js/features/instance-notice.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import {localUserSettings} from '../modules/user-settings.ts';
|
||||
|
||||
const DISMISSED_KEY = 'instance_notice_dismissed';
|
||||
|
||||
function hideBanner(el: HTMLElement) {
|
||||
el.style.display = 'none';
|
||||
}
|
||||
|
||||
export function initInstanceNotice(): void {
|
||||
const banner = document.querySelector<HTMLElement>('#instance-notice-banner');
|
||||
if (!banner) return;
|
||||
|
||||
const dismissKey = banner.getAttribute('data-dismiss-key');
|
||||
if (!dismissKey) return;
|
||||
|
||||
if (localUserSettings.getString(DISMISSED_KEY, '') === dismissKey) {
|
||||
hideBanner(banner);
|
||||
return;
|
||||
}
|
||||
|
||||
const dismissBtn = banner.querySelector<HTMLButtonElement>('.instance-notice-dismiss');
|
||||
if (dismissBtn) {
|
||||
dismissBtn.addEventListener('click', () => {
|
||||
localUserSettings.setString(DISMISSED_KEY, dismissKey);
|
||||
hideBanner(banner);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -64,6 +64,7 @@ import {initGlobalButtonClickOnEnter, initGlobalButtons, initGlobalDeleteButton}
|
||||
import {initGlobalComboMarkdownEditor, initGlobalEnterQuickSubmit, initGlobalFormDirtyLeaveConfirm} from './features/common-form.ts';
|
||||
import {callInitFunctions} from './modules/init.ts';
|
||||
import {initRepoViewFileTree} from './features/repo-view-file-tree.ts';
|
||||
import {initInstanceNotice} from './features/instance-notice.ts';
|
||||
|
||||
const initStartTime = performance.now();
|
||||
const initPerformanceTracer = callInitFunctions([
|
||||
@ -75,6 +76,7 @@ const initPerformanceTracer = callInitFunctions([
|
||||
initGlobalDropdown,
|
||||
initGlobalFetchAction,
|
||||
initGlobalTooltips,
|
||||
initInstanceNotice,
|
||||
initGlobalButtonClickOnEnter,
|
||||
initGlobalButtons,
|
||||
initGlobalCopyToClipboardListener,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user