0

feat: add support for markdown in offline message. Closes #2921 (#2977)

This commit is contained in:
Gabe Kangas 2023-05-01 19:45:27 -07:00 committed by GitHub
parent 295e7768a0
commit 7aca27cb08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 69 additions and 13 deletions

View File

@ -72,6 +72,7 @@ func GetWebConfig(w http.ResponseWriter, r *http.Request) {
func getConfigResponse() webConfigResponse { func getConfigResponse() webConfigResponse {
pageContent := utils.RenderPageContentMarkdown(data.GetExtraPageBodyContent()) pageContent := utils.RenderPageContentMarkdown(data.GetExtraPageBodyContent())
offlineMessage := utils.RenderSimpleMarkdown(data.GetCustomOfflineMessage())
socialHandles := data.GetSocialHandles() socialHandles := data.GetSocialHandles()
for i, handle := range socialHandles { for i, handle := range socialHandles {
platform := models.GetSocialHandle(handle.Platform) platform := models.GetSocialHandle(handle.Platform)
@ -119,7 +120,7 @@ func getConfigResponse() webConfigResponse {
return webConfigResponse{ return webConfigResponse{
Name: data.GetServerName(), Name: data.GetServerName(),
Summary: serverSummary, Summary: serverSummary,
OfflineMessage: data.GetCustomOfflineMessage(), OfflineMessage: offlineMessage,
Logo: "/logo", Logo: "/logo",
Tags: data.GetServerMetadataTags(), Tags: data.GetServerMetadataTags(),
Version: config.GetReleaseString(), Version: config.GetReleaseString(),

View File

@ -371,11 +371,12 @@ test('set override websocket host', async (done) => {
test('verify updated config values', async (done) => { test('verify updated config values', async (done) => {
const res = await request.get('/api/config'); const res = await request.get('/api/config');
expect(res.body.name).toBe(newServerName); expect(res.body.name).toBe(newServerName);
expect(res.body.streamTitle).toBe(newStreamTitle); expect(res.body.streamTitle).toBe(newStreamTitle);
expect(res.body.summary).toBe(`${newServerSummary}`); expect(res.body.summary).toBe(`${newServerSummary}`);
expect(res.body.extraPageContent).toBe(newPageContent); expect(res.body.extraPageContent).toBe(newPageContent);
expect(res.body.offlineMessage).toBe(newOfflineMessage); expect(res.body.offlineMessage).toBe(`<p>${newOfflineMessage}</p>`);
expect(res.body.logo).toBe('/logo'); expect(res.body.logo).toBe('/logo');
expect(res.body.socialHandles).toStrictEqual(newSocialHandles); expect(res.body.socialHandles).toStrictEqual(newSocialHandles);
expect(res.body.socketHostOverride).toBe(overriddenWebsocketHost); expect(res.body.socketHostOverride).toBe(overriddenWebsocketHost);

View File

@ -145,7 +145,7 @@ func RenderSimpleMarkdown(raw string) string {
log.Fatalln(err) log.Fatalln(err)
} }
return buf.String() return strings.TrimSpace(buf.String())
} }
// RenderPageContentMarkdown will return HTML specifically handled for the user-specified page content. // RenderPageContentMarkdown will return HTML specifically handled for the user-specified page content.

View File

@ -1,5 +1,9 @@
import React, { useState, useContext, useEffect } from 'react'; 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 { import {
TextFieldWithSubmit, TextFieldWithSubmit,
TEXTFIELD_TYPE_TEXTAREA, TEXTFIELD_TYPE_TEXTAREA,
@ -16,10 +20,13 @@ import {
FIELD_PROPS_YP, FIELD_PROPS_YP,
FIELD_PROPS_NSFW, FIELD_PROPS_NSFW,
FIELD_PROPS_HIDE_VIEWER_COUNT, FIELD_PROPS_HIDE_VIEWER_COUNT,
API_SERVER_OFFLINE_MESSAGE,
} from '../../../../utils/config-constants'; } from '../../../../utils/config-constants';
import { UpdateArgs } from '../../../../types/config-section'; import { UpdateArgs } from '../../../../types/config-section';
import { ToggleSwitch } from '../../ToggleSwitch'; import { ToggleSwitch } from '../../ToggleSwitch';
import { EditLogo } from '../../EditLogo'; import { EditLogo } from '../../EditLogo';
import FormStatusIndicator from '../../FormStatusIndicator';
import { createInputStatus, STATUS_SUCCESS } from '../../../../utils/input-statuses';
const { Title } = Typography; const { Title } = Typography;
@ -32,6 +39,8 @@ export default function EditInstanceDetails() {
const { instanceDetails, yp, hideViewerCount } = serverConfig; const { instanceDetails, yp, hideViewerCount } = serverConfig;
const { instanceUrl } = yp; const { instanceUrl } = yp;
const [offlineMessageSaveStatus, setOfflineMessageSaveStatus] = useState(null);
useEffect(() => { useEffect(() => {
setFormDataValues({ setFormDataValues({
...instanceDetails, ...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) => { const handleFieldChange = ({ fieldName, value }: UpdateArgs) => {
setFormDataValues({ setFormDataValues({
...formDataValues, ...formDataValues,
@ -103,14 +123,42 @@ export default function EditInstanceDetails() {
onChange={handleFieldChange} onChange={handleFieldChange}
/> />
<TextFieldWithSubmit <div style={{ marginBottom: '50px', marginRight: '150px' }}>
fieldName="offlineMessage" <div
{...TEXTFIELD_PROPS_SERVER_OFFLINE_MESSAGE} style={{
type={TEXTFIELD_TYPE_TEXTAREA} display: 'flex',
value={formDataValues.offlineMessage} width: '80vh',
initialValue={instanceDetails.offlineMessage} justifyContent: 'space-between',
onChange={handleFieldChange} alignItems: 'end',
/> }}
>
<p style={{ margin: '20px', marginRight: '10px', fontWeight: '400' }}>Offline Message:</p>
<CodeMirror
value={formDataValues.offlineMessage}
{...TEXTFIELD_PROPS_SERVER_OFFLINE_MESSAGE}
theme={bbedit}
height="150px"
width="450px"
onChange={value => {
handleFieldChange({ fieldName: 'offlineMessage', value });
}}
extensions={[markdown({ base: markdownLanguage, codeLanguages: languages })]}
/>
</div>
<div className="field-tip">
The offline message is displayed to your page visitors when you&apos;re not streaming.
Markdown is supported.
</div>
<Button
type="primary"
onClick={handleSaveOfflineMessage}
style={{ margin: '10px', float: 'right' }}
>
Save Message
</Button>
<FormStatusIndicator status={offlineMessageSaveStatus} />
</div>
{/* Logo section */} {/* Logo section */}
<EditLogo /> <EditLogo />

View File

@ -1,3 +1,4 @@
/* eslint-disable react/no-danger */
/* eslint-disable jsx-a11y/click-events-have-key-events */ /* eslint-disable jsx-a11y/click-events-have-key-events */
import { Divider } from 'antd'; import { Divider } from 'antd';
import { FC } from 'react'; import { FC } from 'react';
@ -85,7 +86,12 @@ export const OfflineBanner: FC<OfflineBannerProps> = ({
<Divider className={styles.separator} /> <Divider className={styles.separator} />
</> </>
)} )}
<div className={styles.bodyText}>{text}</div> {customText ? (
<div className={styles.bodyText} dangerouslySetInnerHTML={{ __html: text }} />
) : (
<div className={styles.bodyText}>{text}</div>
)}
{lastLive && ( {lastLive && (
<div className={styles.lastLiveDate}> <div className={styles.lastLiveDate}>
<ClockCircleOutlined className={styles.clockIcon} /> <ClockCircleOutlined className={styles.clockIcon} />