diff --git a/controllers/config.go b/controllers/config.go index f98075bf3..cdd22f312 100644 --- a/controllers/config.go +++ b/controllers/config.go @@ -72,6 +72,7 @@ func GetWebConfig(w http.ResponseWriter, r *http.Request) { func getConfigResponse() webConfigResponse { pageContent := utils.RenderPageContentMarkdown(data.GetExtraPageBodyContent()) + offlineMessage := utils.RenderSimpleMarkdown(data.GetCustomOfflineMessage()) socialHandles := data.GetSocialHandles() for i, handle := range socialHandles { platform := models.GetSocialHandle(handle.Platform) @@ -119,7 +120,7 @@ func getConfigResponse() webConfigResponse { return webConfigResponse{ Name: data.GetServerName(), Summary: serverSummary, - OfflineMessage: data.GetCustomOfflineMessage(), + OfflineMessage: offlineMessage, Logo: "/logo", Tags: data.GetServerMetadataTags(), Version: config.GetReleaseString(), diff --git a/test/automated/api/configmanagement.test.js b/test/automated/api/configmanagement.test.js index ed389609b..af41c1fb5 100644 --- a/test/automated/api/configmanagement.test.js +++ b/test/automated/api/configmanagement.test.js @@ -371,11 +371,12 @@ test('set override websocket host', async (done) => { test('verify updated config values', async (done) => { const res = await request.get('/api/config'); + expect(res.body.name).toBe(newServerName); expect(res.body.streamTitle).toBe(newStreamTitle); expect(res.body.summary).toBe(`${newServerSummary}`); expect(res.body.extraPageContent).toBe(newPageContent); - expect(res.body.offlineMessage).toBe(newOfflineMessage); + expect(res.body.offlineMessage).toBe(`

${newOfflineMessage}

`); expect(res.body.logo).toBe('/logo'); expect(res.body.socialHandles).toStrictEqual(newSocialHandles); expect(res.body.socketHostOverride).toBe(overriddenWebsocketHost); diff --git a/utils/utils.go b/utils/utils.go index 99fb53399..85801786b 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -145,7 +145,7 @@ func RenderSimpleMarkdown(raw string) string { log.Fatalln(err) } - return buf.String() + return strings.TrimSpace(buf.String()) } // RenderPageContentMarkdown will return HTML specifically handled for the user-specified page content. diff --git a/web/components/admin/config/general/EditInstanceDetails.tsx b/web/components/admin/config/general/EditInstanceDetails.tsx index c6ebf97fc..9c9ec7535 100644 --- a/web/components/admin/config/general/EditInstanceDetails.tsx +++ b/web/components/admin/config/general/EditInstanceDetails.tsx @@ -1,5 +1,9 @@ import React, { useState, useContext, useEffect } from 'react'; -import { Typography } from 'antd'; +import { Button, Typography } from 'antd'; +import { markdown, markdownLanguage } from '@codemirror/lang-markdown'; +import CodeMirror from '@uiw/react-codemirror'; +import { bbedit } from '@uiw/codemirror-theme-bbedit'; +import { languages } from '@codemirror/language-data'; import { TextFieldWithSubmit, TEXTFIELD_TYPE_TEXTAREA, @@ -16,10 +20,13 @@ import { FIELD_PROPS_YP, FIELD_PROPS_NSFW, FIELD_PROPS_HIDE_VIEWER_COUNT, + API_SERVER_OFFLINE_MESSAGE, } from '../../../../utils/config-constants'; import { UpdateArgs } from '../../../../types/config-section'; import { ToggleSwitch } from '../../ToggleSwitch'; import { EditLogo } from '../../EditLogo'; +import FormStatusIndicator from '../../FormStatusIndicator'; +import { createInputStatus, STATUS_SUCCESS } from '../../../../utils/input-statuses'; const { Title } = Typography; @@ -32,6 +39,8 @@ export default function EditInstanceDetails() { const { instanceDetails, yp, hideViewerCount } = serverConfig; const { instanceUrl } = yp; + const [offlineMessageSaveStatus, setOfflineMessageSaveStatus] = useState(null); + useEffect(() => { setFormDataValues({ ...instanceDetails, @@ -56,6 +65,17 @@ export default function EditInstanceDetails() { } }; + const handleSaveOfflineMessage = () => { + postConfigUpdateToAPI({ + apiPath: API_SERVER_OFFLINE_MESSAGE, + data: { value: formDataValues.offlineMessage }, + }); + setOfflineMessageSaveStatus(createInputStatus(STATUS_SUCCESS)); + setTimeout(() => { + setOfflineMessageSaveStatus(null); + }, 2000); + }; + const handleFieldChange = ({ fieldName, value }: UpdateArgs) => { setFormDataValues({ ...formDataValues, @@ -103,14 +123,42 @@ export default function EditInstanceDetails() { onChange={handleFieldChange} /> - +
+
+

Offline Message:

+ { + handleFieldChange({ fieldName: 'offlineMessage', value }); + }} + extensions={[markdown({ base: markdownLanguage, codeLanguages: languages })]} + /> +
+
+ The offline message is displayed to your page visitors when you're not streaming. + Markdown is supported. +
+ + + +
{/* Logo section */} diff --git a/web/components/ui/OfflineBanner/OfflineBanner.tsx b/web/components/ui/OfflineBanner/OfflineBanner.tsx index c27dd5292..a874366a5 100644 --- a/web/components/ui/OfflineBanner/OfflineBanner.tsx +++ b/web/components/ui/OfflineBanner/OfflineBanner.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react/no-danger */ /* eslint-disable jsx-a11y/click-events-have-key-events */ import { Divider } from 'antd'; import { FC } from 'react'; @@ -85,7 +86,12 @@ export const OfflineBanner: FC = ({ )} -
{text}
+ {customText ? ( +
+ ) : ( +
{text}
+ )} + {lastLive && (