feat: configurable favicon service URL (#659)
This commit is contained in:
15
package-lock.json
generated
15
package-lock.json
generated
@@ -75,6 +75,7 @@
|
|||||||
"tailwind-merge": "^2.5.5",
|
"tailwind-merge": "^2.5.5",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"tippy.js": "^6.3.7",
|
"tippy.js": "^6.3.7",
|
||||||
|
"uri-templates": "^0.2.0",
|
||||||
"vaul": "^1.1.2",
|
"vaul": "^1.1.2",
|
||||||
"yet-another-react-lightbox": "^3.21.7",
|
"yet-another-react-lightbox": "^3.21.7",
|
||||||
"zod": "^3.24.1"
|
"zod": "^3.24.1"
|
||||||
@@ -84,6 +85,7 @@
|
|||||||
"@types/node": "^22.10.2",
|
"@types/node": "^22.10.2",
|
||||||
"@types/react": "^18.3.17",
|
"@types/react": "^18.3.17",
|
||||||
"@types/react-dom": "^18.3.5",
|
"@types/react-dom": "^18.3.5",
|
||||||
|
"@types/uri-templates": "^0.1.34",
|
||||||
"@vitejs/plugin-react": "^4.3.4",
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"eslint": "^9.17.0",
|
"eslint": "^9.17.0",
|
||||||
@@ -5422,6 +5424,13 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
|
||||||
"integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="
|
"integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/uri-templates": {
|
||||||
|
"version": "0.1.34",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/uri-templates/-/uri-templates-0.1.34.tgz",
|
||||||
|
"integrity": "sha512-13v4r/Op3iEO1y6FvEHQjrUNnrNyO67SigdFC9n80sVfsrM2AWJRNYbE1pBs4/p87I7z1J979JGeLAo3rM1L/Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/use-sync-external-store": {
|
"node_modules/@types/use-sync-external-store": {
|
||||||
"version": "0.0.6",
|
"version": "0.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
|
||||||
@@ -12278,6 +12287,12 @@
|
|||||||
"punycode": "^2.1.0"
|
"punycode": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/uri-templates": {
|
||||||
|
"version": "0.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/uri-templates/-/uri-templates-0.2.0.tgz",
|
||||||
|
"integrity": "sha512-EWkjYEN0L6KOfEoOH6Wj4ghQqU7eBZMJqRHQnxQAq+dSEzRPClkWjf8557HkWQXF6BrAUoLSAyy9i3RVTliaNg==",
|
||||||
|
"license": "http://geraintluff.github.io/tv4/LICENSE.txt"
|
||||||
|
},
|
||||||
"node_modules/use-callback-ref": {
|
"node_modules/use-callback-ref": {
|
||||||
"version": "1.3.3",
|
"version": "1.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
|
||||||
|
|||||||
@@ -85,6 +85,7 @@
|
|||||||
"tailwind-merge": "^2.5.5",
|
"tailwind-merge": "^2.5.5",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"tippy.js": "^6.3.7",
|
"tippy.js": "^6.3.7",
|
||||||
|
"uri-templates": "^0.2.0",
|
||||||
"vaul": "^1.1.2",
|
"vaul": "^1.1.2",
|
||||||
"yet-another-react-lightbox": "^3.21.7",
|
"yet-another-react-lightbox": "^3.21.7",
|
||||||
"zod": "^3.24.1"
|
"zod": "^3.24.1"
|
||||||
@@ -94,6 +95,7 @@
|
|||||||
"@types/node": "^22.10.2",
|
"@types/node": "^22.10.2",
|
||||||
"@types/react": "^18.3.17",
|
"@types/react": "^18.3.17",
|
||||||
"@types/react-dom": "^18.3.5",
|
"@types/react-dom": "^18.3.5",
|
||||||
|
"@types/uri-templates": "^0.1.34",
|
||||||
"@vitejs/plugin-react": "^4.3.4",
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"eslint": "^9.17.0",
|
"eslint": "^9.17.0",
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
import { faviconUrl } from '@/lib/faviconUrl'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
import { useContentPolicy } from '@/providers/ContentPolicyProvider'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
|
||||||
export function Favicon({
|
export function Favicon({
|
||||||
@@ -10,15 +12,18 @@ export function Favicon({
|
|||||||
className?: string
|
className?: string
|
||||||
fallback?: React.ReactNode
|
fallback?: React.ReactNode
|
||||||
}) {
|
}) {
|
||||||
|
const { faviconUrlTemplate } = useContentPolicy()
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [error, setError] = useState(false)
|
const [error, setError] = useState(false)
|
||||||
if (error) return fallback
|
if (error) return fallback
|
||||||
|
|
||||||
|
const url = faviconUrl(faviconUrlTemplate, `https://${domain}`)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('relative', className)}>
|
<div className={cn('relative', className)}>
|
||||||
{loading && <div className={cn('absolute inset-0', className)}>{fallback}</div>}
|
{loading && <div className={cn('absolute inset-0', className)}>{fallback}</div>}
|
||||||
<img
|
<img
|
||||||
src={`https://${domain}/favicon.ico`}
|
src={url}
|
||||||
alt={domain}
|
alt={domain}
|
||||||
className={cn('absolute inset-0', loading && 'opacity-0', className)}
|
className={cn('absolute inset-0', loading && 'opacity-0', className)}
|
||||||
onError={() => setError(true)}
|
onError={() => setError(true)}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
toGeneralSettings,
|
toGeneralSettings,
|
||||||
toPostSettings,
|
toPostSettings,
|
||||||
toRelaySettings,
|
toRelaySettings,
|
||||||
|
toSystemSettings,
|
||||||
toTranslation,
|
toTranslation,
|
||||||
toWallet
|
toWallet
|
||||||
} from '@/lib/link'
|
} from '@/lib/link'
|
||||||
@@ -15,6 +16,7 @@ import { useNostr } from '@/providers/NostrProvider'
|
|||||||
import {
|
import {
|
||||||
Check,
|
Check,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
|
Cog,
|
||||||
Copy,
|
Copy,
|
||||||
Info,
|
Info,
|
||||||
KeyRound,
|
KeyRound,
|
||||||
@@ -141,6 +143,13 @@ export default function Settings() {
|
|||||||
</div>
|
</div>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
</AboutInfoDialog>
|
</AboutInfoDialog>
|
||||||
|
<SettingItem className="clickable" onClick={() => push(toSystemSettings())}>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<Cog />
|
||||||
|
<div>{t('System')}</div>
|
||||||
|
</div>
|
||||||
|
<ChevronRight />
|
||||||
|
</SettingItem>
|
||||||
<div className="px-4 mt-4">
|
<div className="px-4 mt-4">
|
||||||
<Donation />
|
<Donation />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ export const StorageKey = {
|
|||||||
SIDEBAR_COLLAPSE: 'sidebarCollapse',
|
SIDEBAR_COLLAPSE: 'sidebarCollapse',
|
||||||
PRIMARY_COLOR: 'primaryColor',
|
PRIMARY_COLOR: 'primaryColor',
|
||||||
ENABLE_SINGLE_COLUMN_LAYOUT: 'enableSingleColumnLayout',
|
ENABLE_SINGLE_COLUMN_LAYOUT: 'enableSingleColumnLayout',
|
||||||
|
FAVICON_URL_TEMPLATE: 'faviconUrlTemplate',
|
||||||
MEDIA_UPLOAD_SERVICE: 'mediaUploadService', // deprecated
|
MEDIA_UPLOAD_SERVICE: 'mediaUploadService', // deprecated
|
||||||
HIDE_UNTRUSTED_EVENTS: 'hideUntrustedEvents', // deprecated
|
HIDE_UNTRUSTED_EVENTS: 'hideUntrustedEvents', // deprecated
|
||||||
ACCOUNT_RELAY_LIST_EVENT_MAP: 'accountRelayListEventMap', // deprecated
|
ACCOUNT_RELAY_LIST_EVENT_MAP: 'accountRelayListEventMap', // deprecated
|
||||||
@@ -130,6 +131,8 @@ export const DEFAULT_NOSTRCONNECT_RELAY = [
|
|||||||
'wss://relay.primal.net/'
|
'wss://relay.primal.net/'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export const DEFAULT_FAVICON_URL_TEMPLATE = 'https://{hostname}/favicon.ico'
|
||||||
|
|
||||||
export const POLL_TYPE = {
|
export const POLL_TYPE = {
|
||||||
MULTIPLE_CHOICE: 'multiplechoice',
|
MULTIPLE_CHOICE: 'multiplechoice',
|
||||||
SINGLE_CHOICE: 'singlechoice'
|
SINGLE_CHOICE: 'singlechoice'
|
||||||
|
|||||||
@@ -531,6 +531,7 @@ export default {
|
|||||||
Close: 'إغلاق',
|
Close: 'إغلاق',
|
||||||
'Failed to get invite code from relay': 'فشل الحصول على رمز الدعوة من المرحل',
|
'Failed to get invite code from relay': 'فشل الحصول على رمز الدعوة من المرحل',
|
||||||
'Failed to get invite code': 'فشل الحصول على رمز الدعوة',
|
'Failed to get invite code': 'فشل الحصول على رمز الدعوة',
|
||||||
'Invite code copied to clipboard': 'تم نسخ رمز الدعوة إلى الحافظة'
|
'Invite code copied to clipboard': 'تم نسخ رمز الدعوة إلى الحافظة',
|
||||||
|
'Favicon URL': 'رابط الأيقونة المفضلة'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -547,6 +547,7 @@ export default {
|
|||||||
Close: 'Schließen',
|
Close: 'Schließen',
|
||||||
'Failed to get invite code from relay': 'Fehler beim Abrufen des Einladungscodes vom Relay',
|
'Failed to get invite code from relay': 'Fehler beim Abrufen des Einladungscodes vom Relay',
|
||||||
'Failed to get invite code': 'Fehler beim Abrufen des Einladungscodes',
|
'Failed to get invite code': 'Fehler beim Abrufen des Einladungscodes',
|
||||||
'Invite code copied to clipboard': 'Einladungscode in die Zwischenablage kopiert'
|
'Invite code copied to clipboard': 'Einladungscode in die Zwischenablage kopiert',
|
||||||
|
'Favicon URL': 'Favicon-URL'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -532,6 +532,7 @@ export default {
|
|||||||
Close: 'Close',
|
Close: 'Close',
|
||||||
'Failed to get invite code from relay': 'Failed to get invite code from relay',
|
'Failed to get invite code from relay': 'Failed to get invite code from relay',
|
||||||
'Failed to get invite code': 'Failed to get invite code',
|
'Failed to get invite code': 'Failed to get invite code',
|
||||||
'Invite code copied to clipboard': 'Invite code copied to clipboard'
|
'Invite code copied to clipboard': 'Invite code copied to clipboard',
|
||||||
|
'Favicon URL': 'Favicon URL'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -541,6 +541,7 @@ export default {
|
|||||||
Close: 'Cerrar',
|
Close: 'Cerrar',
|
||||||
'Failed to get invite code from relay': 'Error al obtener código de invitación del relay',
|
'Failed to get invite code from relay': 'Error al obtener código de invitación del relay',
|
||||||
'Failed to get invite code': 'Error al obtener código de invitación',
|
'Failed to get invite code': 'Error al obtener código de invitación',
|
||||||
'Invite code copied to clipboard': 'Código de invitación copiado al portapapeles'
|
'Invite code copied to clipboard': 'Código de invitación copiado al portapapeles',
|
||||||
|
'Favicon URL': 'URL del Favicon'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -536,6 +536,7 @@ export default {
|
|||||||
Close: 'بستن',
|
Close: 'بستن',
|
||||||
'Failed to get invite code from relay': 'دریافت کد دعوت از رله ناموفق بود',
|
'Failed to get invite code from relay': 'دریافت کد دعوت از رله ناموفق بود',
|
||||||
'Failed to get invite code': 'دریافت کد دعوت ناموفق بود',
|
'Failed to get invite code': 'دریافت کد دعوت ناموفق بود',
|
||||||
'Invite code copied to clipboard': 'کد دعوت در کلیپبورد کپی شد'
|
'Invite code copied to clipboard': 'کد دعوت در کلیپبورد کپی شد',
|
||||||
|
'Favicon URL': 'آدرس نماد سایت'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -546,6 +546,7 @@ export default {
|
|||||||
Close: 'Fermer',
|
Close: 'Fermer',
|
||||||
'Failed to get invite code from relay': "Échec de l'obtention du code d'invitation du relay",
|
'Failed to get invite code from relay': "Échec de l'obtention du code d'invitation du relay",
|
||||||
'Failed to get invite code': "Échec de l'obtention du code d'invitation",
|
'Failed to get invite code': "Échec de l'obtention du code d'invitation",
|
||||||
'Invite code copied to clipboard': "Code d'invitation copié dans le presse-papiers"
|
'Invite code copied to clipboard': "Code d'invitation copié dans le presse-papiers",
|
||||||
|
'Favicon URL': 'URL du Favicon'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -538,6 +538,7 @@ export default {
|
|||||||
Close: 'बंद करें',
|
Close: 'बंद करें',
|
||||||
'Failed to get invite code from relay': 'रिले से निमंत्रण कोड प्राप्त करने में विफल',
|
'Failed to get invite code from relay': 'रिले से निमंत्रण कोड प्राप्त करने में विफल',
|
||||||
'Failed to get invite code': 'निमंत्रण कोड प्राप्त करने में विफल',
|
'Failed to get invite code': 'निमंत्रण कोड प्राप्त करने में विफल',
|
||||||
'Invite code copied to clipboard': 'निमंत्रण कोड क्लिपबोर्ड पर कॉपी किया गया'
|
'Invite code copied to clipboard': 'निमंत्रण कोड क्लिपबोर्ड पर कॉपी किया गया',
|
||||||
|
'Favicon URL': 'फ़ेविकॉन URL'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -533,6 +533,7 @@ export default {
|
|||||||
Close: 'Bezárás',
|
Close: 'Bezárás',
|
||||||
'Failed to get invite code from relay': 'Nem sikerült lekérni a meghívókódot a relay-től',
|
'Failed to get invite code from relay': 'Nem sikerült lekérni a meghívókódot a relay-től',
|
||||||
'Failed to get invite code': 'Nem sikerült lekérni a meghívókódot',
|
'Failed to get invite code': 'Nem sikerült lekérni a meghívókódot',
|
||||||
'Invite code copied to clipboard': 'Meghívókód vágólapra másolva'
|
'Invite code copied to clipboard': 'Meghívókód vágólapra másolva',
|
||||||
|
'Favicon URL': 'Favicon URL'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -541,6 +541,7 @@ export default {
|
|||||||
Close: 'Chiudi',
|
Close: 'Chiudi',
|
||||||
'Failed to get invite code from relay': 'Impossibile ottenere il codice di invito dal relay',
|
'Failed to get invite code from relay': 'Impossibile ottenere il codice di invito dal relay',
|
||||||
'Failed to get invite code': 'Impossibile ottenere il codice di invito',
|
'Failed to get invite code': 'Impossibile ottenere il codice di invito',
|
||||||
'Invite code copied to clipboard': 'Codice di invito copiato negli appunti'
|
'Invite code copied to clipboard': 'Codice di invito copiato negli appunti',
|
||||||
|
'Favicon URL': 'URL Favicon'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -535,6 +535,7 @@ export default {
|
|||||||
Close: '閉じる',
|
Close: '閉じる',
|
||||||
'Failed to get invite code from relay': 'リレーから招待コードの取得に失敗しました',
|
'Failed to get invite code from relay': 'リレーから招待コードの取得に失敗しました',
|
||||||
'Failed to get invite code': '招待コードの取得に失敗しました',
|
'Failed to get invite code': '招待コードの取得に失敗しました',
|
||||||
'Invite code copied to clipboard': '招待コードをクリップボードにコピーしました'
|
'Invite code copied to clipboard': '招待コードをクリップボードにコピーしました',
|
||||||
|
'Favicon URL': 'ファビコンURL'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -535,6 +535,7 @@ export default {
|
|||||||
Close: '닫기',
|
Close: '닫기',
|
||||||
'Failed to get invite code from relay': '릴레이에서 초대 코드 가져오기 실패',
|
'Failed to get invite code from relay': '릴레이에서 초대 코드 가져오기 실패',
|
||||||
'Failed to get invite code': '초대 코드 가져오기 실패',
|
'Failed to get invite code': '초대 코드 가져오기 실패',
|
||||||
'Invite code copied to clipboard': '초대 코드가 클립보드에 복사되었습니다'
|
'Invite code copied to clipboard': '초대 코드가 클립보드에 복사되었습니다',
|
||||||
|
'Favicon URL': '파비콘 URL'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -541,6 +541,7 @@ export default {
|
|||||||
Close: 'Zamknij',
|
Close: 'Zamknij',
|
||||||
'Failed to get invite code from relay': 'Nie udało się uzyskać kodu zaproszenia z przekaźnika',
|
'Failed to get invite code from relay': 'Nie udało się uzyskać kodu zaproszenia z przekaźnika',
|
||||||
'Failed to get invite code': 'Nie udało się uzyskać kodu zaproszenia',
|
'Failed to get invite code': 'Nie udało się uzyskać kodu zaproszenia',
|
||||||
'Invite code copied to clipboard': 'Kod zaproszenia skopiowany do schowka'
|
'Invite code copied to clipboard': 'Kod zaproszenia skopiowany do schowka',
|
||||||
|
'Favicon URL': 'URL Favicon'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -538,6 +538,7 @@ export default {
|
|||||||
Close: 'Fechar',
|
Close: 'Fechar',
|
||||||
'Failed to get invite code from relay': 'Falha ao obter código de convite do relay',
|
'Failed to get invite code from relay': 'Falha ao obter código de convite do relay',
|
||||||
'Failed to get invite code': 'Falha ao obter código de convite',
|
'Failed to get invite code': 'Falha ao obter código de convite',
|
||||||
'Invite code copied to clipboard': 'Código de convite copiado para a área de transferência'
|
'Invite code copied to clipboard': 'Código de convite copiado para a área de transferência',
|
||||||
|
'Favicon URL': 'URL do Favicon'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -541,6 +541,7 @@ export default {
|
|||||||
Close: 'Fechar',
|
Close: 'Fechar',
|
||||||
'Failed to get invite code from relay': 'Falha ao obter código de convite do relay',
|
'Failed to get invite code from relay': 'Falha ao obter código de convite do relay',
|
||||||
'Failed to get invite code': 'Falha ao obter código de convite',
|
'Failed to get invite code': 'Falha ao obter código de convite',
|
||||||
'Invite code copied to clipboard': 'Código de convite copiado para a área de transferência'
|
'Invite code copied to clipboard': 'Código de convite copiado para a área de transferência',
|
||||||
|
'Favicon URL': 'URL do Favicon'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -543,6 +543,7 @@ export default {
|
|||||||
Close: 'Закрыть',
|
Close: 'Закрыть',
|
||||||
'Failed to get invite code from relay': 'Не удалось получить код приглашения от релея',
|
'Failed to get invite code from relay': 'Не удалось получить код приглашения от релея',
|
||||||
'Failed to get invite code': 'Не удалось получить код приглашения',
|
'Failed to get invite code': 'Не удалось получить код приглашения',
|
||||||
'Invite code copied to clipboard': 'Код приглашения скопирован в буфер обмена'
|
'Invite code copied to clipboard': 'Код приглашения скопирован в буфер обмена',
|
||||||
|
'Favicon URL': 'URL фавикона'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -529,6 +529,7 @@ export default {
|
|||||||
Close: 'ปิด',
|
Close: 'ปิด',
|
||||||
'Failed to get invite code from relay': 'ไม่สามารถรับรหัสเชิญจากรีเลย์',
|
'Failed to get invite code from relay': 'ไม่สามารถรับรหัสเชิญจากรีเลย์',
|
||||||
'Failed to get invite code': 'ไม่สามารถรับรหัสเชิญ',
|
'Failed to get invite code': 'ไม่สามารถรับรหัสเชิญ',
|
||||||
'Invite code copied to clipboard': 'คัดลอกรหัสเชิญไปยังคลิปบอร์ดแล้ว'
|
'Invite code copied to clipboard': 'คัดลอกรหัสเชิญไปยังคลิปบอร์ดแล้ว',
|
||||||
|
'Favicon URL': 'URL ไอคอน'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -526,6 +526,7 @@ export default {
|
|||||||
Close: '关闭',
|
Close: '关闭',
|
||||||
'Failed to get invite code from relay': '从中继器获取邀请码失败',
|
'Failed to get invite code from relay': '从中继器获取邀请码失败',
|
||||||
'Failed to get invite code': '获取邀请码失败',
|
'Failed to get invite code': '获取邀请码失败',
|
||||||
'Invite code copied to clipboard': '邀请码已复制到剪贴板'
|
'Invite code copied to clipboard': '邀请码已复制到剪贴板',
|
||||||
|
'Favicon URL': '网站图标 URL'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
19
src/lib/faviconUrl.ts
Normal file
19
src/lib/faviconUrl.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import UriTemplate from 'uri-templates'
|
||||||
|
|
||||||
|
export function faviconUrl(template: string, url: string | URL): string {
|
||||||
|
const u = new URL(url)
|
||||||
|
|
||||||
|
return UriTemplate(template).fill({
|
||||||
|
href: u.href,
|
||||||
|
origin: u.origin,
|
||||||
|
protocol: u.protocol,
|
||||||
|
username: u.username,
|
||||||
|
password: u.password,
|
||||||
|
host: u.host,
|
||||||
|
hostname: u.hostname,
|
||||||
|
port: u.port,
|
||||||
|
pathname: u.pathname,
|
||||||
|
hash: u.hash,
|
||||||
|
search: u.search
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -72,6 +72,7 @@ export const toGeneralSettings = () => '/settings/general'
|
|||||||
export const toAppearanceSettings = () => '/settings/appearance'
|
export const toAppearanceSettings = () => '/settings/appearance'
|
||||||
export const toTranslation = () => '/settings/translation'
|
export const toTranslation = () => '/settings/translation'
|
||||||
export const toEmojiPackSettings = () => '/settings/emoji-packs'
|
export const toEmojiPackSettings = () => '/settings/emoji-packs'
|
||||||
|
export const toSystemSettings = () => '/settings/system'
|
||||||
export const toProfileEditor = () => '/profile-editor'
|
export const toProfileEditor = () => '/profile-editor'
|
||||||
export const toRelay = (url: string) => `/relays/${encodeURIComponent(url)}`
|
export const toRelay = (url: string) => `/relays/${encodeURIComponent(url)}`
|
||||||
export const toRelayReviews = (url: string) => `/relays/${encodeURIComponent(url)}/reviews`
|
export const toRelayReviews = (url: string) => `/relays/${encodeURIComponent(url)}/reviews`
|
||||||
|
|||||||
33
src/pages/secondary/SystemSettingsPage/index.tsx
Normal file
33
src/pages/secondary/SystemSettingsPage/index.tsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { Input } from '@/components/ui/input'
|
||||||
|
import { Label } from '@/components/ui/label'
|
||||||
|
import { DEFAULT_FAVICON_URL_TEMPLATE } from '@/constants'
|
||||||
|
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
||||||
|
import { useContentPolicy } from '@/providers/ContentPolicyProvider'
|
||||||
|
import { forwardRef } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
const SystemSettingsPage = forwardRef(({ index }: { index?: number }, ref) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { faviconUrlTemplate, setFaviconUrlTemplate } = useContentPolicy()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SecondaryPageLayout ref={ref} index={index} title={t('System')}>
|
||||||
|
<div className="space-y-4 mt-3">
|
||||||
|
<div className="px-4 space-y-2">
|
||||||
|
<Label htmlFor="favicon-url" className="text-base font-normal">
|
||||||
|
{t('Favicon URL')}
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="favicon-url"
|
||||||
|
type="text"
|
||||||
|
value={faviconUrlTemplate}
|
||||||
|
onChange={(e) => setFaviconUrlTemplate(e.target.value)}
|
||||||
|
placeholder={DEFAULT_FAVICON_URL_TEMPLATE}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</SecondaryPageLayout>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
SystemSettingsPage.displayName = 'SystemSettingsPage'
|
||||||
|
export default SystemSettingsPage
|
||||||
@@ -16,6 +16,9 @@ type TContentPolicyContext = {
|
|||||||
autoLoadMedia: boolean
|
autoLoadMedia: boolean
|
||||||
mediaAutoLoadPolicy: TMediaAutoLoadPolicy
|
mediaAutoLoadPolicy: TMediaAutoLoadPolicy
|
||||||
setMediaAutoLoadPolicy: (policy: TMediaAutoLoadPolicy) => void
|
setMediaAutoLoadPolicy: (policy: TMediaAutoLoadPolicy) => void
|
||||||
|
|
||||||
|
faviconUrlTemplate: string
|
||||||
|
setFaviconUrlTemplate: (template: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const ContentPolicyContext = createContext<TContentPolicyContext | undefined>(undefined)
|
const ContentPolicyContext = createContext<TContentPolicyContext | undefined>(undefined)
|
||||||
@@ -35,6 +38,7 @@ export function ContentPolicyProvider({ children }: { children: React.ReactNode
|
|||||||
storage.getHideContentMentioningMutedUsers()
|
storage.getHideContentMentioningMutedUsers()
|
||||||
)
|
)
|
||||||
const [mediaAutoLoadPolicy, setMediaAutoLoadPolicy] = useState(storage.getMediaAutoLoadPolicy())
|
const [mediaAutoLoadPolicy, setMediaAutoLoadPolicy] = useState(storage.getMediaAutoLoadPolicy())
|
||||||
|
const [faviconUrlTemplate, setFaviconUrlTemplate] = useState(storage.getFaviconUrlTemplate())
|
||||||
const [connectionType, setConnectionType] = useState((navigator as any).connection?.type)
|
const [connectionType, setConnectionType] = useState((navigator as any).connection?.type)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -83,6 +87,11 @@ export function ContentPolicyProvider({ children }: { children: React.ReactNode
|
|||||||
setMediaAutoLoadPolicy(policy)
|
setMediaAutoLoadPolicy(policy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updateFaviconUrlTemplate = (template: string) => {
|
||||||
|
storage.setFaviconUrlTemplate(template)
|
||||||
|
setFaviconUrlTemplate(template)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContentPolicyContext.Provider
|
<ContentPolicyContext.Provider
|
||||||
value={{
|
value={{
|
||||||
@@ -94,7 +103,9 @@ export function ContentPolicyProvider({ children }: { children: React.ReactNode
|
|||||||
setHideContentMentioningMutedUsers: updateHideContentMentioningMutedUsers,
|
setHideContentMentioningMutedUsers: updateHideContentMentioningMutedUsers,
|
||||||
autoLoadMedia,
|
autoLoadMedia,
|
||||||
mediaAutoLoadPolicy,
|
mediaAutoLoadPolicy,
|
||||||
setMediaAutoLoadPolicy: updateMediaAutoLoadPolicy
|
setMediaAutoLoadPolicy: updateMediaAutoLoadPolicy,
|
||||||
|
faviconUrlTemplate,
|
||||||
|
setFaviconUrlTemplate: updateFaviconUrlTemplate
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import RelaySettingsPage from '@/pages/secondary/RelaySettingsPage'
|
|||||||
import RizfulPage from '@/pages/secondary/RizfulPage'
|
import RizfulPage from '@/pages/secondary/RizfulPage'
|
||||||
import SearchPage from '@/pages/secondary/SearchPage'
|
import SearchPage from '@/pages/secondary/SearchPage'
|
||||||
import SettingsPage from '@/pages/secondary/SettingsPage'
|
import SettingsPage from '@/pages/secondary/SettingsPage'
|
||||||
|
import SystemSettingsPage from '@/pages/secondary/SystemSettingsPage'
|
||||||
import TranslationPage from '@/pages/secondary/TranslationPage'
|
import TranslationPage from '@/pages/secondary/TranslationPage'
|
||||||
import WalletPage from '@/pages/secondary/WalletPage'
|
import WalletPage from '@/pages/secondary/WalletPage'
|
||||||
import { match } from 'path-to-regexp'
|
import { match } from 'path-to-regexp'
|
||||||
@@ -41,6 +42,7 @@ const SECONDARY_ROUTE_CONFIGS = [
|
|||||||
{ path: '/settings/appearance', element: <AppearanceSettingsPage /> },
|
{ path: '/settings/appearance', element: <AppearanceSettingsPage /> },
|
||||||
{ path: '/settings/translation', element: <TranslationPage /> },
|
{ path: '/settings/translation', element: <TranslationPage /> },
|
||||||
{ path: '/settings/emoji-packs', element: <EmojiPackSettingsPage /> },
|
{ path: '/settings/emoji-packs', element: <EmojiPackSettingsPage /> },
|
||||||
|
{ path: '/settings/system', element: <SystemSettingsPage /> },
|
||||||
{ path: '/profile-editor', element: <ProfileEditorPage /> },
|
{ path: '/profile-editor', element: <ProfileEditorPage /> },
|
||||||
{ path: '/mutes', element: <MuteListPage /> },
|
{ path: '/mutes', element: <MuteListPage /> },
|
||||||
{ path: '/rizful', element: <RizfulPage /> },
|
{ path: '/rizful', element: <RizfulPage /> },
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
DEFAULT_FAVICON_URL_TEMPLATE,
|
||||||
DEFAULT_NIP_96_SERVICE,
|
DEFAULT_NIP_96_SERVICE,
|
||||||
ExtendedKind,
|
ExtendedKind,
|
||||||
MEDIA_AUTO_LOAD_POLICY,
|
MEDIA_AUTO_LOAD_POLICY,
|
||||||
@@ -52,6 +53,7 @@ class LocalStorageService {
|
|||||||
private sidebarCollapse: boolean = false
|
private sidebarCollapse: boolean = false
|
||||||
private primaryColor: TPrimaryColor = 'DEFAULT'
|
private primaryColor: TPrimaryColor = 'DEFAULT'
|
||||||
private enableSingleColumnLayout: boolean = true
|
private enableSingleColumnLayout: boolean = true
|
||||||
|
private faviconUrlTemplate: string = DEFAULT_FAVICON_URL_TEMPLATE
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
if (!LocalStorageService.instance) {
|
if (!LocalStorageService.instance) {
|
||||||
@@ -205,6 +207,9 @@ class LocalStorageService {
|
|||||||
this.enableSingleColumnLayout =
|
this.enableSingleColumnLayout =
|
||||||
window.localStorage.getItem(StorageKey.ENABLE_SINGLE_COLUMN_LAYOUT) !== 'false'
|
window.localStorage.getItem(StorageKey.ENABLE_SINGLE_COLUMN_LAYOUT) !== 'false'
|
||||||
|
|
||||||
|
this.faviconUrlTemplate =
|
||||||
|
window.localStorage.getItem(StorageKey.FAVICON_URL_TEMPLATE) ?? DEFAULT_FAVICON_URL_TEMPLATE
|
||||||
|
|
||||||
// Clean up deprecated data
|
// Clean up deprecated data
|
||||||
window.localStorage.removeItem(StorageKey.ACCOUNT_PROFILE_EVENT_MAP)
|
window.localStorage.removeItem(StorageKey.ACCOUNT_PROFILE_EVENT_MAP)
|
||||||
window.localStorage.removeItem(StorageKey.ACCOUNT_FOLLOW_LIST_EVENT_MAP)
|
window.localStorage.removeItem(StorageKey.ACCOUNT_FOLLOW_LIST_EVENT_MAP)
|
||||||
@@ -515,6 +520,15 @@ class LocalStorageService {
|
|||||||
this.enableSingleColumnLayout = enable
|
this.enableSingleColumnLayout = enable
|
||||||
window.localStorage.setItem(StorageKey.ENABLE_SINGLE_COLUMN_LAYOUT, enable.toString())
|
window.localStorage.setItem(StorageKey.ENABLE_SINGLE_COLUMN_LAYOUT, enable.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getFaviconUrlTemplate() {
|
||||||
|
return this.faviconUrlTemplate
|
||||||
|
}
|
||||||
|
|
||||||
|
setFaviconUrlTemplate(template: string) {
|
||||||
|
this.faviconUrlTemplate = template
|
||||||
|
window.localStorage.setItem(StorageKey.FAVICON_URL_TEMPLATE, template)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const instance = new LocalStorageService()
|
const instance = new LocalStorageService()
|
||||||
|
|||||||
Reference in New Issue
Block a user