mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-08-29 01:08:59 +00:00
ENH - refine DarkChat and Picstagram flows
This commit is contained in:
@@ -1644,6 +1644,7 @@ const defaultLocales: LocaleTree = {
|
||||
noChatsBody:
|
||||
'Connect with a Dark-ID or invitation code. There is no public search.',
|
||||
newChat: 'New Chat',
|
||||
to: 'To:',
|
||||
connectPrivately: 'Connect privately',
|
||||
newChatBody:
|
||||
'Enter an exact Dark-ID or invitation code. Unknown identities require confirmation.',
|
||||
@@ -1720,6 +1721,7 @@ const defaultLocales: LocaleTree = {
|
||||
shareActivity: 'Share activity status',
|
||||
inviteCode: 'Private invitation code',
|
||||
copyInvite: 'Copy Invite',
|
||||
shareInvite: 'Share Invite',
|
||||
privacyDisclaimer:
|
||||
'DarkChat stores messages on the server and does not claim end-to-end encryption.',
|
||||
profileActions: 'Account Actions',
|
||||
|
||||
@@ -3,6 +3,7 @@ import { describe, expect, it } from 'vitest'
|
||||
import type { EasySharePayload } from '@/types/easyshare'
|
||||
import {
|
||||
easyShareCrewLinkInviteCode,
|
||||
easyShareDarkChatInviteCode,
|
||||
easyShareDestinationAppIds,
|
||||
easyShareMusicTarget,
|
||||
easyShareRoute,
|
||||
@@ -89,11 +90,25 @@ describe('EasyShare deep links', () => {
|
||||
|
||||
it('accepts the canonical payload id as a CrewLink invite fallback', () => {
|
||||
expect(easyShareCrewLinkInviteCode('link', '', 'n1ght247')).toBe('N1GHT247')
|
||||
expect(easyShareCrewLinkInviteCode('profile', '', 'n1ght247')).toBeNull()
|
||||
expect(easyShareCrewLinkInviteCode('link', '', 'invalid-code')).toBeNull()
|
||||
})
|
||||
|
||||
it('extracts a private DarkChat invitation from its profile share', () => {
|
||||
expect(
|
||||
easyShareCrewLinkInviteCode('profile', '', 'n1ght247'),
|
||||
easyShareDarkChatInviteCode(
|
||||
'profile',
|
||||
'skyphone://darkchat/invite/dc-7x4k-nova',
|
||||
),
|
||||
).toBe('DC-7X4K-NOVA')
|
||||
expect(
|
||||
easyShareDarkChatInviteCode(
|
||||
'link',
|
||||
'skyphone://darkchat/invite/DC-7X4K-NOVA',
|
||||
),
|
||||
).toBeNull()
|
||||
expect(
|
||||
easyShareCrewLinkInviteCode('link', '', 'invalid-code'),
|
||||
easyShareDarkChatInviteCode('profile', 'skyphone://darkchat/invite/bad'),
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
@@ -113,9 +128,12 @@ describe('EasyShare deep links', () => {
|
||||
'skyphone://music/playlist/music-playlist-1',
|
||||
{ id: 'music-playlist-1', kind: 'playlist' },
|
||||
],
|
||||
] as const)('extracts the exact shared music target', (kind, link, target) => {
|
||||
expect(easyShareMusicTarget(kind, link)).toEqual(target)
|
||||
})
|
||||
] as const)(
|
||||
'extracts the exact shared music target',
|
||||
(kind, link, target) => {
|
||||
expect(easyShareMusicTarget(kind, link)).toEqual(target)
|
||||
},
|
||||
)
|
||||
|
||||
it('rejects mismatched music share kinds', () => {
|
||||
expect(
|
||||
@@ -128,17 +146,14 @@ describe('EasyShare deep links', () => {
|
||||
})
|
||||
|
||||
describe('EasyShare destination suggestions', () => {
|
||||
it.each([
|
||||
'document',
|
||||
'link',
|
||||
'location',
|
||||
'note',
|
||||
'text',
|
||||
] as const)('offers Notes for %s content from another app', (kind) => {
|
||||
expect(
|
||||
easyShareDestinationAppIds(payload({ appId: 'calendar', kind })),
|
||||
).toEqual(['messages', 'darkchat', 'flare', 'notes'])
|
||||
})
|
||||
it.each(['document', 'link', 'location', 'note', 'text'] as const)(
|
||||
'offers Notes for %s content from another app',
|
||||
(kind) => {
|
||||
expect(
|
||||
easyShareDestinationAppIds(payload({ appId: 'calendar', kind })),
|
||||
).toEqual(['messages', 'darkchat', 'flare', 'notes'])
|
||||
},
|
||||
)
|
||||
|
||||
it.each([
|
||||
'contact',
|
||||
@@ -148,13 +163,16 @@ describe('EasyShare destination suggestions', () => {
|
||||
'profile',
|
||||
'track',
|
||||
'video',
|
||||
] as const)('does not suggest lossy Notes conversion for %s content', (kind) => {
|
||||
expect(easyShareDestinationAppIds(payload({ kind }))).toEqual([
|
||||
'messages',
|
||||
'darkchat',
|
||||
'flare',
|
||||
])
|
||||
})
|
||||
] as const)(
|
||||
'does not suggest lossy Notes conversion for %s content',
|
||||
(kind) => {
|
||||
expect(easyShareDestinationAppIds(payload({ kind }))).toEqual([
|
||||
'messages',
|
||||
'darkchat',
|
||||
'flare',
|
||||
])
|
||||
},
|
||||
)
|
||||
|
||||
it('does not suggest saving a Notes item back into Notes', () => {
|
||||
expect(easyShareDestinationAppIds(payload({}))).toEqual([
|
||||
|
||||
@@ -40,11 +40,12 @@ export function easyShareRoute(payload: EasySharePayload): {
|
||||
path: string
|
||||
query: Record<string, string>
|
||||
} {
|
||||
const match = payload.link?.match(/^skyphone:\/\/([^/]+)(?:\/([^/]+))?(?:\/(.+))?$/)
|
||||
const match = payload.link?.match(
|
||||
/^skyphone:\/\/([^/]+)(?:\/([^/]+))?(?:\/(.+))?$/,
|
||||
)
|
||||
const schemeApp = match?.[1]
|
||||
const app = schemeApp ? getPhoneApp(schemeApp) : getPhoneApp(payload.appId)
|
||||
const path =
|
||||
(schemeApp && schemeRoutes[schemeApp]) || app?.route || '/'
|
||||
const path = (schemeApp && schemeRoutes[schemeApp]) || app?.route || '/'
|
||||
const query: Record<string, string> = {
|
||||
easyShareId: String(payload.id ?? match?.[3] ?? match?.[2] ?? ''),
|
||||
easyShareKind: payload.kind,
|
||||
@@ -75,6 +76,17 @@ export function easyShareCrewLinkInviteCode(
|
||||
return null
|
||||
}
|
||||
|
||||
export function easyShareDarkChatInviteCode(
|
||||
kind: unknown,
|
||||
link: unknown,
|
||||
): string | null {
|
||||
if (kind !== 'profile' || typeof link !== 'string') return null
|
||||
const match = link.match(
|
||||
/^skyphone:\/\/darkchat\/invite\/(DC-[A-Z0-9]{4}-[A-Z0-9]{4})$/i,
|
||||
)
|
||||
return match ? match[1].toUpperCase() : null
|
||||
}
|
||||
|
||||
export type EasyShareMusicTarget =
|
||||
| { id: string; kind: 'playlist' }
|
||||
| { id: string; kind: 'track'; source: 'server' | 'youtube' }
|
||||
|
||||
@@ -16,7 +16,7 @@ describe('DarkChatApp Sky UI contract', () => {
|
||||
expect(source).toContain('<SkyScrollArea')
|
||||
expect(source).toContain('<SkySettingsGroup')
|
||||
expect(source).toContain('<SkyMessagebar')
|
||||
expect(source).toContain('<SkyPillNavigation')
|
||||
expect(source).not.toContain('<SkyPillNavigation')
|
||||
})
|
||||
|
||||
it('keeps one identity action in the inbox header', () => {
|
||||
@@ -25,6 +25,36 @@ describe('DarkChatApp Sky UI contract', () => {
|
||||
const inbox = source.slice(inboxStart, newChatStart)
|
||||
|
||||
expect(inbox.match(/@click="openProfile"/g)).toHaveLength(1)
|
||||
expect(inbox).toContain('@click="beginNewChat"')
|
||||
expect(inbox).toContain('<SquarePen :size="21" />')
|
||||
})
|
||||
|
||||
it('matches the SMS recipient composer for new private chats', () => {
|
||||
const newChatStart = source.indexOf("screen === 'new'")
|
||||
const threadStart = source.indexOf("screen === 'thread'", newChatStart)
|
||||
const newChat = source.slice(newChatStart, threadStart)
|
||||
|
||||
expect(newChat).toContain('class="dc-recipient-field"')
|
||||
expect(newChat).toContain('layout="inline"')
|
||||
expect(newChat).toContain('class="dc-new-chat-contacts"')
|
||||
expect(newChat).toContain("{{ phone.t('Common.cancel') }}")
|
||||
expect(newChat).not.toContain('class="dc-hero"')
|
||||
})
|
||||
|
||||
it('keeps the search visually compact without shrinking its wrapper', () => {
|
||||
expect(source).toMatch(
|
||||
/\.dc-search :deep\(\.sky-searchbar__control\),[\s\S]*?height:\s*38px;[\s\S]*?min-height:\s*38px;/,
|
||||
)
|
||||
})
|
||||
|
||||
it('shares and consumes private profile invitations', () => {
|
||||
expect(source).toContain('function sharePrivateInvite(): void')
|
||||
expect(source).toContain('@click="sharePrivateInvite"')
|
||||
expect(source).toContain('easyShareDarkChatInviteCode(')
|
||||
expect(source).toContain('function openSharedInvite(): void')
|
||||
expect(source).toMatch(
|
||||
/openSharedInvite\(\)[\s\S]*?identifier\.value = sharedInviteCode\.value[\s\S]*?screen\.value = 'new'/,
|
||||
)
|
||||
})
|
||||
|
||||
it('uses the full conversation card width', () => {
|
||||
@@ -44,14 +74,18 @@ describe('DarkChatApp Sky UI contract', () => {
|
||||
expect(source).toContain('@click="deleteProfile"')
|
||||
})
|
||||
|
||||
it('separates the round attachment action from the floating message pill', () => {
|
||||
it('aligns the attachment action, input, and send icon in one composer pill', () => {
|
||||
const composerStart = source.indexOf('<div v-else class="dc-composer-row">')
|
||||
const composerEnd = source.indexOf('</section>', composerStart)
|
||||
const composer = source.slice(composerStart, composerEnd)
|
||||
|
||||
expect(source).toContain('class="dc-composer-row"')
|
||||
expect(source).toContain('class="dc-composer-action"')
|
||||
expect(source).toContain('class="dc-composer-pill"')
|
||||
expect(source).toMatch(
|
||||
/<div v-else class="dc-composer-row">[\s\S]*?<SkyGlass[\s\S]*?class="dc-composer-action"[\s\S]*?<SkyGlass class="dc-composer-pill">[\s\S]*?<SkyMessagebar/,
|
||||
/<div v-else class="dc-composer-row">[\s\S]*?class="dc-composer-pill"[\s\S]*?class="dc-composer-action"[\s\S]*?<SkyMessagebar/,
|
||||
)
|
||||
expect(source).not.toContain('<template #left>')
|
||||
expect(composer).not.toContain('<template #left>')
|
||||
expect(source).toMatch(
|
||||
/\.dc-composer-pill\s*\{[^}]*border-radius:\s*var\(--sky-radius-pill\)/s,
|
||||
)
|
||||
@@ -60,7 +94,7 @@ describe('DarkChatApp Sky UI contract', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('presents attachments as a compact five-action Sky glass rail', () => {
|
||||
it('presents attachments as the vertical five-action SMS glass menu', () => {
|
||||
const attachmentsStart = source.indexOf(
|
||||
'v-if="attachmentOpen" class="dc-attachments"',
|
||||
)
|
||||
@@ -70,7 +104,16 @@ describe('DarkChatApp Sky UI contract', () => {
|
||||
expect(attachments.match(/class="dc-attachment-action"/g)).toHaveLength(5)
|
||||
expect(attachments.match(/<SkyGlass/g)).toHaveLength(5)
|
||||
expect(source).toMatch(
|
||||
/\.dc-attachments\s*\{[^}]*grid-template-columns:\s*repeat\(5,/s,
|
||||
/\.dc-attachments\s*\{[^}]*display:\s*flex;[^}]*flex-direction:\s*column;/s,
|
||||
)
|
||||
})
|
||||
|
||||
it('centers all composer controls on the same level', () => {
|
||||
expect(source).toMatch(
|
||||
/\.dc-composer-row \.dc-composer-pill\s*\{[^}]*display:\s*flex;[^}]*align-items:\s*center;/s,
|
||||
)
|
||||
expect(source).toMatch(
|
||||
/\.dc-composer-action\s*\{[^}]*width:\s*46px;[^}]*height:\s*46px;/s,
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
ShieldCheck,
|
||||
ShieldOff,
|
||||
Smile,
|
||||
SquarePen,
|
||||
Trash2,
|
||||
UserRound,
|
||||
UserMinus,
|
||||
@@ -28,7 +29,7 @@ import {
|
||||
X,
|
||||
} from 'lucide-vue-next'
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
import DarkChatVoiceMessage from '@/components/DarkChatVoiceMessage.vue'
|
||||
import FullEmojiPicker from '@/components/FullEmojiPicker.vue'
|
||||
@@ -49,6 +50,7 @@ import type { EasySharePayload } from '@/types/easyshare'
|
||||
import type { MediaType, PhoneMedia } from '@/types/media'
|
||||
import { copyText } from '@/utils/clipboard'
|
||||
import { parseDatabaseDate, type DatabaseDateValue } from '@/utils/date'
|
||||
import { easyShareDarkChatInviteCode } from '@/utils/easyshare'
|
||||
import { handleEnterAction } from '@/utils/keyboard'
|
||||
import {
|
||||
SkyAppPage,
|
||||
@@ -64,7 +66,6 @@ import {
|
||||
SkyListItem,
|
||||
SkyMessagebar,
|
||||
SkyNavbar,
|
||||
SkyPillNavigation,
|
||||
SkyScrollArea,
|
||||
SkySearchbar,
|
||||
SkySettingsGroup,
|
||||
@@ -88,6 +89,7 @@ const easyShare = useEasyShareStore()
|
||||
const sms = useMessagesStore()
|
||||
const messageMedia = useMessageMediaStore()
|
||||
const phone = usePhoneStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const screen = ref<'inbox' | 'new' | 'thread' | 'contact' | 'profile'>('inbox')
|
||||
const search = ref('')
|
||||
@@ -147,6 +149,23 @@ const filteredConversations = computed(() => {
|
||||
.includes(needle),
|
||||
)
|
||||
})
|
||||
const contactSuggestions = computed(() => {
|
||||
const needle = identifier.value.trim().toLocaleLowerCase(phone.lang)
|
||||
if (!needle) return darkchat.contacts.slice(0, 8)
|
||||
return darkchat.contacts
|
||||
.filter((contact) =>
|
||||
`${contact.alias} ${contact.darkId}`
|
||||
.toLocaleLowerCase(phone.lang)
|
||||
.includes(needle),
|
||||
)
|
||||
.slice(0, 8)
|
||||
})
|
||||
const sharedInviteCode = ref(
|
||||
easyShareDarkChatInviteCode(
|
||||
route.query.easyShareKind,
|
||||
route.query.easyShareLink,
|
||||
),
|
||||
)
|
||||
const active = computed(() => darkchat.activeConversation)
|
||||
const attachmentPanelOpen = computed(() => emojiOpen.value || gifOpen.value)
|
||||
const timerOptions = [0, -1, 60, 300, 3600, 86400, 604800]
|
||||
@@ -342,6 +361,25 @@ function back(): void {
|
||||
resetPanels()
|
||||
}
|
||||
|
||||
function beginNewChat(): void {
|
||||
identifier.value = ''
|
||||
screen.value = 'new'
|
||||
resetPanels()
|
||||
}
|
||||
|
||||
function openSharedInvite(): void {
|
||||
if (!sharedInviteCode.value || !darkchat.profile) return
|
||||
identifier.value = sharedInviteCode.value
|
||||
sharedInviteCode.value = null
|
||||
screen.value = 'new'
|
||||
resetPanels()
|
||||
const query = { ...route.query }
|
||||
delete query.easyShareId
|
||||
delete query.easyShareKind
|
||||
delete query.easyShareLink
|
||||
void router.replace({ path: route.path, query })
|
||||
}
|
||||
|
||||
function resetPanels(): void {
|
||||
attachmentOpen.value = false
|
||||
emojiOpen.value = false
|
||||
@@ -704,6 +742,7 @@ async function createProfile(): Promise<void> {
|
||||
const success = await darkchat.createProfile()
|
||||
profileActionPending.value = false
|
||||
if (!success) showToast(errorText(darkchat.lastError ?? undefined))
|
||||
else openSharedInvite()
|
||||
}
|
||||
|
||||
function selectDisappearing(value: number | string): void {
|
||||
@@ -733,7 +772,7 @@ function chooseSelection(value: number | string): void {
|
||||
selectionSheet.value = null
|
||||
}
|
||||
|
||||
function shareIdentity(): void {
|
||||
function sharePrivateInvite(): void {
|
||||
const profile = darkchat.profile
|
||||
if (!profile) return
|
||||
easyShare.open({
|
||||
@@ -918,6 +957,7 @@ function recordingTime(): string {
|
||||
onMounted(async () => {
|
||||
if (signedIn.value) await darkchat.bootstrap()
|
||||
if (await openEasyShareDraft()) return
|
||||
openSharedInvite()
|
||||
if (!active.value) return
|
||||
screen.value = 'thread'
|
||||
const media = messageMedia.consume(`darkchat:${active.value.id}`)
|
||||
@@ -1072,6 +1112,27 @@ onBeforeUnmount(() => {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.dc-search :deep(.sky-searchbar__control),
|
||||
.dc-search :deep(.sky-searchbar__input) {
|
||||
height: 38px;
|
||||
min-height: 38px;
|
||||
}
|
||||
|
||||
.dc-search :deep(.sky-searchbar__input) {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.dc-search :deep(.sky-searchbar__clear) {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
}
|
||||
|
||||
.dc-search :deep(.sky-searchbar__clear::before) {
|
||||
position: absolute;
|
||||
inset: -3px;
|
||||
content: '';
|
||||
}
|
||||
|
||||
.dc-security {
|
||||
width: 100%;
|
||||
min-height: 58px;
|
||||
@@ -1197,6 +1258,65 @@ onBeforeUnmount(() => {
|
||||
margin: 0 2px 22px;
|
||||
}
|
||||
|
||||
.dc-new-chat-scroll {
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
.dc-recipient-field,
|
||||
.dc-new-chat-contacts {
|
||||
margin: 0 calc(var(--sky-page-gutter) * -1) 12px;
|
||||
border-top: 1px solid var(--dc-border);
|
||||
border-bottom: 1px solid var(--dc-border);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.dc-recipient-field :deep(.sky-field) {
|
||||
min-height: 52px;
|
||||
padding: 0 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.dc-recipient-field :deep(.sky-field__inner) {
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.dc-recipient-field :deep(.sky-field__label) {
|
||||
margin: 0;
|
||||
flex: none;
|
||||
color: var(--dc-secondary);
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.dc-recipient-field :deep(.sky-field__control),
|
||||
.dc-recipient-field :deep(.sky-field__input) {
|
||||
min-height: 52px;
|
||||
}
|
||||
|
||||
.dc-recipient-field :deep(.sky-field__control) {
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.dc-recipient-field :deep(.sky-field__input) {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.dc-new-chat-contacts :deep(.sky-list-item__row) {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.dc-identifier-action {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.dc-contacts-group {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
@@ -1266,6 +1386,19 @@ onBeforeUnmount(() => {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.dc-invite-actions {
|
||||
min-width: 0;
|
||||
display: grid;
|
||||
grid-column: 1 / -1;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.dc-invite-actions :deep(.sky-button) {
|
||||
min-width: 0;
|
||||
grid-column: auto;
|
||||
}
|
||||
|
||||
.dc-thread-meta {
|
||||
min-height: 28px;
|
||||
display: flex;
|
||||
@@ -1432,30 +1565,34 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.dc-attachments {
|
||||
display: grid;
|
||||
padding: 9px 10px 10px;
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
gap: 5px;
|
||||
border-top: 1px solid var(--dc-border);
|
||||
background: var(--dc-surface);
|
||||
position: absolute;
|
||||
z-index: 48;
|
||||
bottom: calc(var(--sky-safe-area-bottom) + 78px);
|
||||
left: calc(var(--sky-safe-area-left) + var(--sky-page-gutter));
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
gap: 7px;
|
||||
}
|
||||
|
||||
.dc-attachment-action {
|
||||
min-width: 0;
|
||||
min-height: 72px;
|
||||
width: auto;
|
||||
min-height: 42px;
|
||||
display: flex;
|
||||
padding: 7px 2px 6px;
|
||||
padding: 4px 14px 4px 5px;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
border-radius: 18px;
|
||||
gap: 9px;
|
||||
border-radius: var(--sky-radius-pill);
|
||||
color: var(--dc-label);
|
||||
font-size: 12px;
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.dc-attachment-action > span {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: grid;
|
||||
flex: 0 0 32px;
|
||||
border-radius: 50%;
|
||||
color: var(--dc-accent);
|
||||
background: var(--sky-app-accent-soft);
|
||||
@@ -1463,13 +1600,11 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.dc-attachment-action > small {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
font-size: 9px;
|
||||
font-weight: 600;
|
||||
line-height: 12px;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
width: auto;
|
||||
color: inherit;
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@@ -1569,30 +1704,43 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.dc-composer-row .dc-composer-pill {
|
||||
width: 100%;
|
||||
min-height: 56px;
|
||||
display: flex;
|
||||
margin: 0;
|
||||
padding: 5px 7px;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.dc-composer-action {
|
||||
width: var(--sky-touch-target);
|
||||
height: var(--sky-touch-target);
|
||||
box-sizing: border-box;
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
display: grid;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
flex: 0 0 var(--sky-touch-target);
|
||||
flex: 0 0 46px;
|
||||
border-radius: 50%;
|
||||
color: var(--dc-accent);
|
||||
place-items: center;
|
||||
transition:
|
||||
color 180ms ease,
|
||||
transform 180ms ease;
|
||||
}
|
||||
|
||||
.dc-composer-action.active {
|
||||
color: #fff;
|
||||
background: var(--dc-accent);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.dc-composer {
|
||||
min-width: 0;
|
||||
padding: 6px;
|
||||
padding: 0;
|
||||
flex: 1;
|
||||
gap: 4px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
@@ -1826,15 +1974,19 @@ onBeforeUnmount(() => {
|
||||
:subtitle="t('privateNetwork')"
|
||||
variant="large"
|
||||
>
|
||||
<template #right>
|
||||
<SkyGlass
|
||||
component="button"
|
||||
class="dc-navbar-action"
|
||||
<template #left>
|
||||
<SkyLink
|
||||
icon-only
|
||||
:aria-label="t('myIdentity')"
|
||||
@click="openProfile"
|
||||
>
|
||||
<UserRound :size="20" />
|
||||
</SkyGlass>
|
||||
</SkyLink>
|
||||
</template>
|
||||
<template #right>
|
||||
<SkyLink icon-only :aria-label="t('newChat')" @click="beginNewChat">
|
||||
<SquarePen :size="21" />
|
||||
</SkyLink>
|
||||
</template>
|
||||
<template #subnavbar>
|
||||
<SkySearchbar
|
||||
@@ -1847,7 +1999,7 @@ onBeforeUnmount(() => {
|
||||
</template>
|
||||
</SkyNavbar>
|
||||
|
||||
<SkyScrollArea padded with-tabbar class="dc-inbox-content">
|
||||
<SkyScrollArea padded class="dc-inbox-content">
|
||||
<SkyGlass
|
||||
component="button"
|
||||
class="dc-security"
|
||||
@@ -1910,68 +2062,36 @@ onBeforeUnmount(() => {
|
||||
<template #icon><ShieldCheck :size="27" /></template>
|
||||
</SkyEmptyState>
|
||||
</SkyScrollArea>
|
||||
|
||||
<SkyPillNavigation
|
||||
layout="compact"
|
||||
align="end"
|
||||
:label="t('newChat')"
|
||||
class="dc-inbox-navigation"
|
||||
>
|
||||
<SkyButton raised rounded @click="screen = 'new'">
|
||||
<MessageCirclePlus :size="19" />{{ t('newChat') }}
|
||||
</SkyButton>
|
||||
</SkyPillNavigation>
|
||||
</section>
|
||||
|
||||
<section v-else-if="screen === 'new'" class="dc-screen">
|
||||
<SkyNavbar
|
||||
:title="t('newChat')"
|
||||
show-back
|
||||
back-appearance="surface"
|
||||
:back-label="phone.t('Common.back')"
|
||||
@back="back"
|
||||
/>
|
||||
<SkyScrollArea padded class="dc-scroll-content">
|
||||
<SkyCard class="dc-hero" outline>
|
||||
<span class="dc-icon-tile"><QrCode :size="27" /></span>
|
||||
<h2>{{ t('connectPrivately') }}</h2>
|
||||
<p>{{ t('newChatBody') }}</p>
|
||||
</SkyCard>
|
||||
<SkyList
|
||||
inset
|
||||
strong
|
||||
class="dc-form-list"
|
||||
@keydown.enter="handleEnterAction($event, requestStart)"
|
||||
>
|
||||
<SkyNavbar :title="t('newChat')">
|
||||
<template #right>
|
||||
<SkyLink @click="back">{{ phone.t('Common.cancel') }}</SkyLink>
|
||||
</template>
|
||||
</SkyNavbar>
|
||||
<SkyScrollArea padded class="dc-new-chat-scroll">
|
||||
<SkyList class="dc-recipient-field" density="compact" flush>
|
||||
<SkyField
|
||||
:label="t('darkIdOrInvite')"
|
||||
:label="t('to')"
|
||||
layout="inline"
|
||||
:value="identifier"
|
||||
autocomplete="off"
|
||||
placeholder="dark:7X4K-P92D"
|
||||
:placeholder="t('darkIdOrInvite')"
|
||||
clear-button
|
||||
:clear-label="phone.t('Common.clear')"
|
||||
@input="setIdentifier"
|
||||
@clear="identifier = ''"
|
||||
@keyup.enter="requestStart()"
|
||||
/>
|
||||
</SkyList>
|
||||
<SkyButton
|
||||
large
|
||||
rounded
|
||||
block
|
||||
class="dc-primary"
|
||||
:disabled="!identifier.trim()"
|
||||
@click="requestStart()"
|
||||
>
|
||||
{{ t('continue') }}
|
||||
</SkyButton>
|
||||
|
||||
<SkySettingsGroup
|
||||
:title="t('contacts')"
|
||||
:footer="t('contactsHint')"
|
||||
class="dc-contacts-group"
|
||||
<SkyList
|
||||
v-if="contactSuggestions.length"
|
||||
class="dc-new-chat-contacts"
|
||||
flush
|
||||
>
|
||||
<SkyListItem
|
||||
v-for="contact in darkchat.contacts"
|
||||
v-for="contact in contactSuggestions"
|
||||
:key="contact.id"
|
||||
link
|
||||
link-component="button"
|
||||
@@ -1995,22 +2115,17 @@ onBeforeUnmount(() => {
|
||||
</span>
|
||||
</template>
|
||||
</SkyListItem>
|
||||
<li v-if="!darkchat.contacts.length" class="dc-contacts-empty">
|
||||
<UserRound :size="22" />{{ t('noContacts') }}
|
||||
</li>
|
||||
</SkySettingsGroup>
|
||||
|
||||
<SkyCard class="dc-qr-card" outline :content-wrap="false">
|
||||
<div class="dc-faux-qr"><QrCode :size="58" /></div>
|
||||
<div>
|
||||
<strong>{{ darkchat.profile.darkId }}</strong>
|
||||
<small>{{ t('shareIdentity') }}</small>
|
||||
</div>
|
||||
<SkyButton tonal rounded @click="shareIdentity">
|
||||
<Share2 :size="16" />
|
||||
{{ phone.t('Apps.easyShare.shareProfile') }}
|
||||
</SkyButton>
|
||||
</SkyCard>
|
||||
</SkyList>
|
||||
<SkyButton
|
||||
v-else-if="identifier.trim()"
|
||||
rounded
|
||||
tonal
|
||||
class="dc-identifier-action"
|
||||
@click="requestStart()"
|
||||
>
|
||||
<MessageCirclePlus :size="17" />
|
||||
{{ identifier }}
|
||||
</SkyButton>
|
||||
</SkyScrollArea>
|
||||
</section>
|
||||
|
||||
@@ -2234,16 +2349,16 @@ onBeforeUnmount(() => {
|
||||
</SkyLink>
|
||||
</SkyGlass>
|
||||
<div v-else class="dc-composer-row">
|
||||
<SkyGlass
|
||||
component="button"
|
||||
class="dc-composer-action"
|
||||
:class="{ active: attachmentOpen || attachmentPanelOpen }"
|
||||
:aria-label="phone.t('Common.add')"
|
||||
@click="toggleAttachmentPanel"
|
||||
>
|
||||
<Plus :size="23" />
|
||||
</SkyGlass>
|
||||
<SkyGlass class="dc-composer-pill">
|
||||
<SkyGlass component="div" :highlight="false" class="dc-composer-pill">
|
||||
<SkyGlass
|
||||
component="button"
|
||||
class="dc-composer-action"
|
||||
:class="{ active: attachmentOpen || attachmentPanelOpen }"
|
||||
:aria-label="phone.t('Common.add')"
|
||||
@click="toggleAttachmentPanel"
|
||||
>
|
||||
<Plus :size="23" />
|
||||
</SkyGlass>
|
||||
<SkyMessagebar
|
||||
class="dc-composer"
|
||||
embedded
|
||||
@@ -2448,13 +2563,18 @@ onBeforeUnmount(() => {
|
||||
<strong>{{ darkchat.profile.inviteCode }}</strong>
|
||||
<small>{{ t('inviteCode') }}</small>
|
||||
</div>
|
||||
<SkyButton
|
||||
tonal
|
||||
rounded
|
||||
@click="copyIdentity(darkchat.profile.inviteCode)"
|
||||
>
|
||||
<Copy :size="16" />{{ t('copyInvite') }}
|
||||
</SkyButton>
|
||||
<div class="dc-invite-actions">
|
||||
<SkyButton
|
||||
clear
|
||||
rounded
|
||||
@click="copyIdentity(darkchat.profile.inviteCode)"
|
||||
>
|
||||
<Copy :size="16" />{{ t('copyInvite') }}
|
||||
</SkyButton>
|
||||
<SkyButton tonal rounded @click="sharePrivateInvite">
|
||||
<Share2 :size="16" />{{ t('shareInvite') }}
|
||||
</SkyButton>
|
||||
</div>
|
||||
</SkyCard>
|
||||
<p class="dc-privacy-note">
|
||||
<LockKeyhole :size="17" />{{ t('privacyDisclaimer') }}
|
||||
|
||||
@@ -43,4 +43,10 @@ describe('House app sheets', () => {
|
||||
/\.house-key-list\s*\{[^}]*--sky-list-outer-left:\s*0px;[^}]*--sky-list-outer-right:\s*0px;/s,
|
||||
)
|
||||
})
|
||||
|
||||
it('centers the resident icon above the Give a Key title', () => {
|
||||
expect(source).toMatch(
|
||||
/\.house-candidates > svg\s*\{[^}]*display:\s*block;[^}]*margin:\s*0 auto;/s,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1046,7 +1046,8 @@ onBeforeUnmount(() => {
|
||||
text-align: center;
|
||||
}
|
||||
.house-candidates > svg {
|
||||
margin-top: 0;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
color: var(--house-accent);
|
||||
}
|
||||
.house-candidates h2 {
|
||||
|
||||
@@ -15,6 +15,18 @@ describe('Picstagram app layout', () => {
|
||||
expect(source).toContain('ps-post-more')
|
||||
expect(source).toContain('count(post.like_count)')
|
||||
expect(source).toContain('count(post.comment_count)')
|
||||
expect(source).toContain('class="ps-home-navbar"')
|
||||
expect(source).toMatch(
|
||||
/\.ps-home-navbar :deep\(\.sky-navbar__inner\)\s*\{[^}]*margin-bottom:\s*2px;[^}]*translateY\(-3px\)/s,
|
||||
)
|
||||
})
|
||||
|
||||
it('uses the central anchored dropdown for image actions', () => {
|
||||
expect(source).toContain('<SkyDropdown')
|
||||
expect(source).toContain(':target="postMenuTarget"')
|
||||
expect(source).toContain('@select="selectPostMenuItem"')
|
||||
expect(source).toContain('showPostActions($event, post)')
|
||||
expect(source).toContain("case 'share':")
|
||||
})
|
||||
|
||||
it('opens comments independently from post detail and collapses replies', () => {
|
||||
@@ -51,8 +63,11 @@ describe('Picstagram app layout', () => {
|
||||
it('keeps create out of navigation and exposes privacy requests', () => {
|
||||
expect(source).toContain(':item-count="4"')
|
||||
expect(source).not.toContain('ps-create-tab')
|
||||
expect(source).toContain('profileDraft.private = false')
|
||||
expect(source).toContain('profileDraft.private = true')
|
||||
expect(source).toContain('v-model="profileDraft.private"')
|
||||
expect(source).toContain('class="ps-edit-field-list"')
|
||||
expect(source).toContain('class="ps-privacy-control__icon"')
|
||||
expect(source).not.toContain('profileDraft.private = false')
|
||||
expect(source).not.toContain('profileDraft.private = true')
|
||||
expect(source).toContain(
|
||||
'respondToFollowRequest(activity.profile_id, true)',
|
||||
)
|
||||
|
||||
@@ -57,6 +57,7 @@ import {
|
||||
SkyChip,
|
||||
SkyDialog,
|
||||
SkyDialogButton,
|
||||
SkyDropdown,
|
||||
SkyField,
|
||||
SkyGlass,
|
||||
SkyLink,
|
||||
@@ -113,6 +114,8 @@ const commentsOpen = ref(false)
|
||||
const commentBody = ref('')
|
||||
const replyingTo = ref<PicstagramComment | null>(null)
|
||||
const actionsOpen = ref(false)
|
||||
const postMenuOpened = ref(false)
|
||||
const postMenuTarget = ref<HTMLElement | null>(null)
|
||||
const reportOpen = ref(false)
|
||||
const reportTarget = ref<{
|
||||
id: string
|
||||
@@ -158,6 +161,27 @@ const tabIndex = computed(() =>
|
||||
const currentComposeMedia = computed(
|
||||
() => selectedMedia.value[composePreviewIndex.value] ?? null,
|
||||
)
|
||||
const postMenuItems = computed(() => {
|
||||
const post = actionPost.value
|
||||
if (!post) return []
|
||||
if (post.is_owner) {
|
||||
return [
|
||||
{ id: 'share', label: t('share') },
|
||||
{ id: 'archive', label: t('archive') },
|
||||
{
|
||||
destructive: true,
|
||||
id: 'delete',
|
||||
label: t('deletePost'),
|
||||
separatorBefore: true,
|
||||
},
|
||||
]
|
||||
}
|
||||
return [
|
||||
{ id: 'share', label: t('share') },
|
||||
{ id: 'report', label: t('report'), separatorBefore: true },
|
||||
{ destructive: true, id: 'block', label: t('block') },
|
||||
]
|
||||
})
|
||||
const taggedProfilePosts = computed(() => {
|
||||
const handle = currentProfile.value?.handle.trim().toLowerCase()
|
||||
if (!handle) return []
|
||||
@@ -612,9 +636,39 @@ async function saveProfile(): Promise<void> {
|
||||
notify(t('profileSaved'))
|
||||
}
|
||||
|
||||
function showPostActions(post: PicstagramPost): void {
|
||||
function showPostActions(event: MouseEvent, post: PicstagramPost): void {
|
||||
event.stopPropagation()
|
||||
actionPost.value = post
|
||||
actionsOpen.value = true
|
||||
postMenuTarget.value = event.currentTarget as HTMLElement
|
||||
postMenuOpened.value = true
|
||||
}
|
||||
|
||||
function dismissPostMenu(): void {
|
||||
postMenuOpened.value = false
|
||||
postMenuTarget.value = null
|
||||
}
|
||||
|
||||
function selectPostMenuItem(id: string): void {
|
||||
const post = actionPost.value
|
||||
if (!post) return
|
||||
dismissPostMenu()
|
||||
switch (id) {
|
||||
case 'share':
|
||||
sharePost(post)
|
||||
break
|
||||
case 'archive':
|
||||
archiveSelectedPost()
|
||||
break
|
||||
case 'delete':
|
||||
deleteDialogOpen.value = true
|
||||
break
|
||||
case 'report':
|
||||
startReport('post', post.id, t('post'))
|
||||
break
|
||||
case 'block':
|
||||
blockDialogOpen.value = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function showProfileActions(): void {
|
||||
@@ -979,7 +1033,7 @@ onBeforeUnmount(() => {
|
||||
icon-only
|
||||
class="ps-post-more"
|
||||
:aria-label="t('more')"
|
||||
@click="showPostActions(selectedPost)"
|
||||
@click="showPostActions($event, selectedPost)"
|
||||
>
|
||||
<MoreHorizontal />
|
||||
</SkyLink>
|
||||
@@ -1099,7 +1153,7 @@ onBeforeUnmount(() => {
|
||||
</template>
|
||||
|
||||
<template v-else-if="tab === 'home'">
|
||||
<SkyNavbar :title="t('name')" variant="compact">
|
||||
<SkyNavbar class="ps-home-navbar" :title="t('name')" variant="compact">
|
||||
<template #left>
|
||||
<SkyLink
|
||||
component="button"
|
||||
@@ -1190,7 +1244,7 @@ onBeforeUnmount(() => {
|
||||
icon-only
|
||||
class="ps-post-more"
|
||||
:aria-label="t('more')"
|
||||
@click.stop="showPostActions(post)"
|
||||
@click="showPostActions($event, post)"
|
||||
>
|
||||
<MoreHorizontal />
|
||||
</SkyLink>
|
||||
@@ -2054,40 +2108,44 @@ onBeforeUnmount(() => {
|
||||
</button>
|
||||
</div>
|
||||
<div class="ps-edit-fields">
|
||||
<SkyField
|
||||
v-model="profileDraft.displayName"
|
||||
:label="t('displayName')"
|
||||
outline
|
||||
/><SkyField
|
||||
v-model="profileDraft.handle"
|
||||
:label="t('username')"
|
||||
outline
|
||||
/><SkyField
|
||||
v-model="profileDraft.bio"
|
||||
:label="t('bio')"
|
||||
type="textarea"
|
||||
:rows="6"
|
||||
outline
|
||||
/>
|
||||
<SkyList inset strong class="ps-edit-field-list">
|
||||
<SkyField
|
||||
v-model="profileDraft.displayName"
|
||||
:label="t('displayName')"
|
||||
maxlength="40"
|
||||
/>
|
||||
<SkyField
|
||||
v-model="profileDraft.handle"
|
||||
:label="t('username')"
|
||||
maxlength="24"
|
||||
/>
|
||||
<SkyField
|
||||
v-model="profileDraft.bio"
|
||||
:label="t('bio')"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
maxlength="160"
|
||||
/>
|
||||
</SkyList>
|
||||
<section class="ps-privacy-control">
|
||||
<span class="ps-privacy-control__icon">
|
||||
<LockKeyhole v-if="profileDraft.private" />
|
||||
<UserRound v-else />
|
||||
</span>
|
||||
<div>
|
||||
<strong>{{ t('profileVisibility') }}</strong>
|
||||
<strong>{{
|
||||
t(
|
||||
profileDraft.private
|
||||
? 'privateProfileSetting'
|
||||
: 'publicProfile',
|
||||
)
|
||||
}}</strong>
|
||||
<small>{{ t('privacyHint') }}</small>
|
||||
</div>
|
||||
<SkySegmented strong>
|
||||
<SkySegmentedButton
|
||||
:active="!profileDraft.private"
|
||||
@click="profileDraft.private = false"
|
||||
>
|
||||
<UserRound />{{ t('publicProfile') }}
|
||||
</SkySegmentedButton>
|
||||
<SkySegmentedButton
|
||||
:active="profileDraft.private"
|
||||
@click="profileDraft.private = true"
|
||||
>
|
||||
<LockKeyhole />{{ t('privateProfileSetting') }}
|
||||
</SkySegmentedButton>
|
||||
</SkySegmented>
|
||||
<SkyToggle
|
||||
v-model="profileDraft.private"
|
||||
:aria-label="t('privateProfileSetting')"
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
@@ -2340,32 +2398,25 @@ onBeforeUnmount(() => {
|
||||
</section></SkySheet
|
||||
>
|
||||
|
||||
<SkyDropdown
|
||||
:items="postMenuItems"
|
||||
:label="t('more')"
|
||||
:opened="postMenuOpened"
|
||||
placement="auto"
|
||||
:target="postMenuTarget"
|
||||
@backdropclick="dismissPostMenu"
|
||||
@escape="dismissPostMenu"
|
||||
@positionerror="dismissPostMenu"
|
||||
@select="selectPostMenuItem"
|
||||
/>
|
||||
|
||||
<SkyActionSheet
|
||||
:opened="actionsOpen"
|
||||
:label="t('more')"
|
||||
@backdropclick="actionsOpen = false"
|
||||
@escape="actionsOpen = false"
|
||||
>
|
||||
<SkyActionGroup v-if="actionPost"
|
||||
><SkyActionButton
|
||||
v-if="actionPost.is_owner"
|
||||
@click="archiveSelectedPost"
|
||||
>{{ t('archive') }}</SkyActionButton
|
||||
><SkyActionButton
|
||||
v-if="actionPost.is_owner"
|
||||
@click="((deleteDialogOpen = true), (actionsOpen = false))"
|
||||
>{{ t('deletePost') }}</SkyActionButton
|
||||
><SkyActionButton
|
||||
v-else
|
||||
@click="startReport('post', actionPost.id, t('post'))"
|
||||
>{{ t('report') }}</SkyActionButton
|
||||
><SkyActionButton
|
||||
v-if="!actionPost.is_owner"
|
||||
@click="((blockDialogOpen = true), (actionsOpen = false))"
|
||||
>{{ t('block') }}</SkyActionButton
|
||||
></SkyActionGroup
|
||||
>
|
||||
<SkyActionGroup v-else-if="currentProfile"
|
||||
<SkyActionGroup v-if="currentProfile"
|
||||
><SkyActionButton @click="shareCurrentProfile">{{
|
||||
t('shareProfile')
|
||||
}}</SkyActionButton
|
||||
@@ -2583,6 +2634,11 @@ button {
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.ps-home-navbar :deep(.sky-navbar__inner) {
|
||||
margin-bottom: 2px;
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
|
||||
.ps-stories {
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
@@ -3672,11 +3728,16 @@ button {
|
||||
font-size: 12px;
|
||||
}
|
||||
.ps-edit-fields {
|
||||
padding: 0 var(--sky-page-gutter);
|
||||
padding: 0;
|
||||
}
|
||||
.ps-edit-field-list {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.ps-privacy-control {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 11px;
|
||||
margin: 0 var(--sky-page-gutter);
|
||||
padding: 13px 14px;
|
||||
border: 1px solid var(--sky-hairline);
|
||||
border-radius: var(--sky-radius-card);
|
||||
@@ -3684,23 +3745,30 @@ button {
|
||||
}
|
||||
.ps-privacy-control > div {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
gap: 2px;
|
||||
}
|
||||
.ps-privacy-control__icon {
|
||||
display: grid;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
flex: 0 0 34px;
|
||||
place-items: center;
|
||||
border-radius: 50%;
|
||||
background: var(--ps-accent-soft);
|
||||
color: var(--ps-accent);
|
||||
}
|
||||
.ps-privacy-control small {
|
||||
color: var(--sky-muted);
|
||||
font-size: 11px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
.ps-privacy-control :deep(.sky-segmented-button) {
|
||||
min-width: 0;
|
||||
gap: 5px;
|
||||
}
|
||||
.ps-privacy-control :deep(svg) {
|
||||
.ps-privacy-control__icon svg {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
}
|
||||
.ps-fields :deep(.sky-field),
|
||||
.ps-edit-fields :deep(.sky-field) {
|
||||
.ps-fields :deep(.sky-field) {
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
padding: 12px 14px;
|
||||
@@ -3708,34 +3776,26 @@ button {
|
||||
border-radius: var(--sky-radius-card);
|
||||
background: var(--sky-surface);
|
||||
}
|
||||
.ps-fields :deep(.sky-field__inner),
|
||||
.ps-edit-fields :deep(.sky-field__inner) {
|
||||
.ps-fields :deep(.sky-field__inner) {
|
||||
padding: 0;
|
||||
}
|
||||
.ps-fields :deep(.sky-field__label),
|
||||
.ps-edit-fields :deep(.sky-field__label) {
|
||||
.ps-fields :deep(.sky-field__label) {
|
||||
margin-top: 0;
|
||||
}
|
||||
.ps-fields :deep(.sky-field__label-text),
|
||||
.ps-edit-fields :deep(.sky-field__label-text) {
|
||||
.ps-fields :deep(.sky-field__label-text) {
|
||||
top: auto;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
}
|
||||
.ps-fields :deep(.sky-field__control),
|
||||
.ps-edit-fields :deep(.sky-field__control) {
|
||||
.ps-fields :deep(.sky-field__control) {
|
||||
margin: 0;
|
||||
border-radius: calc(var(--sky-radius-control) + 10px);
|
||||
background: var(--sky-surface-muted);
|
||||
}
|
||||
.ps-fields :deep(.sky-field__border),
|
||||
.ps-edit-fields :deep(.sky-field__border) {
|
||||
.ps-fields :deep(.sky-field__border) {
|
||||
display: none;
|
||||
}
|
||||
.ps-edit-fields :deep(.sky-field__textarea) {
|
||||
padding-inline: 4px;
|
||||
}
|
||||
.ps-compose-fields :deep(.sky-field) {
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
|
||||
Reference in New Issue
Block a user