summaryrefslogtreecommitdiff
path: root/packages/frontend
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2023-04-11 15:51:07 +0900
committerGitHub <noreply@github.com>2023-04-11 15:51:07 +0900
commit75b28d6782d9e1a37dd40444fbccffdf4331737a (patch)
tree1b6a1b33ca19a579d7d24d92fef4bfb05ae1e86e /packages/frontend
parentMerge pull request #10543 from misskey-dev/develop (diff)
parentfix(client): noPaging: true with gallery/featured (diff)
downloadmisskey-75b28d6782d9e1a37dd40444fbccffdf4331737a.tar.gz
misskey-75b28d6782d9e1a37dd40444fbccffdf4331737a.tar.bz2
misskey-75b28d6782d9e1a37dd40444fbccffdf4331737a.zip
Merge pull request #10578 from misskey-dev/develop
Release: 13.11.2
Diffstat (limited to 'packages/frontend')
-rw-r--r--packages/frontend/.storybook/fakes.ts1
-rw-r--r--packages/frontend/.storybook/generate.tsx1
-rw-r--r--packages/frontend/src/components/MkChannelList.vue31
-rw-r--r--packages/frontend/src/components/MkContainer.vue1
-rw-r--r--packages/frontend/src/components/MkEmojiPicker.vue5
-rw-r--r--packages/frontend/src/components/MkPostForm.vue24
-rw-r--r--packages/frontend/src/components/global/MkAd.vue2
-rw-r--r--packages/frontend/src/pages/channels.vue62
-rw-r--r--packages/frontend/src/pages/gallery/index.vue2
-rw-r--r--packages/frontend/src/pages/settings/apps.vue45
-rw-r--r--packages/frontend/src/pages/settings/general.vue2
-rw-r--r--packages/frontend/src/pages/settings/preferences-backups.vue2
-rw-r--r--packages/frontend/src/pages/settings/webhook.vue29
-rw-r--r--packages/frontend/src/pages/user/home.stories.impl.ts74
-rw-r--r--packages/frontend/src/store.ts4
-rw-r--r--packages/frontend/src/ui/_common_/sw-inject.ts21
-rw-r--r--packages/frontend/src/ui/classic.vue3
-rw-r--r--packages/frontend/src/ui/universal.vue4
-rw-r--r--packages/frontend/src/ui/universal.widgets.vue6
19 files changed, 262 insertions, 57 deletions
diff --git a/packages/frontend/.storybook/fakes.ts b/packages/frontend/.storybook/fakes.ts
index 23b82a8ac5..5fd21cdf0a 100644
--- a/packages/frontend/.storybook/fakes.ts
+++ b/packages/frontend/.storybook/fakes.ts
@@ -77,6 +77,7 @@ export function userDetailed(id = 'someuserid', username = 'miskist', host = 'mi
createdAt: '2016-12-28T22:49:51.000Z',
description: 'I am a cool user!',
ffVisibility: 'public',
+ roles: [],
fields: [
{
name: 'Website',
diff --git a/packages/frontend/.storybook/generate.tsx b/packages/frontend/.storybook/generate.tsx
index b3bbeeb51c..dd40bac2cc 100644
--- a/packages/frontend/.storybook/generate.tsx
+++ b/packages/frontend/.storybook/generate.tsx
@@ -398,6 +398,7 @@ function toStories(component: string): string {
Promise.all([
glob('src/components/global/*.vue'),
glob('src/components/MkGalleryPostPreview.vue'),
+ glob('src/pages/user/home.vue'),
])
.then((globs) => globs.flat())
.then((components) => Promise.all(components.map((component) => {
diff --git a/packages/frontend/src/components/MkChannelList.vue b/packages/frontend/src/components/MkChannelList.vue
new file mode 100644
index 0000000000..408eab7399
--- /dev/null
+++ b/packages/frontend/src/components/MkChannelList.vue
@@ -0,0 +1,31 @@
+<template>
+<MkPagination :pagination="pagination">
+ <template #empty>
+ <div class="_fullinfo">
+ <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
+ <div>{{ i18n.ts.notFound }}</div>
+ </div>
+ </template>
+
+ <template #default="{ items }">
+ <MkChannelPreview v-for="item in items" :key="item.id" class="_margin" :channel="extractor(item)"/>
+ </template>
+</MkPagination>
+</template>
+
+<script lang="ts" setup>
+import MkChannelPreview from '@/components/MkChannelPreview.vue';
+import MkPagination, { Paging } from '@/components/MkPagination.vue';
+import { i18n } from '@/i18n';
+
+const props = withDefaults(defineProps<{
+ pagination: Paging;
+ noGap?: boolean;
+ extractor?: (item: any) => any;
+}>(), {
+ extractor: (item) => item,
+});
+</script>
+
+<style lang="scss" scoped>
+</style>
diff --git a/packages/frontend/src/components/MkContainer.vue b/packages/frontend/src/components/MkContainer.vue
index 1834224b8d..a6372b7b6f 100644
--- a/packages/frontend/src/components/MkContainer.vue
+++ b/packages/frontend/src/components/MkContainer.vue
@@ -82,6 +82,7 @@ export default defineComponent({
omitted: null,
ignoreOmit: false,
defaultStore,
+ i18n,
};
},
mounted() {
diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue
index 0ae182ce32..9eaf16374b 100644
--- a/packages/frontend/src/components/MkEmojiPicker.vue
+++ b/packages/frontend/src/components/MkEmojiPicker.vue
@@ -439,7 +439,6 @@ defineExpose({
&.asDrawer {
width: 100% !important;
- padding: 12px 0 max(env(safe-area-inset-bottom, 0px), 12px) 0;
> .emojis {
::v-deep(section) {
@@ -498,6 +497,10 @@ defineExpose({
background: transparent;
color: var(--fg);
+ &:not(:focus):not(.filled) {
+ margin-bottom: env(safe-area-inset-bottom, 0px);
+ }
+
&:not(.filled) {
order: 1;
z-index: 2;
diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue
index 10cb7d96cc..42a3748d9a 100644
--- a/packages/frontend/src/components/MkPostForm.vue
+++ b/packages/frontend/src/components/MkPostForm.vue
@@ -1124,16 +1124,16 @@ defineExpose({
display: grid;
grid-auto-flow: row;
grid-template-columns: repeat(auto-fill, minmax(42px, 1fr));
- grid-auto-rows: 46px;
+ grid-auto-rows: 40px;
}
.footerRight {
- flex: 0.3;
+ flex: 0;
margin-left: auto;
display: grid;
grid-auto-flow: row;
grid-template-columns: repeat(auto-fill, minmax(42px, 1fr));
- grid-auto-rows: 46px;
+ grid-auto-rows: 40px;
direction: rtl;
}
@@ -1198,13 +1198,21 @@ defineExpose({
}
}
-@container (max-width: 330px) {
- .headerRight {
- gap: 0;
+@container (max-width: 350px) {
+ .footer {
+ font-size: 0.9em;
}
- .footer {
- font-size: 14px;
+ .footerLeft {
+ grid-template-columns: repeat(auto-fill, minmax(38px, 1fr));
+ }
+
+ .footerRight {
+ grid-template-columns: repeat(auto-fill, minmax(38px, 1fr));
+ }
+
+ .headerRight {
+ gap: 0;
}
}
</style>
diff --git a/packages/frontend/src/components/global/MkAd.vue b/packages/frontend/src/components/global/MkAd.vue
index 5799f99d5f..aa975600f0 100644
--- a/packages/frontend/src/components/global/MkAd.vue
+++ b/packages/frontend/src/components/global/MkAd.vue
@@ -83,7 +83,7 @@ const choseAd = (): Ad | null => {
};
const chosen = ref(choseAd());
-const shouldHide = $ref($i && $i.policies.canHideAds && (props.specify == null));
+const shouldHide = $ref(!defaultStore.state.forceShowAds && $i && $i.policies.canHideAds && (props.specify == null));
function reduceFrequency(): void {
if (chosen.value == null) return;
diff --git a/packages/frontend/src/pages/channels.vue b/packages/frontend/src/pages/channels.vue
index 3a5aa00c5b..bc6a6e4952 100644
--- a/packages/frontend/src/pages/channels.vue
+++ b/packages/frontend/src/pages/channels.vue
@@ -2,6 +2,23 @@
<MkStickyContainer>
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
<MkSpacer :content-max="700">
+ <div v-if="tab === 'search'">
+ <div class="_gaps">
+ <MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search">
+ <template #prefix><i class="ti ti-search"></i></template>
+ </MkInput>
+ <MkRadios v-model="searchType" @update:model-value="search()">
+ <option value="nameAndDescription">{{ i18n.ts._channel.nameAndDescription }}</option>
+ <option value="nameOnly">{{ i18n.ts._channel.nameOnly }}</option>
+ </MkRadios>
+ <MkButton large primary gradate rounded @click="search">{{ i18n.ts.search }}</MkButton>
+ </div>
+
+ <MkFoldableSection v-if="channelPagination">
+ <template #header>{{ i18n.ts.searchResult }}</template>
+ <MkChannelList :key="key" :pagination="channelPagination"/>
+ </MkFoldableSection>
+ </div>
<div v-if="tab === 'featured'">
<MkPagination v-slot="{items}" :pagination="featuredPagination">
<MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/>
@@ -28,17 +45,35 @@
</template>
<script lang="ts" setup>
-import { computed } from 'vue';
+import { computed, onMounted } from 'vue';
import MkChannelPreview from '@/components/MkChannelPreview.vue';
+import MkChannelList from '@/components/MkChannelList.vue';
import MkPagination from '@/components/MkPagination.vue';
+import MkInput from '@/components/MkInput.vue';
+import MkRadios from '@/components/MkRadios.vue';
import MkButton from '@/components/MkButton.vue';
+import MkFoldableSection from '@/components/MkFoldableSection.vue';
import { useRouter } from '@/router';
import { definePageMetadata } from '@/scripts/page-metadata';
import { i18n } from '@/i18n';
const router = useRouter();
-let tab = $ref('featured');
+const props = defineProps<{
+ query: string;
+ type?: string;
+}>();
+
+let key = $ref('');
+let tab = $ref('search');
+let searchQuery = $ref('');
+let searchType = $ref('nameAndDescription');
+let channelPagination = $ref();
+
+onMounted(() => {
+ searchQuery = props.query ?? '';
+ searchType = props.type ?? 'nameAndDescription';
+});
const featuredPagination = {
endpoint: 'channels/featured' as const,
@@ -58,6 +93,25 @@ const ownedPagination = {
limit: 10,
};
+async function search() {
+ const query = searchQuery.toString().trim();
+
+ if (query == null || query === '') return;
+
+ const type = searchType.toString().trim();
+
+ channelPagination = {
+ endpoint: 'channels/search',
+ limit: 10,
+ params: {
+ query: searchQuery,
+ type: type,
+ },
+ };
+
+ key = query + type;
+}
+
function create() {
router.push('/channels/new');
}
@@ -69,6 +123,10 @@ const headerActions = $computed(() => [{
}]);
const headerTabs = $computed(() => [{
+ key: 'search',
+ title: i18n.ts.search,
+ icon: 'ti ti-search',
+}, {
key: 'featured',
title: i18n.ts._channel.featured,
icon: 'ti ti-comet',
diff --git a/packages/frontend/src/pages/gallery/index.vue b/packages/frontend/src/pages/gallery/index.vue
index de8f448da1..fc9cc7ae9e 100644
--- a/packages/frontend/src/pages/gallery/index.vue
+++ b/packages/frontend/src/pages/gallery/index.vue
@@ -66,7 +66,7 @@ const recentPostsPagination = {
};
const popularPostsPagination = {
endpoint: 'gallery/featured' as const,
- limit: 5,
+ noPaging: true,
};
const myPostsPagination = {
endpoint: 'i/gallery/posts' as const,
diff --git a/packages/frontend/src/pages/settings/apps.vue b/packages/frontend/src/pages/settings/apps.vue
index 955d812154..599d6329e2 100644
--- a/packages/frontend/src/pages/settings/apps.vue
+++ b/packages/frontend/src/pages/settings/apps.vue
@@ -8,27 +8,29 @@
</div>
</template>
<template #default="{items}">
- <div v-for="token in items" :key="token.id" class="_panel bfomjevm">
- <img v-if="token.iconUrl" class="icon" :src="token.iconUrl" alt=""/>
- <div class="body">
- <div class="name">{{ token.name }}</div>
- <div class="description">{{ token.description }}</div>
- <MkKeyValue oneline>
- <template #key>{{ i18n.ts.installedDate }}</template>
- <template #value><MkTime :time="token.createdAt"/></template>
- </MkKeyValue>
- <MkKeyValue oneline>
- <template #key>{{ i18n.ts.lastUsedDate }}</template>
- <template #value><MkTime :time="token.lastUsedAt"/></template>
- </MkKeyValue>
- <details>
- <summary>{{ i18n.ts.details }}</summary>
- <ul>
- <li v-for="p in token.permission" :key="p">{{ i18n.t(`_permissions.${p}`) }}</li>
- </ul>
- </details>
- <div class="actions">
- <MkButton inline danger @click="revoke(token)"><i class="ti ti-trash"></i></MkButton>
+ <div class="_gaps">
+ <div v-for="token in items" :key="token.id" class="_panel bfomjevm">
+ <img v-if="token.iconUrl" class="icon" :src="token.iconUrl" alt=""/>
+ <div class="body">
+ <div class="name">{{ token.name }}</div>
+ <div class="description">{{ token.description }}</div>
+ <MkKeyValue oneline>
+ <template #key>{{ i18n.ts.installedDate }}</template>
+ <template #value><MkTime :time="token.createdAt"/></template>
+ </MkKeyValue>
+ <MkKeyValue oneline>
+ <template #key>{{ i18n.ts.lastUsedDate }}</template>
+ <template #value><MkTime :time="token.lastUsedAt"/></template>
+ </MkKeyValue>
+ <details>
+ <summary>{{ i18n.ts.details }}</summary>
+ <ul>
+ <li v-for="p in token.permission" :key="p">{{ i18n.t(`_permissions.${p}`) }}</li>
+ </ul>
+ </details>
+ <div class="actions">
+ <MkButton inline danger @click="revoke(token)"><i class="ti ti-trash"></i></MkButton>
+ </div>
</div>
</div>
</div>
@@ -51,6 +53,7 @@ const list = ref<any>(null);
const pagination = {
endpoint: 'i/apps' as const,
limit: 100,
+ noPaging: true,
params: {
sort: '+lastUsedAt',
},
diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue
index f88e934e1d..904fd3f952 100644
--- a/packages/frontend/src/pages/settings/general.vue
+++ b/packages/frontend/src/pages/settings/general.vue
@@ -61,6 +61,7 @@
<MkSwitch v-model="squareAvatars">{{ i18n.ts.squareAvatars }}</MkSwitch>
<MkSwitch v-model="useSystemFont">{{ i18n.ts.useSystemFont }}</MkSwitch>
<MkSwitch v-model="disableDrawer">{{ i18n.ts.disableDrawer }}</MkSwitch>
+ <MkSwitch v-model="forceShowAds">{{ i18n.ts.forceShowAds }}</MkSwitch>
</div>
<div>
<MkRadios v-model="emojiStyle">
@@ -157,6 +158,7 @@ const advancedMfm = computed(defaultStore.makeGetterSetter('advancedMfm'));
const emojiStyle = computed(defaultStore.makeGetterSetter('emojiStyle'));
const disableDrawer = computed(defaultStore.makeGetterSetter('disableDrawer'));
const disableShowingAnimatedImages = computed(defaultStore.makeGetterSetter('disableShowingAnimatedImages'));
+const forceShowAds = computed(defaultStore.makeGetterSetter('forceShowAds'));
const loadRawImages = computed(defaultStore.makeGetterSetter('loadRawImages'));
const imageNewTab = computed(defaultStore.makeGetterSetter('imageNewTab'));
const nsfw = computed(defaultStore.makeGetterSetter('nsfw'));
diff --git a/packages/frontend/src/pages/settings/preferences-backups.vue b/packages/frontend/src/pages/settings/preferences-backups.vue
index 28e39236f7..092b9a9cc8 100644
--- a/packages/frontend/src/pages/settings/preferences-backups.vue
+++ b/packages/frontend/src/pages/settings/preferences-backups.vue
@@ -399,7 +399,7 @@ function menu(ev: MouseEvent, profileId: string) {
icon: 'ti ti-device-floppy',
action: () => save(profileId),
}, null, {
- text: ts._preferencesBackups.delete,
+ text: ts.delete,
icon: 'ti ti-trash',
action: () => deleteProfile(profileId),
danger: true,
diff --git a/packages/frontend/src/pages/settings/webhook.vue b/packages/frontend/src/pages/settings/webhook.vue
index bc729ab871..4f702758f4 100644
--- a/packages/frontend/src/pages/settings/webhook.vue
+++ b/packages/frontend/src/pages/settings/webhook.vue
@@ -7,18 +7,20 @@
<FormSection>
<MkPagination :pagination="pagination">
<template #default="{items}">
- <FormLink v-for="webhook in items" :key="webhook.id" :to="`/settings/webhook/edit/${webhook.id}`" class="_margin">
- <template #icon>
- <i v-if="webhook.active === false" class="ti ti-player-pause"></i>
- <i v-else-if="webhook.latestStatus === null" class="ti ti-circle"></i>
- <i v-else-if="[200, 201, 204].includes(webhook.latestStatus)" class="ti ti-check" :style="{ color: 'var(--success)' }"></i>
- <i v-else class="ti ti-alert-triangle" :style="{ color: 'var(--error)' }"></i>
- </template>
- {{ webhook.name || webhook.url }}
- <template #suffix>
- <MkTime v-if="webhook.latestSentAt" :time="webhook.latestSentAt"></MkTime>
- </template>
- </FormLink>
+ <div class="_gaps">
+ <FormLink v-for="webhook in items" :key="webhook.id" :to="`/settings/webhook/edit/${webhook.id}`">
+ <template #icon>
+ <i v-if="webhook.active === false" class="ti ti-player-pause"></i>
+ <i v-else-if="webhook.latestStatus === null" class="ti ti-circle"></i>
+ <i v-else-if="[200, 201, 204].includes(webhook.latestStatus)" class="ti ti-check" :style="{ color: 'var(--success)' }"></i>
+ <i v-else class="ti ti-alert-triangle" :style="{ color: 'var(--error)' }"></i>
+ </template>
+ {{ webhook.name || webhook.url }}
+ <template #suffix>
+ <MkTime v-if="webhook.latestSentAt" :time="webhook.latestSentAt"></MkTime>
+ </template>
+ </FormLink>
+ </div>
</template>
</MkPagination>
</FormSection>
@@ -35,7 +37,8 @@ import { i18n } from '@/i18n';
const pagination = {
endpoint: 'i/webhooks/list' as const,
- limit: 10,
+ limit: 100,
+ noPaging: true,
};
const headerActions = $computed(() => []);
diff --git a/packages/frontend/src/pages/user/home.stories.impl.ts b/packages/frontend/src/pages/user/home.stories.impl.ts
new file mode 100644
index 0000000000..50da0c6991
--- /dev/null
+++ b/packages/frontend/src/pages/user/home.stories.impl.ts
@@ -0,0 +1,74 @@
+/* eslint-disable @typescript-eslint/explicit-function-return-type */
+import { StoryObj } from '@storybook/vue3';
+import { rest } from 'msw';
+import { userDetailed } from '../../../.storybook/fakes';
+import { commonHandlers } from '../../../.storybook/mocks';
+import home_ from './home.vue';
+export const Default = {
+ render(args) {
+ return {
+ components: {
+ home_,
+ },
+ setup() {
+ return {
+ args,
+ };
+ },
+ computed: {
+ props() {
+ return {
+ ...this.args,
+ };
+ },
+ },
+ template: '<home_ v-bind="props" />',
+ };
+ },
+ args: {
+ user: userDetailed(),
+ disableNotes: false,
+ },
+ parameters: {
+ layout: 'fullscreen',
+ msw: {
+ handlers: [
+ ...commonHandlers,
+ rest.post('/api/users/notes', (req, res, ctx) => {
+ return res(ctx.json([]));
+ }),
+ rest.get('/api/charts/user/notes', (req, res, ctx) => {
+ const length = Math.max(Math.min(parseInt(req.url.searchParams.get('limit') ?? '30', 10), 1), 300);
+ return res(ctx.json({
+ total: Array.from({ length }, () => 0),
+ inc: Array.from({ length }, () => 0),
+ dec: Array.from({ length }, () => 0),
+ diffs: {
+ normal: Array.from({ length }, () => 0),
+ reply: Array.from({ length }, () => 0),
+ renote: Array.from({ length }, () => 0),
+ withFile: Array.from({ length }, () => 0),
+ },
+ }));
+ }),
+ rest.get('/api/charts/user/pv', (req, res, ctx) => {
+ const length = Math.max(Math.min(parseInt(req.url.searchParams.get('limit') ?? '30', 10), 1), 300);
+ return res(ctx.json({
+ upv: {
+ user: Array.from({ length }, () => 0),
+ visitor: Array.from({ length }, () => 0),
+ },
+ pv: {
+ user: Array.from({ length }, () => 0),
+ visitor: Array.from({ length }, () => 0),
+ },
+ }));
+ }),
+ ],
+ },
+ chromatic: {
+ // `XActivity` is not compatible with Chromatic for now
+ disableSnapshot: true,
+ },
+ },
+} satisfies StoryObj<typeof home_>;
diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts
index e5558829d4..0728fc84e5 100644
--- a/packages/frontend/src/store.ts
+++ b/packages/frontend/src/store.ts
@@ -298,6 +298,10 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'device',
default: false,
},
+ forceShowAds: {
+ where: 'device',
+ default: false,
+ },
aiChanMode: {
where: 'device',
default: false,
diff --git a/packages/frontend/src/ui/_common_/sw-inject.ts b/packages/frontend/src/ui/_common_/sw-inject.ts
index a92a06bd3e..c97e2b567b 100644
--- a/packages/frontend/src/ui/_common_/sw-inject.ts
+++ b/packages/frontend/src/ui/_common_/sw-inject.ts
@@ -1,17 +1,18 @@
-import { post } from '@/os';
+import { api, post } from '@/os';
import { $i, login } from '@/account';
import { getAccountFromId } from '@/scripts/get-account-from-id';
import { mainRouter } from '@/router';
+import { deepClone } from '@/scripts/clone';
export function swInject() {
- navigator.serviceWorker.addEventListener('message', ev => {
+ navigator.serviceWorker.addEventListener('message', async ev => {
if (_DEV_) {
console.log('sw msg', ev.data);
}
if (ev.data.type !== 'order') return;
- if (ev.data.loginId !== $i?.id) {
+ if (ev.data.loginId && ev.data.loginId !== $i?.id) {
return getAccountFromId(ev.data.loginId).then(account => {
if (!account) return;
return login(account.token, ev.data.url);
@@ -19,8 +20,18 @@ export function swInject() {
}
switch (ev.data.order) {
- case 'post':
- return post(ev.data.options);
+ case 'post': {
+ const props = deepClone(ev.data.options);
+ // プッシュ通知から来たreply,renoteはtruncateBodyが通されているため、
+ // 完全なノートを取得しなおす
+ if (props.reply) {
+ props.reply = await api('notes/show', { noteId: props.reply.id });
+ }
+ if (props.renote) {
+ props.renote = await api('notes/show', { noteId: props.renote.id });
+ }
+ return post(props);
+ }
case 'push':
if (mainRouter.currentRoute.value.path === ev.data.url) {
return window.scroll({ top: 0, behavior: 'smooth' });
diff --git a/packages/frontend/src/ui/classic.vue b/packages/frontend/src/ui/classic.vue
index 4838272a9e..792c1ccc5e 100644
--- a/packages/frontend/src/ui/classic.vue
+++ b/packages/frontend/src/ui/classic.vue
@@ -250,6 +250,7 @@ onMounted(() => {
> .widgets {
//--panelBorder: none;
width: 300px;
+ padding-bottom: calc(var(--margin) + env(safe-area-inset-bottom, 0px));
@media (max-width: $widgets-hide-threshold) {
display: none;
@@ -304,7 +305,7 @@ onMounted(() => {
right: 0;
z-index: 1001;
height: 100dvh;
- padding: var(--margin);
+ padding: var(--margin) var(--margin) calc(var(--margin) + env(safe-area-inset-bottom, 0px));
box-sizing: border-box;
overflow: auto;
background: var(--bg);
diff --git a/packages/frontend/src/ui/universal.vue b/packages/frontend/src/ui/universal.vue
index ab3d01532b..2462967515 100644
--- a/packages/frontend/src/ui/universal.vue
+++ b/packages/frontend/src/ui/universal.vue
@@ -296,7 +296,7 @@ $widgets-hide-threshold: 1090px;
}
.widgets {
- padding: 0 var(--margin);
+ padding: 0 var(--margin) calc(var(--margin) + env(safe-area-inset-bottom, 0px));
border-left: solid 0.5px var(--divider);
background: var(--bg);
@@ -329,7 +329,7 @@ $widgets-hide-threshold: 1090px;
right: 0;
z-index: 1001;
height: 100dvh;
- padding: var(--margin) !important;
+ padding: var(--margin) var(--margin) calc(var(--margin) + env(safe-area-inset-bottom, 0px)) !important;
box-sizing: border-box;
overflow: auto;
overscroll-behavior: contain;
diff --git a/packages/frontend/src/ui/universal.widgets.vue b/packages/frontend/src/ui/universal.widgets.vue
index 2e557d30ce..d11649c603 100644
--- a/packages/frontend/src/ui/universal.widgets.vue
+++ b/packages/frontend/src/ui/universal.widgets.vue
@@ -3,7 +3,7 @@
<XWidgets :class="$style.widgets" :edit="editMode" :widgets="widgets" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="editMode = false"/>
<button v-if="editMode" class="_textButton" style="font-size: 0.9em;" @click="editMode = false"><i class="ti ti-check"></i> {{ i18n.ts.editWidgetsExit }}</button>
- <button v-else class="_textButton mk-widget-edit" style="font-size: 0.9em;" @click="editMode = true"><i class="ti ti-pencil"></i> {{ i18n.ts.editWidgets }}</button>
+ <button v-else class="_textButton mk-widget-edit" :class="$style.edit" style="font-size: 0.9em;" @click="editMode = true"><i class="ti ti-pencil"></i> {{ i18n.ts.editWidgets }}</button>
</div>
</template>
@@ -91,4 +91,8 @@ function updateWidgets(thisWidgets) {
.widgets {
width: 300px;
}
+
+.edit {
+ width: 100%;
+}
</style>