Merge branch 'master' into codex/database-backup-oss-autotimer
This commit is contained in:
@@ -42,7 +42,7 @@
|
|||||||
|
|
||||||
1. 草稿页作品卡对齐发现页列表卡风格:左侧信息,右侧封面图,移动端单列,桌面两到三列。
|
1. 草稿页作品卡对齐发现页列表卡风格:左侧信息,右侧封面图,移动端单列,桌面两到三列。
|
||||||
2. 草稿页顶部 `全部 / 草稿 / 已发布` 筛选与发现页 `推荐 / 今日 / 分类 / 排行` 频道标签复用同一选中 / 未选中视觉,即 `platform-mobile-home-channel` 与 `platform-mobile-home-channel--active`,不再使用旧 `platform-tab` 胶囊样式。
|
2. 草稿页顶部 `全部 / 草稿 / 已发布` 筛选与发现页 `推荐 / 今日 / 分类 / 排行` 频道标签复用同一选中 / 未选中视觉,即 `platform-mobile-home-channel` 与 `platform-mobile-home-channel--active`,不再使用旧 `platform-tab` 胶囊样式。
|
||||||
3. 草稿页与底部导航的未读提示点统一使用平台暖棕色点和暖棕光晕,不再使用红点或红色 glow;草稿 Tab 作品架卡片无论草稿 / 已发布都不外露作者信息;已发布作品卡右上角直接显示无边框分享 icon。删除等破坏性动作继续收口到左滑或长按操作层。
|
3. 草稿页与底部导航的未读提示点统一使用平台暖棕色点和暖棕光晕,不再使用红点或红色 glow;草稿 Tab 作品架卡片无论草稿 / 已发布都不外露作者信息;已发布作品卡右上角直接显示无边框分享 icon。删除等破坏性动作在作品卡上也要直接开放独立删除入口,左滑或长按仅作为辅助操作层。
|
||||||
4. 生成中作品在整卡上加等待遮罩,但不移除作品基础信息。
|
4. 生成中作品在整卡上加等待遮罩,但不移除作品基础信息。
|
||||||
5. 生成中状态不能只存在前端内存 notice。后端作品摘要必须下发可恢复的 `generationStatus`;前端刷新或退出产品后,作品架优先用摘要状态恢复等待遮罩,本轮内存 notice 只作为即时反馈。
|
5. 生成中状态不能只存在前端内存 notice。后端作品摘要必须下发可恢复的 `generationStatus`;前端刷新或退出产品后,作品架优先用摘要状态恢复等待遮罩,本轮内存 notice 只作为即时反馈。
|
||||||
6. 点击 `generationStatus=generating` 的草稿卡必须恢复对应玩法的生成进度页,不能进入空白结果页或普通工作区;恢复生成页的 `startedAtMs` 使用进入生成页的当前时间,作品摘要 `updatedAt` 只用于排序和摘要展示,不参与假进度起算。
|
6. 点击 `generationStatus=generating` 的草稿卡必须恢复对应玩法的生成进度页,不能进入空白结果页或普通工作区;恢复生成页的 `startedAtMs` 使用进入生成页的当前时间,作品摘要 `updatedAt` 只用于排序和摘要展示,不参与假进度起算。
|
||||||
|
|||||||
@@ -560,7 +560,7 @@ test('creation hub shows RPG public work code from published library entry', ()
|
|||||||
expect(screen.queryByText('CW-00000001')).toBeNull();
|
expect(screen.queryByText('CW-00000001')).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('creation hub hides persisted draft delete action behind swipe underlay', () => {
|
test('creation hub exposes persisted draft delete action directly on the card', () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<CustomWorldCreationHub
|
<CustomWorldCreationHub
|
||||||
items={[{ ...baseDraftItem, profileId: 'profile-1' }]}
|
items={[{ ...baseDraftItem, profileId: 'profile-1' }]}
|
||||||
@@ -579,7 +579,7 @@ test('creation hub hides persisted draft delete action behind swipe underlay', (
|
|||||||
expect(
|
expect(
|
||||||
container.querySelector('.creation-work-card__swipe-underlay'),
|
container.querySelector('.creation-work-card__swipe-underlay'),
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
expect(screen.queryByRole('button', { name: '删除' })).toBeNull();
|
expect(screen.getByRole('button', { name: '删除' })).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('creation hub reveals persisted draft delete action from left swipe', () => {
|
test('creation hub reveals persisted draft delete action from left swipe', () => {
|
||||||
@@ -607,7 +607,9 @@ test('creation hub reveals persisted draft delete action from left swipe', () =>
|
|||||||
});
|
});
|
||||||
fireEvent.touchEnd(card);
|
fireEvent.touchEnd(card);
|
||||||
|
|
||||||
expect(screen.getByRole('button', { name: '删除' })).toBeTruthy();
|
expect(
|
||||||
|
container.querySelector('.creation-work-card__swipe-button--danger'),
|
||||||
|
).toBeTruthy();
|
||||||
expect(
|
expect(
|
||||||
container.querySelector('.creation-work-card-shell--actions-visible'),
|
container.querySelector('.creation-work-card-shell--actions-visible'),
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
@@ -615,7 +617,7 @@ test('creation hub reveals persisted draft delete action from left swipe', () =>
|
|||||||
|
|
||||||
test('creation hub reveals persisted draft delete action from keyboard', async () => {
|
test('creation hub reveals persisted draft delete action from keyboard', async () => {
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
render(
|
const { container } = render(
|
||||||
<CustomWorldCreationHub
|
<CustomWorldCreationHub
|
||||||
items={[{ ...baseDraftItem, profileId: 'profile-1' }]}
|
items={[{ ...baseDraftItem, profileId: 'profile-1' }]}
|
||||||
loading={false}
|
loading={false}
|
||||||
@@ -633,7 +635,9 @@ test('creation hub reveals persisted draft delete action from keyboard', async (
|
|||||||
screen.getByRole('button', { name: /继续完善《潮雾列岛》/u }).focus();
|
screen.getByRole('button', { name: /继续完善《潮雾列岛》/u }).focus();
|
||||||
await user.keyboard('{ArrowLeft}');
|
await user.keyboard('{ArrowLeft}');
|
||||||
|
|
||||||
expect(screen.getByRole('button', { name: '删除' })).toBeTruthy();
|
expect(
|
||||||
|
container.querySelector('.creation-work-card__swipe-button--danger'),
|
||||||
|
).toBeTruthy();
|
||||||
expect(screen.queryByRole('button', { name: '分享' })).toBeNull();
|
expect(screen.queryByRole('button', { name: '分享' })).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -642,7 +646,7 @@ test('creation hub shows delete action for baby object match drafts', async () =
|
|||||||
const onDeleteBabyObjectMatch = vi.fn();
|
const onDeleteBabyObjectMatch = vi.fn();
|
||||||
const onOpenBabyObjectMatchDetail = vi.fn();
|
const onOpenBabyObjectMatchDetail = vi.fn();
|
||||||
|
|
||||||
render(
|
const { container } = render(
|
||||||
<CustomWorldCreationHub
|
<CustomWorldCreationHub
|
||||||
items={[]}
|
items={[]}
|
||||||
babyObjectMatchItems={[babyObjectMatchDraftItem]}
|
babyObjectMatchItems={[babyObjectMatchDraftItem]}
|
||||||
@@ -662,7 +666,11 @@ test('creation hub shows delete action for baby object match drafts', async () =
|
|||||||
screen.getByRole('button', { name: /继续创作《宝贝识物删除测试》/u }).focus();
|
screen.getByRole('button', { name: /继续创作《宝贝识物删除测试》/u }).focus();
|
||||||
await user.keyboard('{ArrowLeft}');
|
await user.keyboard('{ArrowLeft}');
|
||||||
|
|
||||||
await user.click(screen.getByRole('button', { name: '删除' }));
|
await user.click(
|
||||||
|
container.querySelector(
|
||||||
|
'.creation-work-card__swipe-button--danger',
|
||||||
|
) as HTMLButtonElement,
|
||||||
|
);
|
||||||
|
|
||||||
expect(onDeleteBabyObjectMatch).toHaveBeenCalledWith(
|
expect(onDeleteBabyObjectMatch).toHaveBeenCalledWith(
|
||||||
babyObjectMatchDraftItem,
|
babyObjectMatchDraftItem,
|
||||||
@@ -711,7 +719,7 @@ test('creation hub works-only tab filters bark battle draft and published works'
|
|||||||
expect(onOpenBarkBattleDetail).toHaveBeenCalledWith(barkBattlePublishedItem);
|
expect(onOpenBarkBattleDetail).toHaveBeenCalledWith(barkBattlePublishedItem);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('creation hub published work delete action is revealed without opening card', async () => {
|
test('creation hub published work delete action is directly visible', async () => {
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
const onDeletePuzzle = vi.fn();
|
const onDeletePuzzle = vi.fn();
|
||||||
const onOpenPuzzleDetail = vi.fn();
|
const onOpenPuzzleDetail = vi.fn();
|
||||||
@@ -751,12 +759,6 @@ test('creation hub published work delete action is revealed without opening card
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(screen.queryByRole('button', { name: '删除' })).toBeNull();
|
|
||||||
expect(screen.getByRole('button', { name: '分享' })).toBeTruthy();
|
|
||||||
|
|
||||||
screen.getByRole('button', { name: /查看详情《待删拼图》/u }).focus();
|
|
||||||
await user.keyboard('{ArrowLeft}');
|
|
||||||
|
|
||||||
expect(screen.getByRole('button', { name: '删除' })).toBeTruthy();
|
expect(screen.getByRole('button', { name: '删除' })).toBeTruthy();
|
||||||
expect(screen.getByRole('button', { name: '分享' })).toBeTruthy();
|
expect(screen.getByRole('button', { name: '分享' })).toBeTruthy();
|
||||||
|
|
||||||
@@ -768,6 +770,115 @@ test('creation hub published work delete action is revealed without opening card
|
|||||||
expect(onOpenPuzzleDetail).not.toHaveBeenCalled();
|
expect(onOpenPuzzleDetail).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('creation hub exposes work delete action directly on card', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const onDeletePuzzle = vi.fn();
|
||||||
|
const onOpenPuzzleDetail = vi.fn();
|
||||||
|
|
||||||
|
render(
|
||||||
|
<CustomWorldCreationHub
|
||||||
|
items={[]}
|
||||||
|
puzzleItems={[
|
||||||
|
{
|
||||||
|
workId: 'puzzle:direct-delete',
|
||||||
|
profileId: 'puzzle-profile-direct-delete',
|
||||||
|
ownerUserId: 'user-1',
|
||||||
|
authorDisplayName: '拼图作者',
|
||||||
|
levelName: '直接删除拼图',
|
||||||
|
summary: '作品卡片直接开放删除入口。',
|
||||||
|
themeTags: ['灯塔'],
|
||||||
|
coverImageSrc: null,
|
||||||
|
publicationStatus: 'draft',
|
||||||
|
updatedAt: new Date('2026-05-02T12:00:00.000Z').toISOString(),
|
||||||
|
publishedAt: null,
|
||||||
|
playCount: 0,
|
||||||
|
remixCount: 0,
|
||||||
|
likeCount: 0,
|
||||||
|
publishReady: true,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
loading={false}
|
||||||
|
error={null}
|
||||||
|
onRetry={() => {}}
|
||||||
|
onCreateType={noopCreateType}
|
||||||
|
onOpenDraft={() => {}}
|
||||||
|
onEnterPublished={() => {}}
|
||||||
|
onOpenPuzzleDetail={onOpenPuzzleDetail}
|
||||||
|
onDeletePuzzle={onDeletePuzzle}
|
||||||
|
entryConfig={testEntryConfig}
|
||||||
|
creationTypes={testCreationTypes}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
await user.click(screen.getByRole('button', { name: '删除' }));
|
||||||
|
|
||||||
|
expect(onDeletePuzzle).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({ profileId: 'puzzle-profile-direct-delete' }),
|
||||||
|
);
|
||||||
|
expect(onOpenPuzzleDetail).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('creation hub keeps swipe delete action available', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const onDeletePuzzle = vi.fn();
|
||||||
|
const onOpenPuzzleDetail = vi.fn();
|
||||||
|
|
||||||
|
const { container } = render(
|
||||||
|
<CustomWorldCreationHub
|
||||||
|
items={[]}
|
||||||
|
puzzleItems={[
|
||||||
|
{
|
||||||
|
workId: 'puzzle:swipe-delete',
|
||||||
|
profileId: 'puzzle-profile-swipe-delete',
|
||||||
|
ownerUserId: 'user-1',
|
||||||
|
authorDisplayName: '拼图作者',
|
||||||
|
levelName: '左滑删除拼图',
|
||||||
|
summary: '左滑仍然保留辅助删除入口。',
|
||||||
|
themeTags: ['灯塔'],
|
||||||
|
coverImageSrc: null,
|
||||||
|
publicationStatus: 'published',
|
||||||
|
updatedAt: new Date('2026-05-02T12:00:00.000Z').toISOString(),
|
||||||
|
publishedAt: new Date('2026-05-02T12:10:00.000Z').toISOString(),
|
||||||
|
playCount: 8,
|
||||||
|
remixCount: 2,
|
||||||
|
likeCount: 1,
|
||||||
|
publishReady: true,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
loading={false}
|
||||||
|
error={null}
|
||||||
|
onRetry={() => {}}
|
||||||
|
onCreateType={noopCreateType}
|
||||||
|
onOpenDraft={() => {}}
|
||||||
|
onEnterPublished={() => {}}
|
||||||
|
onOpenPuzzleDetail={onOpenPuzzleDetail}
|
||||||
|
onDeletePuzzle={onDeletePuzzle}
|
||||||
|
entryConfig={testEntryConfig}
|
||||||
|
creationTypes={testCreationTypes}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const card = screen.getByRole('button', { name: /查看详情《左滑删除拼图》/u });
|
||||||
|
fireEvent.touchStart(card, {
|
||||||
|
touches: [{ clientX: 180, clientY: 20 }],
|
||||||
|
});
|
||||||
|
fireEvent.touchMove(card, {
|
||||||
|
touches: [{ clientX: 80, clientY: 22 }],
|
||||||
|
});
|
||||||
|
fireEvent.touchEnd(card);
|
||||||
|
|
||||||
|
const swipeDeleteButton = container.querySelector(
|
||||||
|
'.creation-work-card__swipe-button--danger',
|
||||||
|
) as HTMLButtonElement | null;
|
||||||
|
expect(swipeDeleteButton).toBeTruthy();
|
||||||
|
await user.click(swipeDeleteButton!);
|
||||||
|
|
||||||
|
expect(onDeletePuzzle).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({ profileId: 'puzzle-profile-swipe-delete' }),
|
||||||
|
);
|
||||||
|
expect(onOpenPuzzleDetail).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
test('creation hub opens persisted rpg drafts by card click', async () => {
|
test('creation hub opens persisted rpg drafts by card click', async () => {
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
const openedItems: CustomWorldWorkSummary[] = [];
|
const openedItems: CustomWorldWorkSummary[] = [];
|
||||||
@@ -942,7 +1053,7 @@ test('creation hub left swipe draft reveals delete without opening card', () =>
|
|||||||
const onDeletePublished = vi.fn();
|
const onDeletePublished = vi.fn();
|
||||||
const onOpenDraft = vi.fn();
|
const onOpenDraft = vi.fn();
|
||||||
|
|
||||||
render(
|
const { container } = render(
|
||||||
<CustomWorldCreationHub
|
<CustomWorldCreationHub
|
||||||
items={[{ ...baseDraftItem, profileId: 'profile-1' }]}
|
items={[{ ...baseDraftItem, profileId: 'profile-1' }]}
|
||||||
loading={false}
|
loading={false}
|
||||||
@@ -966,6 +1077,8 @@ test('creation hub left swipe draft reveals delete without opening card', () =>
|
|||||||
});
|
});
|
||||||
fireEvent.touchEnd(card);
|
fireEvent.touchEnd(card);
|
||||||
|
|
||||||
expect(screen.getByRole('button', { name: '删除' })).toBeTruthy();
|
expect(
|
||||||
|
container.querySelector('.creation-work-card__swipe-button--danger'),
|
||||||
|
).toBeTruthy();
|
||||||
expect(onOpenDraft).not.toHaveBeenCalled();
|
expect(onOpenDraft).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -676,6 +676,7 @@ export function CustomWorldWorkCard({
|
|||||||
{displayTitle}
|
{displayTitle}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="creation-work-card__quick-actions">
|
||||||
{canUseShareAction ? (
|
{canUseShareAction ? (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -708,11 +709,42 @@ export function CustomWorldWorkCard({
|
|||||||
? '分享内容复制失败'
|
? '分享内容复制失败'
|
||||||
: '分享'
|
: '分享'
|
||||||
}
|
}
|
||||||
className="creation-work-card__share-button"
|
className="creation-work-card__quick-action-button"
|
||||||
>
|
>
|
||||||
<Share2 aria-hidden="true" className="h-4 w-4" />
|
<Share2 aria-hidden="true" className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
) : null}
|
) : null}
|
||||||
|
{onDelete ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
suppressOpenRef.current = false;
|
||||||
|
closeSwipeActions();
|
||||||
|
onDelete();
|
||||||
|
}}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
}}
|
||||||
|
onPointerDown={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
}}
|
||||||
|
onTouchStart={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
}}
|
||||||
|
disabled={deleteBusy}
|
||||||
|
title={deleteBusy ? '删除中' : '删除作品'}
|
||||||
|
aria-label={deleteBusy ? '删除中' : '删除'}
|
||||||
|
className="creation-work-card__quick-action-button creation-work-card__quick-action-button--danger"
|
||||||
|
>
|
||||||
|
{deleteBusy ? (
|
||||||
|
<span className="text-xs leading-none">...</span>
|
||||||
|
) : (
|
||||||
|
<Trash2 aria-hidden="true" className="h-4 w-4" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="creation-work-card__meta platform-category-game-item__meta">
|
<div className="creation-work-card__meta platform-category-game-item__meta">
|
||||||
|
|||||||
@@ -2081,7 +2081,7 @@ function pickDraftCompletionDialogSourceId(
|
|||||||
function buildDraftCompletionDialogSource(
|
function buildDraftCompletionDialogSource(
|
||||||
kind: CreationWorkShelfKind,
|
kind: CreationWorkShelfKind,
|
||||||
ids: Array<string | null | undefined>,
|
ids: Array<string | null | undefined>,
|
||||||
) {
|
): string {
|
||||||
const sourceId = pickDraftCompletionDialogSourceId(ids);
|
const sourceId = pickDraftCompletionDialogSourceId(ids);
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case 'rpg':
|
case 'rpg':
|
||||||
@@ -2103,6 +2103,7 @@ function buildDraftCompletionDialogSource(
|
|||||||
case 'baby-object-match':
|
case 'baby-object-match':
|
||||||
return formatPlatformTaskCompletionSource('宝贝识物草稿', sourceId);
|
return formatPlatformTaskCompletionSource('宝贝识物草稿', sourceId);
|
||||||
}
|
}
|
||||||
|
return formatPlatformTaskCompletionSource('创作草稿', sourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createMiniGameDraftGenerationStateForRestoredDraft(
|
function createMiniGameDraftGenerationStateForRestoredDraft(
|
||||||
|
|||||||
@@ -2044,7 +2044,14 @@ html[data-mobile-keyboard-open='true'] .platform-mobile-bottom-dock {
|
|||||||
white-space: normal;
|
white-space: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.creation-work-card__share-button {
|
.creation-work-card__quick-actions {
|
||||||
|
display: inline-flex;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.12rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.creation-work-card__quick-action-button {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
width: 2rem;
|
width: 2rem;
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
@@ -2061,17 +2068,32 @@ html[data-mobile-keyboard-open='true'] .platform-mobile-bottom-dock {
|
|||||||
transform 160ms ease;
|
transform 160ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.creation-work-card__share-button:hover {
|
.creation-work-card__quick-action-button:hover {
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
background: color-mix(in srgb, var(--platform-cool-bg) 24%, transparent);
|
background: color-mix(in srgb, var(--platform-cool-bg) 24%, transparent);
|
||||||
color: var(--platform-cool-text);
|
color: var(--platform-cool-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.creation-work-card__share-button:focus-visible {
|
.creation-work-card__quick-action-button:focus-visible {
|
||||||
outline: 2px solid var(--platform-cool-border);
|
outline: 2px solid var(--platform-cool-border);
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.creation-work-card__quick-action-button--danger {
|
||||||
|
color: color-mix(in srgb, #c7653d 78%, var(--platform-text-soft));
|
||||||
|
}
|
||||||
|
|
||||||
|
.creation-work-card__quick-action-button--danger:hover {
|
||||||
|
background: color-mix(in srgb, #c7653d 18%, transparent);
|
||||||
|
color: #a9472c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.creation-work-card__quick-action-button:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.62;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
.creation-work-card__meta {
|
.creation-work-card__meta {
|
||||||
display: flex;
|
display: flex;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user