mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-03 10:59:34 +00:00
feat(settings): move the remark model control to the subscription tab
Relocate Remark Model & Separation Character from the General/Panel tab to the Subscription tab's Information section, beside Show Info and Email in Remark, since it only governs how share-link remarks are composed. The sample preview uses concrete example values and renders the separator literally. Also drop the port from the subscription page link rows so each row shows just the inbound remark; the port still appears in the client QR modal and the client info modal.
This commit is contained in:
@@ -34,7 +34,7 @@ export class AllSetting {
|
||||
subSupportUrl = '';
|
||||
subProfileUrl = '';
|
||||
subAnnounce = '';
|
||||
subEnableRouting = true;
|
||||
subEnableRouting = false;
|
||||
subRoutingRules = '';
|
||||
subListen = '';
|
||||
subPort = 2096;
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
Input,
|
||||
InputNumber,
|
||||
Select,
|
||||
Space,
|
||||
Switch,
|
||||
} from 'antd';
|
||||
import type { AllSetting } from '@/models/setting';
|
||||
@@ -23,8 +22,6 @@ interface GeneralTabProps {
|
||||
updateSetting: (patch: Partial<AllSetting>) => void;
|
||||
}
|
||||
|
||||
const REMARK_MODELS: Record<string, string> = { i: 'Inbound', e: 'Email', o: 'Other' };
|
||||
const REMARK_SEPARATORS = [' ', '-', '_', '@', ':', '~', '|', ',', '.', '/'];
|
||||
const DATEPICKER_LIST: { name: string; value: 'gregorian' | 'jalalian' }[] = [
|
||||
{ name: 'Gregorian (Standard)', value: 'gregorian' },
|
||||
{ name: 'Jalalian (شمسی)', value: 'jalalian' },
|
||||
@@ -57,30 +54,6 @@ export default function GeneralTab({ allSetting, updateSetting }: GeneralTabProp
|
||||
return () => { cancelled = true; };
|
||||
}, []);
|
||||
|
||||
const remarkModel = useMemo(() => {
|
||||
const rm = allSetting.remarkModel || '';
|
||||
return rm.length > 1 ? rm.substring(1).split('') : [];
|
||||
}, [allSetting.remarkModel]);
|
||||
|
||||
const remarkSeparator = useMemo(() => {
|
||||
const rm = allSetting.remarkModel || '-';
|
||||
return rm.length > 1 ? rm.charAt(0) : '-';
|
||||
}, [allSetting.remarkModel]);
|
||||
|
||||
const remarkSample = useMemo(() => {
|
||||
const parts = remarkModel.map((k) => REMARK_MODELS[k]);
|
||||
return parts.length === 0 ? '' : parts.join(remarkSeparator);
|
||||
}, [remarkModel, remarkSeparator]);
|
||||
|
||||
function setRemarkModel(parts: string[]) {
|
||||
updateSetting({ remarkModel: remarkSeparator + parts.join('') });
|
||||
}
|
||||
|
||||
function setRemarkSeparator(sep: string) {
|
||||
const tail = (allSetting.remarkModel || '-').substring(1);
|
||||
updateSetting({ remarkModel: sep + tail });
|
||||
}
|
||||
|
||||
const ldapInboundTagList = useMemo(() => {
|
||||
const csv = allSetting.ldapInboundTags || '';
|
||||
return csv.length ? csv.split(',').map((s) => s.trim()).filter(Boolean) : [];
|
||||
@@ -115,28 +88,6 @@ export default function GeneralTab({ allSetting, updateSetting }: GeneralTabProp
|
||||
label: t('pages.settings.panelSettings'),
|
||||
children: (
|
||||
<>
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.remarkModel')}
|
||||
description={<>{t('pages.settings.sampleRemark')}: <i>#{remarkSample}</i></>}
|
||||
>
|
||||
<Space.Compact style={{ width: '100%' }}>
|
||||
<Select
|
||||
mode="multiple"
|
||||
value={remarkModel}
|
||||
onChange={setRemarkModel}
|
||||
style={{ paddingRight: '.5rem', minWidth: '80%', width: 'auto' }}
|
||||
options={Object.entries(REMARK_MODELS).map(([k, l]) => ({ value: k, label: l }))}
|
||||
/>
|
||||
<Select
|
||||
value={remarkSeparator}
|
||||
onChange={setRemarkSeparator}
|
||||
style={{ width: '20%' }}
|
||||
options={REMARK_SEPARATORS.map((s) => ({ value: s, label: s }))}
|
||||
/>
|
||||
</Space.Compact>
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem paddings="small" title={t('pages.settings.panelListeningIP')} description={t('pages.settings.panelListeningIPDesc')}>
|
||||
<Input value={allSetting.webListen} onChange={(e) => updateSetting({ webListen: e.target.value })} />
|
||||
</SettingListItem>
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import { Collapse, Divider, Input, InputNumber, Switch } from 'antd';
|
||||
import { useMemo } from 'react';
|
||||
import { Collapse, Divider, Input, InputNumber, Select, Space, Switch } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { AllSetting } from '@/models/setting';
|
||||
import { SettingListItem } from '@/components/ui';
|
||||
import { sanitizePath, normalizePath } from './uriPath';
|
||||
|
||||
const REMARK_MODELS: Record<string, string> = { i: 'Inbound', e: 'Email', o: 'Other' };
|
||||
const REMARK_SAMPLES: Record<string, string> = { i: 'Germany', e: 'john', o: 'Relay' };
|
||||
const REMARK_SEPARATORS = [' ', '-', '_', '@', ':', '~', '|', ',', '.', '/'];
|
||||
|
||||
interface SubscriptionGeneralTabProps {
|
||||
allSetting: AllSetting;
|
||||
updateSetting: (patch: Partial<AllSetting>) => void;
|
||||
@@ -12,6 +17,30 @@ interface SubscriptionGeneralTabProps {
|
||||
export default function SubscriptionGeneralTab({ allSetting, updateSetting }: SubscriptionGeneralTabProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const remarkModel = useMemo(() => {
|
||||
const rm = allSetting.remarkModel || '';
|
||||
return rm.length > 1 ? rm.substring(1).split('') : [];
|
||||
}, [allSetting.remarkModel]);
|
||||
|
||||
const remarkSeparator = useMemo(() => {
|
||||
const rm = allSetting.remarkModel || '-';
|
||||
return rm.length > 1 ? rm.charAt(0) : '-';
|
||||
}, [allSetting.remarkModel]);
|
||||
|
||||
const remarkSample = useMemo(() => {
|
||||
const parts = remarkModel.map((k) => REMARK_SAMPLES[k]);
|
||||
return parts.length === 0 ? '' : parts.join(remarkSeparator);
|
||||
}, [remarkModel, remarkSeparator]);
|
||||
|
||||
function setRemarkModel(parts: string[]) {
|
||||
updateSetting({ remarkModel: remarkSeparator + parts.join('') });
|
||||
}
|
||||
|
||||
function setRemarkSeparator(sep: string) {
|
||||
const tail = (allSetting.remarkModel || '-').substring(1);
|
||||
updateSetting({ remarkModel: sep + tail });
|
||||
}
|
||||
|
||||
return (
|
||||
<Collapse defaultActiveKey="1" items={[
|
||||
{
|
||||
@@ -68,6 +97,44 @@ export default function SubscriptionGeneralTab({ allSetting, updateSetting }: Su
|
||||
<Switch checked={allSetting.subEmailInRemark} onChange={(v) => updateSetting({ subEmailInRemark: v })} />
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem
|
||||
paddings="small"
|
||||
title={t('pages.settings.remarkModel')}
|
||||
description={
|
||||
<>
|
||||
{t('pages.settings.sampleRemark')}:{' '}
|
||||
<span
|
||||
style={{
|
||||
fontFamily: 'monospace',
|
||||
padding: '1px 6px',
|
||||
borderRadius: 4,
|
||||
border: '1px solid var(--ant-color-border)',
|
||||
background: 'var(--ant-color-fill-tertiary)',
|
||||
whiteSpace: 'pre',
|
||||
}}
|
||||
>
|
||||
{remarkSample ? `#${remarkSample}` : '—'}
|
||||
</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Space.Compact style={{ width: '100%' }}>
|
||||
<Select
|
||||
mode="multiple"
|
||||
value={remarkModel}
|
||||
onChange={setRemarkModel}
|
||||
style={{ paddingRight: '.5rem', minWidth: '80%', width: 'auto' }}
|
||||
options={Object.entries(REMARK_MODELS).map(([k, l]) => ({ value: k, label: l }))}
|
||||
/>
|
||||
<Select
|
||||
value={remarkSeparator}
|
||||
onChange={setRemarkSeparator}
|
||||
style={{ width: '20%' }}
|
||||
options={REMARK_SEPARATORS.map((s) => ({ value: s, label: s === ' ' ? '␣' : s }))}
|
||||
/>
|
||||
</Space.Compact>
|
||||
</SettingListItem>
|
||||
|
||||
<Divider>{t('pages.settings.subTitle')}</Divider>
|
||||
|
||||
<SettingListItem paddings="small" title={t('pages.settings.subTitle')} description={t('pages.settings.subTitleDesc')}>
|
||||
|
||||
@@ -32,7 +32,7 @@ import {
|
||||
|
||||
import { ClipboardManager, IntlUtil, LanguageManager } from '@/utils';
|
||||
import { isPostQuantumLink } from '@/lib/xray/inbound-link';
|
||||
import { LinkTags, linkMetaText, parseLinkParts } from '@/lib/xray/link-label';
|
||||
import { LinkTags, parseLinkParts } from '@/lib/xray/link-label';
|
||||
import { setMessageInstance } from '@/utils/messageBus';
|
||||
import { pauseAnimationsUntilLeave, useTheme } from '@/hooks/useTheme';
|
||||
import SubUsageSummary from './SubUsageSummary';
|
||||
@@ -396,7 +396,7 @@ export default function SubPage() {
|
||||
{links.map((link, idx) => {
|
||||
const parts = parseLinkParts(link, linkEmails[idx] || '');
|
||||
const fallback = `Link ${idx + 1}`;
|
||||
const rowTitle = (parts && linkMetaText(parts)) || fallback;
|
||||
const rowTitle = parts?.remark || fallback;
|
||||
const qrLabel = [parts?.remark, linkEmails[idx]].filter(Boolean).join('-') || rowTitle;
|
||||
const canQr = !isPostQuantumLink(link);
|
||||
return (
|
||||
|
||||
@@ -61,7 +61,7 @@ var defaultValueMap = map[string]string{
|
||||
"subSupportUrl": "",
|
||||
"subProfileUrl": "",
|
||||
"subAnnounce": "",
|
||||
"subEnableRouting": "true",
|
||||
"subEnableRouting": "false",
|
||||
"subRoutingRules": "",
|
||||
"subListen": "",
|
||||
"subPort": "2096",
|
||||
@@ -76,7 +76,7 @@ var defaultValueMap = map[string]string{
|
||||
"subURI": "",
|
||||
"subJsonPath": "/json/",
|
||||
"subJsonURI": "",
|
||||
"subClashEnable": "true",
|
||||
"subClashEnable": "false",
|
||||
"subClashPath": "/clash/",
|
||||
"subClashURI": "",
|
||||
"subJsonFragment": "",
|
||||
|
||||
Reference in New Issue
Block a user