Skip to content

Commit 6982a88

Browse files
author
shuai
committed
Merge branch 'external-img' into test
2 parents 5586940 + 85903cf commit 6982a88

13 files changed

Lines changed: 327 additions & 3 deletions

File tree

i18n/en_US.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1512,6 +1512,9 @@ ui:
15121512
view: View
15131513
card: Card
15141514
compact: Compact
1515+
display_below: Display below
1516+
always_display: Always display
1517+
or: or
15151518
search:
15161519
title: Search Results
15171520
keywords: Keywords
@@ -2057,6 +2060,11 @@ ui:
20572060
privacy_policy:
20582061
label: Privacy policy
20592062
text: "You can add privacy policy content here. If you already have a document hosted elsewhere, provide the full URL here."
2063+
external_content_display:
2064+
label: External content
2065+
text: "Content includes images, videos, and media embedded from external websites."
2066+
always_display: Always display external content
2067+
ask_before_display: Ask before displaying external content
20602068
write:
20612069
page_title: Write
20622070
restrict_answer:
@@ -2315,5 +2323,6 @@ ui:
23152323
answers_deleted: These answers have been deleted.
23162324
copy: Copy to clipboard
23172325
copied: Copied
2326+
external_content_warning: External images/media are not displayed.
23182327

23192328

i18n/zh_CN.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1480,6 +1480,9 @@ ui:
14801480
view: 视图
14811481
card: 卡片模式
14821482
compact: 简洁模式
1483+
display_below: 展示当前
1484+
always_display: 一直展示
1485+
or: 或者
14831486
search:
14841487
title: 搜索结果
14851488
keywords: 关键词
@@ -2011,6 +2014,11 @@ ui:
20112014
privacy_policy:
20122015
label: 隐私政策
20132016
text: "您可以在此添加隐私政策内容。如果您已经在别处托管了文档,请在这里提供完整的URL。"
2017+
external_content_display:
2018+
label: 外部内容
2019+
text: "内容包括外部网站嵌入的图片、视频和媒体。"
2020+
always_display: 始终显示外部内容
2021+
ask_before_display: 在显示外部内容前进行询问
20142022
write:
20152023
page_title: 编辑
20162024
restrict_answer:
@@ -2271,4 +2279,5 @@ ui:
22712279
answers_deleted: 这些回答已被删除。
22722280
copy: 复制
22732281
copied: 已复制
2282+
external_content_warning: 不显示外部图像/媒体。
22742283

ui/src/common/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export const DEFAULT_THEME = 'system';
3131
export const ADMIN_PRIVILEGE_CUSTOM_LEVEL = 99;
3232
export const SKELETON_SHOW_TIME = 1000;
3333
export const LIST_VIEW_STORAGE_KEY = '_a_list_view_';
34+
export const EXTERNAL_CONTENT_DISPLAY_MODE = '_a_ecd_';
3435

