Skip to content

Commit edbcac5

Browse files
authored
feat: Add additional translations and improve favicon update logic (#308)
1 parent 5c2d000 commit edbcac5

File tree

10 files changed

+358
-58
lines changed

10 files changed

+358
-58
lines changed

client/public/locales/en/translation.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,11 @@
157157
},
158158
"favicon": {
159159
"desc": "Set the icon (Favicon) displayed in the browser's address bar",
160-
"title": "Favicon"
160+
"title": "Favicon",
161+
"update": {
162+
"success": "Favicon update was successful",
163+
"failed$message": "Favicon update failed, {{message}}"
164+
}
161165
},
162166
"footer": {
163167
"desc": "Set the footer content of the site (HTML)",

client/public/locales/ja/translation.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,11 @@
157157
},
158158
"favicon": {
159159
"desc": "ブラウザのアドレスバーに表示されるアイコン(ファビコン)を設定します。",
160-
"title": "ファビコン"
160+
"title": "ファビコン",
161+
"update": {
162+
"success": "ファビコンの更新が成功しました",
163+
"failed$message": "ファビコンの更新に失敗しました、{{message}}"
164+
}
161165
},
162166
"footer": {
163167
"desc": "サイトのフッターコンテンツを設定する(HTML)",

client/public/locales/zh-CN/translation.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,11 @@
157157
},
158158
"favicon": {
159159
"desc": "设置显示于浏览器的地址栏的图标(Favicon)",
160-
"title": "网站图标"
160+
"title": "网站图标",
161+
"update": {
162+
"success": "网站图标更新成功",
163+
"failed$message": "网站图标更新失败,{{message}}"
164+
}
161165
},
162166
"footer": {
163167
"desc": "设置显示于站点底部的内容(HTML)",

client/public/locales/zh-TW/translation.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,11 @@
157157
},
158158
"favicon": {
159159
"desc": "設定顯示於瀏覽器的地址欄的圖標(Favicon)",
160-
"title": "網站圖標"
160+
"title": "網站圖標",
161+
"update": {
162+
"success": "網站圖標更新成功",
163+
"failed$message": "網站圖標更新失敗,{{message}}"
164+
}
161165
},
162166
"footer": {
163167
"desc": "設定顯示於網站底部的内容(HTML)",

client/src/App.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useMemo, useRef, useState } from 'react'
1+
import { useEffect, useRef, useState } from 'react'
22
import { Helmet } from 'react-helmet'
33
import { getCookie } from 'typescript-cookie'
44
import { DefaultParams, PathPattern, Route, Switch } from 'wouter'
@@ -61,7 +61,7 @@ function App() {
6161
}
6262
ref.current = true
6363
}, [])
64-
const favicon = useMemo(() => config.get<string>("favicon"), [config])
64+
const favicon = `${process.env.API_URL}/favicon`;
6565
return (
6666
<>
6767
<ClientConfigContext.Provider value={config}>

client/src/page/settings.tsx

+93-15
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,46 @@ export function Settings() {
6666
ref.current = true;
6767
}, []);
6868