3536
export const USER_AGENT_NAMES = {
3637
SegmentFault: 'SegmentFault',

ui/src/common/interface.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,7 @@ export interface SiteSettings {
419419
site_write: AdminSettingsWrite;
420420
version: string;
421421
revision: string;
422+
site_legal: AdminSettingsLegal;
422423
}
423424

424425
export interface AdminSettingBranding {
@@ -429,6 +430,7 @@ export interface AdminSettingBranding {
429430
}
430431

431432
export interface AdminSettingsLegal {
433+
external_content_display: string;
432434
privacy_policy_original_text?: string;
433435
privacy_policy_parsed_text?: string;
434436
terms_of_service_original_text?: string;

ui/src/hooks/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import usePromptWithUnload from './usePrompt';
3131
import useActivationEmailModal from './useActivationEmailModal';
3232
import useCaptchaModal from './useCaptchaModal';
3333
import useSkeletonControl from './useSkeletonControl';
34+
import useExternalToast from './useExternalToast';
3435

3536
export {
3637
useTagModal,
@@ -47,4 +48,5 @@ export {
4748
useActivationEmailModal,
4849
useCaptchaModal,
4950
useSkeletonControl,
51+
useExternalToast,
5052
};
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
import { useLayoutEffect, useState } from 'react';
21+
import { Toast, Button } from 'react-bootstrap';
22+
import { useTranslation } from 'react-i18next';
23+
24+
import ReactDOM from 'react-dom/client';
25+
26+
import { EXTERNAL_CONTENT_DISPLAY_MODE } from '@/common/constants';
27+
import { Storage } from '@/utils';
28+
29+
const toastPortal = document.createElement('div');
30+
toastPortal.style.position = 'fixed';
31+
toastPortal.style.top = '90px';
32+
toastPortal.style.left = '50%';
33+
toastPortal.style.transform = 'translate(-50%, 0)';
34+
toastPortal.style.maxWidth = '100%';
35+
toastPortal.style.zIndex = '1001';
36+
37+
const setPortalPosition = () => {
38+
const header = document.querySelector('#header');
39+
if (header) {
40+
toastPortal.style.top = `${header.getBoundingClientRect().top + 90}px`;
41+
}
42+
};
43+
const startHandlePortalPosition = () => {
44+
setPortalPosition();
45+
window.addEventListener('scroll', setPortalPosition);
46+
};
47+
48+
const stopHandlePortalPosition = () => {
49+
setPortalPosition();
50+
window.removeEventListener('scroll', setPortalPosition);
51+
};
52+
53+
const root = ReactDOM.createRoot(toastPortal);
54+
55+
const useExternalToast = () => {
56+
const [show, setShow] = useState(false);
57+
const { t } = useTranslation('translation', { keyPrefix: 'messages' });
58+
59+
const onClose = () => {
60+
const parent = document.querySelector('.page-wrap');
61+
if (parent?.contains(toastPortal)) {
62+
parent.removeChild(toastPortal);
63+
}
64+
stopHandlePortalPosition();
65+
setShow(false);
66+
};
67+
68+
const onShow = () => {
69+
startHandlePortalPosition();
70+
setShow(true);
71+
};
72+
73+
const showExternalResourceMode = (mode) => {
74+
if (mode === 'always') {
75+
Storage.set(EXTERNAL_CONTENT_DISPLAY_MODE, 'always');
76+
} else {
77+
Storage.remove(EXTERNAL_CONTENT_DISPLAY_MODE);
78+
}
79+
const img = document.querySelectorAll('img');
80+
img.forEach((i) => {
81+
if (!i.src && i.dataset.src) {
82+
i.src = i.dataset.src;
83+
i.removeAttribute('data-src');
84+
i.classList.remove('broken');
85+
}
86+
});
87+
onClose();
88+
};
89+
90+
useLayoutEffect(() => {
91+
const parent = document.querySelector('.page-wrap');
92+
parent?.appendChild(toastPortal);
93+
94+
root.render(
95+
<div className="d-flex justify-content-center">
96+
<Toast
97+
className="align-items-center border-0"
98+
bg="warning"
99+
show={show}
100+
onClose={onClose}>
101+
<div className="d-flex">
102+
<Toast.Body>
103+
{t('external_content_warning')}
104+
<div className="d-flex align-items-center">
105+
<Button
106+
variant="link"
107+
onClick={() => showExternalResourceMode('below')}
108+
className="btn-no-border small link-dark p-0 fw-bold">
109+
{t('display_below', { keyPrefix: 'btns' })}
110+
</Button>
111+
<span className="mx-1">{t('or', { keyPrefix: 'btns' })}</span>
112+
<Button
113+
variant="link"
114+
onClick={() => showExternalResourceMode('always')}
115+
className="btn-no-border small link-dark p-0 fw-bold">
116+
{t('always_display', { keyPrefix: 'btns' })}
117+
</Button>
118+
</div>
119+
</Toast.Body>
120+
<button
121+
className="btn-close me-2 m-auto"
122+
onClick={onClose}
123+
data-bs-dismiss="toast"
124+
aria-label="Close"
125+
/>
126+
</div>
127+
</Toast>
128+
</div>,
129+
);
130+
}, [show]);
131+
132+
return {
133+
onShow,
134+
};
135+
};
136+
137+
export default useExternalToast;

ui/src/pages/Admin/Legal/index.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,37 @@ import { SchemaForm, JSONSchema, initFormData, UISchema } from '@/components';
2727
import { useToast } from '@/hooks';
2828
import { getLegalSetting, putLegalSetting } from '@/services';
2929
import { handleFormError, scrollToElementTop } from '@/utils';
30+
import { siteLealStore } from '@/stores';
3031

3132
const Legal: FC = () => {
3233
const { t } = useTranslation('translation', {
3334
keyPrefix: 'admin.legal',
3435
});
3536
const Toast = useToast();
3637

38+
const externalContent = [
39+
{
40+
value: 'always_display',
41+
label: t('external_content_display.always_display'),
42+
},
43+
{
44+
value: 'ask_before_display',
45+
label: t('external_content_display.ask_before_display'),
46+
},
47+
];
48+
3749
const schema: JSONSchema = {
3850
title: t('page_title'),
3951
required: ['terms_of_service', 'privacy_policy'],
4052
properties: {
53+
external_content_display: {
54+
type: 'string',
55+
title: t('external_content_display.label'),
56+
description: t('external_content_display.text'),
57+
enum: externalContent?.map((lang) => lang.value),
58+
enumNames: externalContent?.map((lang) => lang.label),
59+
default: 0,
60+
},
4161
terms_of_service: {
4262
type: 'string',
4363
title: t('terms_of_service.label'),
@@ -51,6 +71,9 @@ const Legal: FC = () => {
5171
},
5272
};
5373
const uiSchema: UISchema = {
74+
external_content_display: {
75+
'ui:widget': 'select',
76+
},
5477
terms_of_service: {
5578
'ui:widget': 'textarea',
5679
'ui:options': {
@@ -71,6 +94,7 @@ const Legal: FC = () => {
7194
evt.stopPropagation();
7295

7396
const reqParams: Type.AdminSettingsLegal = {
97+
external_content_display: formData.external_content_display.value,
7498
terms_of_service_original_text: formData.terms_of_service.value,
7599
terms_of_service_parsed_text: marked.parse(
76100
formData.terms_of_service.value,
@@ -85,6 +109,9 @@ const Legal: FC = () => {
85109
msg: t('update', { keyPrefix: 'toast' }),
86110
variant: 'success',
87111
});
112+
siteLealStore.getState().update({
113+
external_content_display: reqParams.external_content_display,
114+
});
88115
})
89116
.catch((err) => {
90117
if (err.isError) {
@@ -100,6 +127,8 @@ const Legal: FC = () => {
100127
getLegalSetting().then((setting) => {
101128
if (setting) {
102129
const formMeta = { ...formData };
130+
formMeta.external_content_display.value =
131+
setting.external_content_display;
103132
formMeta.terms_of_service.value =
104133
setting.terms_of_service_original_text;
105134
formMeta.privacy_policy.value = setting.privacy_policy_original_text;

ui/src/pages/Install/components/FourthStep/index.tsx

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ const Index: FC<Props> = ({ visible, data, changeCallback, nextCallback }) => {
267267
{data.contact_email.errorMsg}
268268
</Form.Control.Feedback>
269269
</Form.Group>
270-
270+
<h5>{t('login_required.label')}</h5>
271271
<Form.Group controlId="login_required" className="mb-3">
272272
<Form.Label>{t('login_required.label')}</Form.Label>
273273
<Form.Check
@@ -287,6 +287,36 @@ const Index: FC<Props> = ({ visible, data, changeCallback, nextCallback }) => {
287287
/>
288288
<Form.Text>{t('login_required.text')}</Form.Text>
289289
</Form.Group>
290+
<Form.Group controlId="external_content_display" className="mb-3">
291+
<Form.Label>
292+
{t('external_content_display.label', { keyPrefix: 'admin.legal' })}
293+
</Form.Label>
294+
<Form.Select
295+
value={data.external_content_display.value}
296+
onChange={(e) => {
297+
changeCallback({
298+
external_content_display: {
299+
value: e.target.value,
300+
isInvalid: false,
301+
errorMsg: '',
302+
},
303+
});
304+
}}>
305+
<option value="always_display">
306+
{t('external_content_display.always_display', {
307+
keyPrefix: 'admin.legal',
308+
})}
309+
</option>
310+
<option value="ask_before_display">
311+
{t('external_content_display.ask_before_display', {
312+
keyPrefix: 'admin.legal',
313+
})}
314+
</option>
315+
</Form.Select>
316+
<Form.Text>
317+
{t('external_content_display.text', { keyPrefix: 'admin.legal' })}
318+
</Form.Text>
319+
</Form.Group>
290320

291321
<h5>{t('admin_account')}</h5>
292322
<Form.Group controlId="name" className="mb-3">

ui/src/pages/Install/index.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,11 @@ const Index: FC = () => {
115115
isInvalid: false,
116116
errorMsg: '',
117117
},
118+
external_content_display: {
119+
value: 'always_display',
120+
isInvalid: false,
121+
errorMsg: '',
122+
},
118123
name: {
119124
value: '',
120125
isInvalid: false,
@@ -225,10 +230,12 @@ const Index: FC = () => {
225230
site_url: formData.site_url.value,
226231
contact_email: formData.contact_email.value,
227232
login_required: formData.login_required.value,
233+
external_content_display: formData.external_content_display.value,
228234
name: formData.name.value,
229235
password: formData.password.value,
230236
email: formData.email.value,
231237
};
238+
232239
installBaseInfo(params)
233240
.then(() => {
234241
handleNext();

0 commit comments

Comments
 (0)