69-
function onFileChange(e: ChangeEvent<HTMLInputElement>) {
69+
async function handleFaviconChange(e: ChangeEvent<HTMLInputElement>) {
7070
const file = e.target.files?.[0];
7171
if (file) {
72-
client.wp.post({
72+
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
73+
if (file.size > MAX_FILE_SIZE) {
74+
showAlert(
75+
t("upload.failed$size", {
76+
size: MAX_FILE_SIZE / 1024 / 1024,
77+
}),
78+
);
79+
return;
80+
}
81+
await client.favicon
82+
.post(
83+
{
84+
file: file,
85+
},
86+
{
87+
headers: headersWithAuth(),
88+
},
89+
)
90+
.then(({ data }) => {
91+
if (data && typeof data !== "string") {
92+
showAlert(t("settings.favicon.update.success"));
93+
}
94+
})
95+
.catch((err) => {
96+
showAlert(
97+
t("settings.favicon.update.failed$message", {
98+
message: err.message,
99+
}),
100+
);
101+
});
102+
}
103+
}
104+
105+
async function onFileChange(e: ChangeEvent<HTMLInputElement>) {
106+
const file = e.target.files?.[0];
107+
if (file) {
108+
await client.wp.post({
73109
data: file,
74110
}, {
75111
headers: headersWithAuth()
@@ -106,7 +142,13 @@ export function Settings() {
106142
<ItemSwitch title={t('settings.comment.enable.title')} description={t('settings.comment.enable.desc')} type="client" configKey="comment.enabled" />
107143
<ItemSwitch title={t('settings.counter.enable.title')} description={t('settings.counter.enable.desc')} type="client" configKey="counter.enabled" />
108144
<ItemSwitch title={t('settings.rss.title')} description={t('settings.rss.desc')} type="client" configKey="rss" />
109-
<ItemInput title={t('settings.favicon.title')} description={t('settings.favicon.desc')} type="client" configKey="favicon" configKeyTitle="Favicon" />
145+
<ItemWithUpload
146+
title={t("settings.favicon.title")}
147+
description={t("settings.favicon.desc")}
148+
// @see https://developers.cloudflare.com/images/transform-images/#supported-input-formats
149+
accept="image/jpeg,image/png,image/gif,image/webp,image/svg+xml"
150+
onFileChange={handleFaviconChange}
151+
/>
110152
<ItemInput title={t('settings.footer.title')} description={t('settings.footer.desc')} type="client" configKey="footer" configKeyTitle="Footer HTML" />
111153
<ItemButton title={t('settings.cache.clear.title')} description={t('settings.cache.clear.desc')} buttonTitle={t('clear')} onConfirm={async () => {
112154
await client.config.cache.delete(undefined, {
@@ -119,6 +161,7 @@ export function Settings() {
119161
})
120162
}} alertTitle={t('settings.cache.clear.confirm.title')} alertDescription={t('settings.cache.clear.confirm.desc')} />
121163
<ItemWithUpload title={t('settings.wordpress.title')} description={t('settings.wordpress.desc')}
164+
accept="application/xml"
122165
onFileChange={onFileChange} />
123166
</div>
124167
</main>
@@ -406,25 +449,60 @@ function ItemButton({
406449
);
407450
}
408451

409-
function ItemWithUpload({ title, description, onFileChange }: { title: string, description: string, onFileChange: (e: ChangeEvent<HTMLInputElement>) => void }) {
452+
function ItemWithUpload({
453+
title,
454+
description,
455+
accept,
456+
onFileChange,
457+
}: {
458+
title: string;
459+
description: string;
460+
onFileChange: (e: ChangeEvent<HTMLInputElement>) => Promise<void>;
461+
accept: string;
462+
}) {
410463
const inputRef = useRef<HTMLInputElement>(null);
464+
const [loading, setLoading] = useState(false);
411465
const { t } = useTranslation();
466+
467+
const handleFileChange = async (e: ChangeEvent<HTMLInputElement>) => {
468+
setLoading(true);
469+
try {
470+
await onFileChange(e);
471+
} finally {
472+
setLoading(false);
473+
}
474+
};
475+
412476
return (
413477
<div className="flex flex-col w-full items-start">
414478
<div className="flex flex-row justify-between w-full items-center">
415479
<div className="flex flex-col">
416-
<p className="text-lg font-bold dark:text-white">
417-
{title}
418-
</p>
419-
<p className="text-xs text-neutral-500">
420-
{description}
421-
</p>
480+
<p className="text-lg font-bold dark:text-white">{title}</p>
481+
<p className="text-xs text-neutral-500">{description}</p>
482+
</div>
483+
<div className="flex flex-row items-center justify-center space-x-4">
484+
{loading && (
485+
<ReactLoading
486+
width="1em"
487+
height="1em"
488+
type="spin"
489+
color="#FC466B"
490+
/>
491+
)}
492+
<input
493+
ref={inputRef}
494+
type="file"
495+
className="hidden"
496+
accept={accept}
497+
onChange={handleFileChange}
498+
/>
499+
<Button
500+
onClick={() => {
501+
inputRef.current?.click();
502+
}}
503+
title={t("upload.title")}
504+
/>
422505
</div>
423-
<input ref={inputRef} type="file" className="hidden" accept="application/xml"
424-
onChange={onFileChange} />
425-
<Button onClick={() => {
426-
inputRef.current?.click();
427-
}} title={t('upload.title')} />
428506
</div>
429507
</div>
430508
);

client/src/state/config.tsx

-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import { createContext } from "react";
22

3-
const defaultFavicon = process.env.AVATAR ? `//wsrv.nl/?url=${encodeURIComponent(process.env.AVATAR)}&w=144&h=144&mask=circle` : '/favicon.ico';
43
export const defaultClientConfig = new Map(Object.entries({
5-
"favicon": defaultFavicon,
64
"counter.enabled": true,
75
"friend_apply_enable": true,
86
"comment.enabled": true,

server/src/server.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import cors from '@elysiajs/cors';
22
import { serverTiming } from '@elysiajs/server-timing';
33
import { Elysia } from 'elysia';
44
import { CommentService } from './services/comments';
5+
import { FaviconService } from "./services/favicon";
56
import { FeedService } from './services/feed';
67
import { FriendService } from './services/friends';
78
import { RSSService } from './services/rss';
@@ -28,6 +29,7 @@ export const app = () => new Elysia({ aot: false })
2829
enabled: true,
2930
}))
3031
.use(UserService())
32+
.use(FaviconService())
3133
.use(FeedService())
3234
.use(CommentService())
3335
.use(TagService())
@@ -42,4 +44,4 @@ export const app = () => new Elysia({ aot: false })
4244
return `${path} ${JSON.stringify(params)} not found`
4345
})
4446

45-
export type App = ReturnType<typeof app>;
47+
export type App = ReturnType<typeof app>;

0 commit comments

Comments
 (0)