diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2022-04-02 15:34:03 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2022-04-02 15:34:03 +0900 |
| commit | 2375359d129b63988b0658f735e1d9c014c10d71 (patch) | |
| tree | c8b233d00e4b3cbb41b0d211845bdb0cca803267 /packages/client/src | |
| parent | Merge branch 'develop' (diff) | |
| parent | 12.109.0 (diff) | |
| download | misskey-2375359d129b63988b0658f735e1d9c014c10d71.tar.gz misskey-2375359d129b63988b0658f735e1d9c014c10d71.tar.bz2 misskey-2375359d129b63988b0658f735e1d9c014c10d71.zip | |
Merge branch 'develop'
Diffstat (limited to 'packages/client/src')
44 files changed, 899 insertions, 760 deletions
diff --git a/packages/client/src/account.ts b/packages/client/src/account.ts index 4aeceeccab..4772c0baa5 100644 --- a/packages/client/src/account.ts +++ b/packages/client/src/account.ts @@ -2,7 +2,7 @@ import { del, get, set } from '@/scripts/idb-proxy'; import { reactive } from 'vue'; import * as misskey from 'misskey-js'; import { apiUrl } from '@/config'; -import { waiting, api, popup, popupMenu, success } from '@/os'; +import { waiting, api, popup, popupMenu, success, alert } from '@/os'; import { unisonReload, reloadChannel } from '@/scripts/unison-reload'; import { showSuspendedDialog } from './scripts/show-suspended-dialog'; import { i18n } from './i18n'; @@ -89,7 +89,11 @@ function fetchAccount(token): Promise<Account> { signout(); }); } else { - signout(); + alert({ + type: 'error', + title: i18n.ts.failedToFetchAccountInformation, + text: JSON.stringify(res.error), + }); } } else { res.token = token; @@ -116,6 +120,7 @@ export async function login(token: Account['token'], redirect?: string) { if (_DEV_) console.log('logging as token ', token); const me = await fetchAccount(token); localStorage.setItem('account', JSON.stringify(me)); + document.cookie = `token=${token}; path=/; max-age=31536000`; // bull dashboardの認証とかで使う await addAccount(me.id, token); if (redirect) { diff --git a/packages/client/src/components/form/link.vue b/packages/client/src/components/form/link.vue index 3eb74425b0..b74e9bd684 100644 --- a/packages/client/src/components/form/link.vue +++ b/packages/client/src/components/form/link.vue @@ -80,7 +80,7 @@ export default defineComponent({ margin-right: 0.75em; flex-shrink: 0; text-align: center; - opacity: 0.8; + color: var(--fgTransparentWeak); &:empty { display: none; diff --git a/packages/client/src/components/global/url.vue b/packages/client/src/components/global/url.vue index 56a8c3453a..55f6c5d5f9 100644 --- a/packages/client/src/components/global/url.vue +++ b/packages/client/src/components/global/url.vue @@ -24,6 +24,14 @@ import { url as local } from '@/config'; import * as os from '@/os'; import { useTooltip } from '@/scripts/use-tooltip'; +function safeURIDecode(str: string) { + try { + return decodeURIComponent(str); + } catch { + return str; + } +} + export default defineComponent({ props: { url: { @@ -54,9 +62,9 @@ export default defineComponent({ schema: url.protocol, hostname: decodePunycode(url.hostname), port: url.port, - pathname: decodeURIComponent(url.pathname), - query: decodeURIComponent(url.search), - hash: decodeURIComponent(url.hash), + pathname: safeURIDecode(url.pathname), + query: safeURIDecode(url.search), + hash: safeURIDecode(url.hash), self: self, attr: self ? 'to' : 'href', target: self ? null : '_blank', diff --git a/packages/client/src/components/launch-pad.vue b/packages/client/src/components/launch-pad.vue index 4fe36bfefc..ffefc1b085 100644 --- a/packages/client/src/components/launch-pad.vue +++ b/packages/client/src/components/launch-pad.vue @@ -1,5 +1,5 @@ <template> -<MkModal ref="modal" v-slot="{ type, maxHeight }" :prefer-type="preferedModalType" :anchor="{ x: 'right', y: 'center' }" :transparent-bg="true" :src="src" @click="modal.close()" @closed="emit('closed')"> +<MkModal ref="modal" v-slot="{ type, maxHeight }" :prefer-type="preferedModalType" :anchor="anchor" :transparent-bg="true" :src="src" @click="modal.close()" @closed="emit('closed')"> <div class="szkkfdyq _popup _shadow" :class="{ asDrawer: type === 'drawer' }" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : '' }"> <div class="main"> <template v-for="item in items"> @@ -44,7 +44,9 @@ import { deviceKind } from '@/scripts/device-kind'; const props = withDefaults(defineProps<{ src?: HTMLElement; + anchor?: { x: string; y: string; }; }>(), { + anchor: () => ({ x: 'right', y: 'center' }), }); const emit = defineEmits<{ diff --git a/packages/client/src/components/media-list.vue b/packages/client/src/components/media-list.vue index 532627edbd..7e330575e1 100644 --- a/packages/client/src/components/media-list.vue +++ b/packages/client/src/components/media-list.vue @@ -15,9 +15,9 @@ <script lang="ts" setup> import { onMounted, ref } from 'vue'; import * as misskey from 'misskey-js'; -import PhotoSwipeLightbox from 'photoswipe/dist/photoswipe-lightbox.esm.js'; -import PhotoSwipe from 'photoswipe/dist/photoswipe.esm.js'; -import 'photoswipe/dist/photoswipe.css'; +import PhotoSwipeLightbox from 'photoswipe/lightbox'; +import PhotoSwipe from 'photoswipe'; +import 'photoswipe/style.css'; import XBanner from './media-banner.vue'; import XImage from './media-image.vue'; import XVideo from './media-video.vue'; diff --git a/packages/client/src/components/notifications.vue b/packages/client/src/components/notifications.vue index c3753076ac..d522503a14 100644 --- a/packages/client/src/components/notifications.vue +++ b/packages/client/src/components/notifications.vue @@ -17,7 +17,7 @@ </template> <script lang="ts" setup> -import { defineComponent, PropType, markRaw, onUnmounted, onMounted, computed, ref } from 'vue'; +import { defineComponent, markRaw, onUnmounted, onMounted, computed, ref } from 'vue'; import { notificationTypes } from 'misskey-js'; import MkPagination from '@/components/ui/pagination.vue'; import { Paging } from '@/components/ui/pagination.vue'; @@ -29,7 +29,7 @@ import { stream } from '@/stream'; import { $i } from '@/account'; const props = defineProps<{ - includeTypes?: PropType<typeof notificationTypes[number][]>; + includeTypes?: typeof notificationTypes[number][]; unreadOnly?: boolean; }>(); diff --git a/packages/client/src/components/ui/modal.vue b/packages/client/src/components/ui/modal.vue index ba29575dc4..1e4159055e 100644 --- a/packages/client/src/components/ui/modal.vue +++ b/packages/client/src/components/ui/modal.vue @@ -39,7 +39,7 @@ const props = withDefaults(defineProps<{ }>(), { manualShowing: null, src: null, - anchor: { x: 'center', y: 'bottom' }, + anchor: () => ({ x: 'center', y: 'bottom' }), preferType: 'auto', zPriority: 'low', noOverlap: true, @@ -106,7 +106,7 @@ const align = () => { const popover = content.value!; if (popover == null) return; - const rect = props.src.getBoundingClientRect(); + const srcRect = props.src.getBoundingClientRect(); const width = popover.offsetWidth; const height = popover.offsetHeight; @@ -114,8 +114,8 @@ const align = () => { let left; let top; - const x = rect.left + (fixed.value ? 0 : window.pageXOffset); - const y = rect.top + (fixed.value ? 0 : window.pageYOffset); + const x = srcRect.left + (fixed.value ? 0 : window.pageXOffset); + const y = srcRect.top + (fixed.value ? 0 : window.pageYOffset); if (props.anchor.x === 'center') { left = x + (props.src.offsetWidth / 2) - (width / 2); @@ -140,7 +140,7 @@ const align = () => { } const underSpace = (window.innerHeight - MARGIN) - top; - const upperSpace = (rect.top - MARGIN); + const upperSpace = (srcRect.top - MARGIN); // 画面から縦にはみ出る場合 if (top + height > (window.innerHeight - MARGIN)) { @@ -164,7 +164,7 @@ const align = () => { } const underSpace = (window.innerHeight - MARGIN) - (top - window.pageYOffset); - const upperSpace = (rect.top - MARGIN); + const upperSpace = (srcRect.top - MARGIN); // 画面から縦にはみ出る場合 if (top + height - window.pageYOffset > (window.innerHeight - MARGIN)) { @@ -194,16 +194,16 @@ const align = () => { let transformOriginX = 'center'; let transformOriginY = 'center'; - if (top > rect.top + (fixed.value ? 0 : window.pageYOffset)) { + if (top >= srcRect.top + props.src.offsetHeight + (fixed.value ? 0 : window.pageYOffset)) { transformOriginY = 'top'; - } else if ((top + height) <= rect.top + (fixed.value ? 0 : window.pageYOffset)) { + } else if ((top + height) <= srcRect.top + (fixed.value ? 0 : window.pageYOffset)) { transformOriginY = 'bottom'; } - if (left > rect.left + (fixed.value ? 0 : window.pageXOffset)) { - transformOriginY = 'left'; - } else if ((left + width) <= rect.left + (fixed.value ? 0 : window.pageXOffset)) { - transformOriginY = 'right'; + if (left >= srcRect.left + props.src.offsetWidth + (fixed.value ? 0 : window.pageXOffset)) { + transformOriginX = 'left'; + } else if ((left + width) <= srcRect.left + (fixed.value ? 0 : window.pageXOffset)) { + transformOriginX = 'right'; } transformOrigin.value = `${transformOriginX} ${transformOriginY}`; diff --git a/packages/client/src/os.ts b/packages/client/src/os.ts index 5f1fb1ef96..43c110555f 100644 --- a/packages/client/src/os.ts +++ b/packages/client/src/os.ts @@ -293,23 +293,25 @@ export function inputDate(props: { }); } -export function select(props: { +export function select<C extends any = any>(props: { title?: string | null; text?: string | null; default?: string | null; - items?: { - value: string; +} & ({ + items: { + value: C; text: string; }[]; - groupedItems?: { +} | { + groupedItems: { label: string; items: { - value: string; + value: C; text: string; }[]; }[]; -}): Promise<{ canceled: true; result: undefined; } | { - canceled: false; result: string; +})): Promise<{ canceled: true; result: undefined; } | { + canceled: false; result: C; }> { return new Promise((resolve, reject) => { popup(import('@/components/dialog.vue'), { diff --git a/packages/client/src/pages/admin/bot-protection.vue b/packages/client/src/pages/admin/bot-protection.vue index 82ab155317..5e0cdd96a5 100644 --- a/packages/client/src/pages/admin/bot-protection.vue +++ b/packages/client/src/pages/admin/bot-protection.vue @@ -84,7 +84,7 @@ export default defineComponent({ methods: { async init() { - const meta = await os.api('meta', { detail: true }); + const meta = await os.api('admin/meta'); this.enableHcaptcha = meta.enableHcaptcha; this.hcaptchaSiteKey = meta.hcaptchaSiteKey; this.hcaptchaSecretKey = meta.hcaptchaSecretKey; diff --git a/packages/client/src/pages/admin/email-settings.vue b/packages/client/src/pages/admin/email-settings.vue index 6491a453ab..7df0b6db1c 100644 --- a/packages/client/src/pages/admin/email-settings.vue +++ b/packages/client/src/pages/admin/email-settings.vue @@ -95,7 +95,7 @@ export default defineComponent({ methods: { async init() { - const meta = await os.api('meta', { detail: true }); + const meta = await os.api('admin/meta'); this.enableEmail = meta.enableEmail; this.email = meta.email; this.smtpSecure = meta.smtpSecure; diff --git a/packages/client/src/pages/admin/instance-block.vue b/packages/client/src/pages/admin/instance-block.vue index 6cadc7df39..4cb8dc604e 100644 --- a/packages/client/src/pages/admin/instance-block.vue +++ b/packages/client/src/pages/admin/instance-block.vue @@ -42,7 +42,7 @@ export default defineComponent({ methods: { async init() { - const meta = await os.api('meta', { detail: true }); + const meta = await os.api('admin/meta'); this.blockedHosts = meta.blockedHosts.join('\n'); }, diff --git a/packages/client/src/pages/admin/integrations.discord.vue b/packages/client/src/pages/admin/integrations.discord.vue index 8fc340150a..6b50f1b0a9 100644 --- a/packages/client/src/pages/admin/integrations.discord.vue +++ b/packages/client/src/pages/admin/integrations.discord.vue @@ -60,7 +60,7 @@ export default defineComponent({ methods: { async init() { - const meta = await os.api('meta', { detail: true }); + const meta = await os.api('admin/meta'); this.uri = meta.uri; this.enableDiscordIntegration = meta.enableDiscordIntegration; this.discordClientId = meta.discordClientId; diff --git a/packages/client/src/pages/admin/integrations.github.vue b/packages/client/src/pages/admin/integrations.github.vue index d9db9c00f1..67f299e1bc 100644 --- a/packages/client/src/pages/admin/integrations.github.vue +++ b/packages/client/src/pages/admin/integrations.github.vue @@ -60,7 +60,7 @@ export default defineComponent({ methods: { async init() { - const meta = await os.api('meta', { detail: true }); + const meta = await os.api('admin/meta'); this.uri = meta.uri; this.enableGithubIntegration = meta.enableGithubIntegration; this.githubClientId = meta.githubClientId; diff --git a/packages/client/src/pages/admin/integrations.twitter.vue b/packages/client/src/pages/admin/integrations.twitter.vue index 1f8074535a..a389c71506 100644 --- a/packages/client/src/pages/admin/integrations.twitter.vue +++ b/packages/client/src/pages/admin/integrations.twitter.vue @@ -60,7 +60,7 @@ export default defineComponent({ methods: { async init() { - const meta = await os.api('meta', { detail: true }); + const meta = await os.api('admin/meta'); this.uri = meta.uri; this.enableTwitterIntegration = meta.enableTwitterIntegration; this.twitterConsumerKey = meta.twitterConsumerKey; diff --git a/packages/client/src/pages/admin/integrations.vue b/packages/client/src/pages/admin/integrations.vue index 91d03fef31..4db8a9e0a9 100644 --- a/packages/client/src/pages/admin/integrations.vue +++ b/packages/client/src/pages/admin/integrations.vue @@ -62,7 +62,7 @@ export default defineComponent({ methods: { async init() { - const meta = await os.api('meta', { detail: true }); + const meta = await os.api('admin/meta'); this.enableTwitterIntegration = meta.enableTwitterIntegration; this.enableGithubIntegration = meta.enableGithubIntegration; this.enableDiscordIntegration = meta.enableDiscordIntegration; diff --git a/packages/client/src/pages/admin/object-storage.vue b/packages/client/src/pages/admin/object-storage.vue index 6c5be220f8..a1ee0761c8 100644 --- a/packages/client/src/pages/admin/object-storage.vue +++ b/packages/client/src/pages/admin/object-storage.vue @@ -120,7 +120,7 @@ export default defineComponent({ methods: { async init() { - const meta = await os.api('meta', { detail: true }); + const meta = await os.api('admin/meta'); this.useObjectStorage = meta.useObjectStorage; this.objectStorageBaseUrl = meta.objectStorageBaseUrl; this.objectStorageBucket = meta.objectStorageBucket; diff --git a/packages/client/src/pages/admin/other-settings.vue b/packages/client/src/pages/admin/other-settings.vue index 6b588e88aa..99ea6a5f32 100644 --- a/packages/client/src/pages/admin/other-settings.vue +++ b/packages/client/src/pages/admin/other-settings.vue @@ -44,7 +44,7 @@ export default defineComponent({ methods: { async init() { - const meta = await os.api('meta', { detail: true }); + const meta = await os.api('admin/meta'); }, save() { os.apiWithDialog('admin/update-meta', { diff --git a/packages/client/src/pages/admin/proxy-account.vue b/packages/client/src/pages/admin/proxy-account.vue index 5c4fbffa0c..00f14a176f 100644 --- a/packages/client/src/pages/admin/proxy-account.vue +++ b/packages/client/src/pages/admin/proxy-account.vue @@ -46,7 +46,7 @@ export default defineComponent({ methods: { async init() { - const meta = await os.api('meta', { detail: true }); + const meta = await os.api('admin/meta'); this.proxyAccountId = meta.proxyAccountId; if (this.proxyAccountId) { this.proxyAccount = await os.api('users/show', { userId: this.proxyAccountId }); diff --git a/packages/client/src/pages/admin/queue.vue b/packages/client/src/pages/admin/queue.vue index 522210d933..35fd618c82 100644 --- a/packages/client/src/pages/admin/queue.vue +++ b/packages/client/src/pages/admin/queue.vue @@ -17,6 +17,7 @@ import XQueue from './queue.chart.vue'; import * as os from '@/os'; import { stream } from '@/stream'; import * as symbols from '@/symbols'; +import * as config from '@/config'; export default defineComponent({ components: { @@ -32,6 +33,14 @@ export default defineComponent({ title: this.$ts.jobQueue, icon: 'fas fa-clipboard-list', bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-up-right-from-square', + text: this.$ts.dashboard, + handler: () => { + window.open(config.url + '/queue', '_blank'); + }, + }], }, connection: markRaw(stream.useChannel('queueStats')), } diff --git a/packages/client/src/pages/admin/security.vue b/packages/client/src/pages/admin/security.vue index d069891647..d1c979b3e0 100644 --- a/packages/client/src/pages/admin/security.vue +++ b/packages/client/src/pages/admin/security.vue @@ -72,7 +72,7 @@ export default defineComponent({ methods: { async init() { - const meta = await os.api('meta', { detail: true }); + const meta = await os.api('admin/meta'); this.summalyProxy = meta.summalyProxy; this.enableHcaptcha = meta.enableHcaptcha; this.enableRecaptcha = meta.enableRecaptcha; diff --git a/packages/client/src/pages/admin/settings.vue b/packages/client/src/pages/admin/settings.vue index abd73fd98c..f2970d0459 100644 --- a/packages/client/src/pages/admin/settings.vue +++ b/packages/client/src/pages/admin/settings.vue @@ -210,7 +210,7 @@ export default defineComponent({ methods: { async init() { - const meta = await os.api('meta', { detail: true }); + const meta = await os.api('admin/meta'); this.name = meta.name; this.description = meta.description; this.tosUrl = meta.tosUrl; diff --git a/packages/client/src/pages/my-antennas/editor.vue b/packages/client/src/pages/my-antennas/editor.vue index 77199388c5..8c1d6148fe 100644 --- a/packages/client/src/pages/my-antennas/editor.vue +++ b/packages/client/src/pages/my-antennas/editor.vue @@ -7,10 +7,10 @@ <MkSelect v-model="src" class="_formBlock"> <template #label>{{ $ts.antennaSource }}</template> <option value="all">{{ $ts._antennaSources.all }}</option> - <option value="home">{{ $ts._antennaSources.homeTimeline }}</option> + <!--<option value="home">{{ $ts._antennaSources.homeTimeline }}</option>--> <option value="users">{{ $ts._antennaSources.users }}</option> - <option value="list">{{ $ts._antennaSources.userList }}</option> - <option value="group">{{ $ts._antennaSources.userGroup }}</option> + <!--<option value="list">{{ $ts._antennaSources.userList }}</option>--> + <!--<option value="group">{{ $ts._antennaSources.userGroup }}</option>--> </MkSelect> <MkSelect v-if="src === 'list'" v-model="userListId" class="_formBlock"> <template #label>{{ $ts.userList }}</template> diff --git a/packages/client/src/pages/settings/index.vue b/packages/client/src/pages/settings/index.vue index 42e40c5acb..44c3be62fe 100644 --- a/packages/client/src/pages/settings/index.vue +++ b/packages/client/src/pages/settings/index.vue @@ -149,6 +149,11 @@ const menuDef = computed(() => [{ to: '/settings/api', active: page.value === 'api', }, { + icon: 'fas fa-bolt', + text: 'Webhook', + to: '/settings/webhook', + active: page.value === 'webhook', + }, { icon: 'fas fa-ellipsis-h', text: i18n.ts.other, to: '/settings/other', @@ -192,6 +197,9 @@ const component = computed(() => { case 'security': return defineAsyncComponent(() => import('./security.vue')); case '2fa': return defineAsyncComponent(() => import('./2fa.vue')); case 'api': return defineAsyncComponent(() => import('./api.vue')); + case 'webhook': return defineAsyncComponent(() => import('./webhook.vue')); + case 'webhook/new': return defineAsyncComponent(() => import('./webhook.new.vue')); + case 'webhook/edit': return defineAsyncComponent(() => import('./webhook.edit.vue')); case 'apps': return defineAsyncComponent(() => import('./apps.vue')); case 'other': return defineAsyncComponent(() => import('./other.vue')); case 'general': return defineAsyncComponent(() => import('./general.vue')); diff --git a/packages/client/src/pages/settings/profile.vue b/packages/client/src/pages/settings/profile.vue index 8ed29d5c24..e991d725b6 100644 --- a/packages/client/src/pages/settings/profile.vue +++ b/packages/client/src/pages/settings/profile.vue @@ -54,7 +54,7 @@ </FormSlot> <FormSwitch v-model="profile.isCat" class="_formBlock">{{ i18n.ts.flagAsCat }}<template #caption>{{ i18n.ts.flagAsCatDescription }}</template></FormSwitch> - <FormSwitch v-model="profile.showTimelineReplies" class="_formBlock">{{ i18n.ts.flagShowTimelineReplies }}<template #caption>{{ i18n.ts.flagShowTimelineRepliesDescription }}</template></FormSwitch> + <FormSwitch v-model="profile.showTimelineReplies" class="_formBlock">{{ i18n.ts.flagShowTimelineReplies }}<template #caption>{{ i18n.ts.flagShowTimelineRepliesDescription }} {{ i18n.ts.reflectMayTakeTime }}</template></FormSwitch> <FormSwitch v-model="profile.isBot" class="_formBlock">{{ i18n.ts.flagAsBot }}<template #caption>{{ i18n.ts.flagAsBotDescription }}</template></FormSwitch> <FormSwitch v-model="profile.alwaysMarkNsfw" class="_formBlock">{{ i18n.ts.alwaysMarkSensitive }}</FormSwitch> diff --git a/packages/client/src/pages/settings/webhook.edit.vue b/packages/client/src/pages/settings/webhook.edit.vue new file mode 100644 index 0000000000..bb3a25407e --- /dev/null +++ b/packages/client/src/pages/settings/webhook.edit.vue @@ -0,0 +1,89 @@ +<template> +<div class="_formRoot"> + <FormInput v-model="name" class="_formBlock"> + <template #label>Name</template> + </FormInput> + + <FormInput v-model="url" type="url" class="_formBlock"> + <template #label>URL</template> + </FormInput> + + <FormInput v-model="secret" class="_formBlock"> + <template #prefix><i class="fas fa-lock"></i></template> + <template #label>Secret</template> + </FormInput> + + <FormSection> + <template #label>Events</template> + + <FormSwitch v-model="event_follow" class="_formBlock">Follow</FormSwitch> + <FormSwitch v-model="event_followed" class="_formBlock">Followed</FormSwitch> + <FormSwitch v-model="event_note" class="_formBlock">Note</FormSwitch> + <FormSwitch v-model="event_reply" class="_formBlock">Reply</FormSwitch> + <FormSwitch v-model="event_renote" class="_formBlock">Renote</FormSwitch> + <FormSwitch v-model="event_reaction" class="_formBlock">Reaction</FormSwitch> + <FormSwitch v-model="event_mention" class="_formBlock">Mention</FormSwitch> + </FormSection> + + <FormSwitch v-model="active" class="_formBlock">Active</FormSwitch> + + <div class="_formBlock" style="display: flex; gap: var(--margin); flex-wrap: wrap;"> + <FormButton primary inline @click="save"><i class="fas fa-check"></i> {{ i18n.ts.save }}</FormButton> + </div> +</div> +</template> + +<script lang="ts" setup> +import { } from 'vue'; +import FormInput from '@/components/form/input.vue'; +import FormSection from '@/components/form/section.vue'; +import FormSwitch from '@/components/form/switch.vue'; +import FormButton from '@/components/ui/button.vue'; +import * as os from '@/os'; +import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; + +const webhook = await os.api('i/webhooks/show', { + webhookId: new URLSearchParams(window.location.search).get('id') +}); + +let name = $ref(webhook.name); +let url = $ref(webhook.url); +let secret = $ref(webhook.secret); +let active = $ref(webhook.active); + +let event_follow = $ref(webhook.on.includes('follow')); +let event_followed = $ref(webhook.on.includes('followed')); +let event_note = $ref(webhook.on.includes('note')); +let event_reply = $ref(webhook.on.includes('reply')); +let event_renote = $ref(webhook.on.includes('renote')); +let event_reaction = $ref(webhook.on.includes('reaction')); +let event_mention = $ref(webhook.on.includes('mention')); + +async function save(): Promise<void> { + const events = []; + if (event_follow) events.push('follow'); + if (event_followed) events.push('followed'); + if (event_note) events.push('note'); + if (event_reply) events.push('reply'); + if (event_renote) events.push('renote'); + if (event_reaction) events.push('reaction'); + if (event_mention) events.push('mention'); + + os.apiWithDialog('i/webhooks/update', { + name, + url, + secret, + on: events, + active, + }); +} + +defineExpose({ + [symbols.PAGE_INFO]: { + title: 'Edit webhook', + icon: 'fas fa-bolt', + bg: 'var(--bg)', + }, +}); +</script> diff --git a/packages/client/src/pages/settings/webhook.new.vue b/packages/client/src/pages/settings/webhook.new.vue new file mode 100644 index 0000000000..9bb492c49e --- /dev/null +++ b/packages/client/src/pages/settings/webhook.new.vue @@ -0,0 +1,81 @@ +<template> +<div class="_formRoot"> + <FormInput v-model="name" class="_formBlock"> + <template #label>Name</template> + </FormInput> + + <FormInput v-model="url" type="url" class="_formBlock"> + <template #label>URL</template> + </FormInput> + + <FormInput v-model="secret" class="_formBlock"> + <template #prefix><i class="fas fa-lock"></i></template> + <template #label>Secret</template> + </FormInput> + + <FormSection> + <template #label>Events</template> + + <FormSwitch v-model="event_follow" class="_formBlock">Follow</FormSwitch> + <FormSwitch v-model="event_followed" class="_formBlock">Followed</FormSwitch> + <FormSwitch v-model="event_note" class="_formBlock">Note</FormSwitch> + <FormSwitch v-model="event_reply" class="_formBlock">Reply</FormSwitch> + <FormSwitch v-model="event_renote" class="_formBlock">Renote</FormSwitch> + <FormSwitch v-model="event_reaction" class="_formBlock">Reaction</FormSwitch> + <FormSwitch v-model="event_mention" class="_formBlock">Mention</FormSwitch> + </FormSection> + + <div class="_formBlock" style="display: flex; gap: var(--margin); flex-wrap: wrap;"> + <FormButton primary inline @click="create"><i class="fas fa-check"></i> {{ i18n.ts.create }}</FormButton> + </div> +</div> +</template> + +<script lang="ts" setup> +import { } from 'vue'; +import FormInput from '@/components/form/input.vue'; +import FormSection from '@/components/form/section.vue'; +import FormSwitch from '@/components/form/switch.vue'; +import FormButton from '@/components/ui/button.vue'; +import * as os from '@/os'; +import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; + +let name = $ref(''); +let url = $ref(''); +let secret = $ref(''); + +let event_follow = $ref(true); +let event_followed = $ref(true); +let event_note = $ref(true); +let event_reply = $ref(true); +let event_renote = $ref(true); +let event_reaction = $ref(true); +let event_mention = $ref(true); + +async function create(): Promise<void> { + const events = []; + if (event_follow) events.push('follow'); + if (event_followed) events.push('followed'); + if (event_note) events.push('note'); + if (event_reply) events.push('reply'); + if (event_renote) events.push('renote'); + if (event_reaction) events.push('reaction'); + if (event_mention) events.push('mention'); + + os.apiWithDialog('i/webhooks/create', { + name, + url, + secret, + on: events, + }); +} + +defineExpose({ + [symbols.PAGE_INFO]: { + title: 'Create new webhook', + icon: 'fas fa-bolt', + bg: 'var(--bg)', + }, +}); +</script> diff --git a/packages/client/src/pages/settings/webhook.vue b/packages/client/src/pages/settings/webhook.vue new file mode 100644 index 0000000000..c9af8b6766 --- /dev/null +++ b/packages/client/src/pages/settings/webhook.vue @@ -0,0 +1,52 @@ +<template> +<div class="_formRoot"> + <FormSection> + <FormLink :to="`/settings/webhook/new`"> + Create webhook + </FormLink> + </FormSection> + + <FormSection> + <MkPagination :pagination="pagination"> + <template v-slot="{items}"> + <FormLink v-for="webhook in items" :key="webhook.id" :to="`/settings/webhook/edit?id=${webhook.id}`" class="_formBlock"> + <template #icon> + <i v-if="webhook.active === false" class="fas fa-circle-pause"></i> + <i v-else-if="webhook.latestStatus === null" class="far fa-circle"></i> + <i v-else-if="[200, 201, 204].includes(webhook.latestStatus)" class="fas fa-check" :style="{ color: 'var(--success)' }"></i> + <i v-else class="fas fa-triangle-exclamation" :style="{ color: 'var(--error)' }"></i> + </template> + {{ webhook.name || webhook.url }} + <template #suffix> + <MkTime v-if="webhook.latestSentAt" :time="webhook.latestSentAt"></MkTime> + </template> + </FormLink> + </template> + </MkPagination> + </FormSection> +</div> +</template> + +<script lang="ts" setup> +import { } from 'vue'; +import MkPagination from '@/components/ui/pagination.vue'; +import FormSection from '@/components/form/section.vue'; +import FormLink from '@/components/form/link.vue'; +import { userPage } from '@/filters/user'; +import * as os from '@/os'; +import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; + +const pagination = { + endpoint: 'i/webhooks/list' as const, + limit: 10, +}; + +defineExpose({ + [symbols.PAGE_INFO]: { + title: 'Webhook', + icon: 'fas fa-bolt', + bg: 'var(--bg)', + }, +}); +</script> diff --git a/packages/client/src/pages/settings/word-mute.vue b/packages/client/src/pages/settings/word-mute.vue index 2767e6351a..c11707b6cf 100644 --- a/packages/client/src/pages/settings/word-mute.vue +++ b/packages/client/src/pages/settings/word-mute.vue @@ -13,7 +13,7 @@ </FormTextarea> </div> <div v-show="tab === 'hard'"> - <MkInfo class="_formBlock">{{ $ts._wordMute.hardDescription }}</MkInfo> + <MkInfo class="_formBlock">{{ $ts._wordMute.hardDescription }} {{ $ts.reflectMayTakeTime }}</MkInfo> <FormTextarea v-model="hardMutedWords" class="_formBlock"> <span>{{ $ts._wordMute.muteWords }}</span> <template #caption>{{ $ts._wordMute.muteWordsDescription }}<br>{{ $ts._wordMute.muteWordsDescription2 }}</template> diff --git a/packages/client/src/pages/user-info.vue b/packages/client/src/pages/user-info.vue index 4bdc82f601..516ab4d440 100644 --- a/packages/client/src/pages/user-info.vue +++ b/packages/client/src/pages/user-info.vue @@ -25,6 +25,7 @@ <FormSwitch v-if="user.host == null && $i.isAdmin && (moderator || !user.isAdmin)" v-model="moderator" class="_formBlock" @update:modelValue="toggleModerator">{{ $ts.moderator }}</FormSwitch> <FormSwitch v-model="silenced" class="_formBlock" @update:modelValue="toggleSilence">{{ $ts.silence }}</FormSwitch> <FormSwitch v-model="suspended" class="_formBlock" @update:modelValue="toggleSuspend">{{ $ts.suspend }}</FormSwitch> + {{ $ts.reflectMayTakeTime }} <FormButton v-if="user.host == null && iAmModerator" class="_formBlock" @click="resetPassword"><i class="fas fa-key"></i> {{ $ts.resetPassword }}</FormButton> </FormSection> diff --git a/packages/client/src/ui/classic.header.vue b/packages/client/src/ui/classic.header.vue index a9187ed734..c7fddbc491 100644 --- a/packages/client/src/ui/classic.header.vue +++ b/packages/client/src/ui/classic.header.vue @@ -103,6 +103,7 @@ export default defineComponent({ more(ev) { os.popup(import('@/components/launch-pad.vue'), { src: ev.currentTarget ?? ev.target, + anchor: { x: 'center', y: 'bottom' }, }, { }, 'closed'); }, diff --git a/packages/client/src/ui/classic.sidebar.vue b/packages/client/src/ui/classic.sidebar.vue index b08977ac3a..3364ee39be 100644 --- a/packages/client/src/ui/classic.sidebar.vue +++ b/packages/client/src/ui/classic.sidebar.vue @@ -121,9 +121,9 @@ export default defineComponent({ }, more(ev) { - os.popup(import('@/components/launch-pad.vue'), {}, { + os.popup(import('@/components/launch-pad.vue'), { src: ev.currentTarget ?? ev.target, - }, 'closed'); + }, {}, 'closed'); }, openAccountMenu:(ev) => { diff --git a/packages/client/src/ui/deck.vue b/packages/client/src/ui/deck.vue index e4571d4091..1e0d9a1652 100644 --- a/packages/client/src/ui/deck.vue +++ b/packages/client/src/ui/deck.vue @@ -17,7 +17,8 @@ :key="ids[0]" class="column" :column="columns.find(c => c.id === ids[0])" - :style="columns.find(c => c.id === ids[0]).flexible ? { flex: 1, minWidth: '350px' } : { width: columns.find(c => c.id === ids[0]).width + 'px' }" + :is-stacked="false" + :style="columns.find(c => c.id === ids[0])!.flexible ? { flex: 1, minWidth: '350px' } : { width: columns.find(c => c.id === ids[0])!.width + 'px' }" @parent-focus="moveFocus(ids[0], $event)" /> </template> @@ -25,8 +26,8 @@ <div v-if="isMobile" class="buttons"> <button class="button nav _button" @click="drawerMenuShowing = true"><i class="fas fa-bars"></i><span v-if="menuIndicated" class="indicator"><i class="fas fa-circle"></i></span></button> <button class="button home _button" @click="$router.push('/')"><i class="fas fa-home"></i></button> - <button class="button notifications _button" @click="$router.push('/my/notifications')"><i class="fas fa-bell"></i><span v-if="$i.hasUnreadNotification" class="indicator"><i class="fas fa-circle"></i></span></button> - <button class="button post _button" @click="post()"><i class="fas fa-pencil-alt"></i></button> + <button class="button notifications _button" @click="$router.push('/my/notifications')"><i class="fas fa-bell"></i><span v-if="$i?.hasUnreadNotification" class="indicator"><i class="fas fa-circle"></i></span></button> + <button class="button post _button" @click="os.post()"><i class="fas fa-pencil-alt"></i></button> </div> <transition :name="$store.state.animation ? 'menu-back' : ''"> @@ -45,8 +46,8 @@ </div> </template> -<script lang="ts"> -import { computed, defineComponent, provide, ref, watch } from 'vue'; +<script lang="ts" setup> +import { computed, provide, ref, watch } from 'vue'; import { v4 as uuid } from 'uuid'; import DeckColumnCore from '@/ui/deck/column-core.vue'; import XSidebar from '@/ui/_common_/sidebar.vue'; @@ -60,102 +61,82 @@ import { useRoute } from 'vue-router'; import { $i } from '@/account'; import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XCommon, - XSidebar, - XDrawerMenu, - DeckColumnCore, - }, - - setup() { - const isMobile = ref(window.innerWidth <= 500); - window.addEventListener('resize', () => { - isMobile.value = window.innerWidth <= 500; - }); +const isMobile = ref(window.innerWidth <= 500); +window.addEventListener('resize', () => { + isMobile.value = window.innerWidth <= 500; +}); - const drawerMenuShowing = ref(false); +const drawerMenuShowing = ref(false); - const route = useRoute(); - watch(route, () => { - drawerMenuShowing.value = false; - }); +const route = useRoute(); +watch(route, () => { + drawerMenuShowing.value = false; +}); - const columns = deckStore.reactiveState.columns; - const layout = deckStore.reactiveState.layout; - const menuIndicated = computed(() => { - if ($i == null) return false; - for (const def in menuDef) { - if (menuDef[def].indicated) return true; - } - return false; - }); +const columns = deckStore.reactiveState.columns; +const layout = deckStore.reactiveState.layout; +const menuIndicated = computed(() => { + if ($i == null) return false; + for (const def in menuDef) { + if (menuDef[def].indicated) return true; + } + return false; +}); - const addColumn = async (ev) => { - const columns = [ - 'main', - 'widgets', - 'notifications', - 'tl', - 'antenna', - 'list', - 'mentions', - 'direct', - ]; +const addColumn = async (ev) => { + const columns = [ + 'main', + 'widgets', + 'notifications', + 'tl', + 'antenna', + 'list', + 'mentions', + 'direct', + ]; - const { canceled, result: column } = await os.select({ - title: i18n.ts._deck.addColumn, - items: columns.map(column => ({ - value: column, text: i18n.t('_deck._columns.' + column) - })) - }); - if (canceled) return; + const { canceled, result: column } = await os.select({ + title: i18n.ts._deck.addColumn, + items: columns.map(column => ({ + value: column, text: i18n.t('_deck._columns.' + column) + })) + }); + if (canceled) return; - addColumnToStore({ - type: column, - id: uuid(), - name: i18n.t('_deck._columns.' + column), - width: 330, - }); - }; + addColumnToStore({ + type: column, + id: uuid(), + name: i18n.t('_deck._columns.' + column), + width: 330, + }); +}; - const onContextmenu = (ev) => { - os.contextMenu([{ - text: i18n.ts._deck.addColumn, - icon: null, - action: addColumn - }], ev); - }; +const onContextmenu = (ev) => { + os.contextMenu([{ + text: i18n.ts._deck.addColumn, + action: addColumn, + }], ev); +}; - provide('shouldSpacerMin', true); - if (deckStore.state.navWindow) { - provide('navHook', (url) => { - os.pageWindow(url); - }); - } - - document.documentElement.style.overflowY = 'hidden'; - document.documentElement.style.scrollBehavior = 'auto'; - window.addEventListener('wheel', (ev) => { - if (getScrollContainer(ev.target) == null) { - document.documentElement.scrollLeft += ev.deltaY > 0 ? 96 : -96; - } - }); - loadDeck(); +provide('shouldSpacerMin', true); +if (deckStore.state.navWindow) { + provide('navHook', (url) => { + os.pageWindow(url); + }); +} - return { - isMobile, - deckStore, - drawerMenuShowing, - columns, - layout, - menuIndicated, - onContextmenu, - wallpaper: localStorage.getItem('wallpaper') != null, - post: os.post, - }; - }, +document.documentElement.style.overflowY = 'hidden'; +document.documentElement.style.scrollBehavior = 'auto'; +window.addEventListener('wheel', (ev) => { + if (getScrollContainer(ev.target as HTMLElement) == null && ev.deltaX === 0) { + document.documentElement.scrollLeft += ev.deltaY; + } }); +loadDeck(); + +function moveFocus(id: string, direction: 'up' | 'down' | 'left' | 'right') { + // TODO?? +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/ui/deck/antenna-column.vue b/packages/client/src/ui/deck/antenna-column.vue index 198ebbbefa..e0f56c2800 100644 --- a/packages/client/src/ui/deck/antenna-column.vue +++ b/packages/client/src/ui/deck/antenna-column.vue @@ -1,75 +1,62 @@ <template> -<XColumn :func="{ handler: setAntenna, title: $ts.selectAntenna }" :column="column" :is-stacked="isStacked"> +<XColumn :func="{ handler: setAntenna, title: $ts.selectAntenna }" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)"> <template #header> <i class="fas fa-satellite"></i><span style="margin-left: 8px;">{{ column.name }}</span> </template> - <XTimeline v-if="column.antennaId" ref="timeline" src="antenna" :antenna="column.antennaId" @after="() => $emit('loaded')"/> + <XTimeline v-if="column.antennaId" ref="timeline" src="antenna" :antenna="column.antennaId" @after="() => emit('loaded')"/> </XColumn> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted } from 'vue'; import XColumn from './column.vue'; import XTimeline from '@/components/timeline.vue'; import * as os from '@/os'; -import { updateColumn } from './deck-store'; +import { updateColumn, Column } from './deck-store'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XColumn, - XTimeline, - }, +const props = defineProps<{ + column: Column; + isStacked: boolean; +}>(); - props: { - column: { - type: Object, - required: true - }, - isStacked: { - type: Boolean, - required: true - } - }, +const emit = defineEmits<{ + (e: 'loaded'): void; + (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; +}>(); - data() { - return { - }; - }, +let timeline = $ref<InstanceType<typeof XTimeline>>(); - watch: { - mediaOnly() { - (this.$refs.timeline as any).reload(); - } - }, - - mounted() { - if (this.column.antennaId == null) { - this.setAntenna(); - } - }, +onMounted(() => { + if (props.column.antennaId == null) { + setAntenna(); + } +}); - methods: { - async setAntenna() { - const antennas = await os.api('antennas/list'); - const { canceled, result: antenna } = await os.select({ - title: this.$ts.selectAntenna, - items: antennas.map(x => ({ - value: x, text: x.name - })), - default: this.column.antennaId - }); - if (canceled) return; - updateColumn(this.column.id, { - antennaId: antenna.id - }); - }, +async function setAntenna() { + const antennas = await os.api('antennas/list'); + const { canceled, result: antenna } = await os.select({ + title: i18n.ts.selectAntenna, + items: antennas.map(x => ({ + value: x, text: x.name + })), + default: props.column.antennaId + }); + if (canceled) return; + updateColumn(props.column.id, { + antennaId: antenna.id + }); +} +/* +function focus() { + timeline.focus(); +} - focus() { - (this.$refs.timeline as any).focus(); - } - } +defineExpose({ + focus, }); +*/ </script> <style lang="scss" scoped> diff --git a/packages/client/src/ui/deck/column-core.vue b/packages/client/src/ui/deck/column-core.vue index 5393bac736..485e89a062 100644 --- a/packages/client/src/ui/deck/column-core.vue +++ b/packages/client/src/ui/deck/column-core.vue @@ -1,17 +1,18 @@ <template> <!-- TODO: リファクタの余地がありそう --> -<XMainColumn v-if="column.type === 'main'" :column="column" :is-stacked="isStacked" @parent-focus="$emit('parent-focus', $event)"/> -<XWidgetsColumn v-else-if="column.type === 'widgets'" :column="column" :is-stacked="isStacked" @parent-focus="$emit('parent-focus', $event)"/> -<XNotificationsColumn v-else-if="column.type === 'notifications'" :column="column" :is-stacked="isStacked" @parent-focus="$emit('parent-focus', $event)"/> -<XTlColumn v-else-if="column.type === 'tl'" :column="column" :is-stacked="isStacked" @parent-focus="$emit('parent-focus', $event)"/> -<XListColumn v-else-if="column.type === 'list'" :column="column" :is-stacked="isStacked" @parent-focus="$emit('parent-focus', $event)"/> -<XAntennaColumn v-else-if="column.type === 'antenna'" :column="column" :is-stacked="isStacked" @parent-focus="$emit('parent-focus', $event)"/> -<XMentionsColumn v-else-if="column.type === 'mentions'" :column="column" :is-stacked="isStacked" @parent-focus="$emit('parent-focus', $event)"/> -<XDirectColumn v-else-if="column.type === 'direct'" :column="column" :is-stacked="isStacked" @parent-focus="$emit('parent-focus', $event)"/> +<div v-if="!column">たぶん見えちゃいけないやつ</div> +<XMainColumn v-else-if="column.type === 'main'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> +<XWidgetsColumn v-else-if="column.type === 'widgets'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> +<XNotificationsColumn v-else-if="column.type === 'notifications'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> +<XTlColumn v-else-if="column.type === 'tl'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> +<XListColumn v-else-if="column.type === 'list'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> +<XAntennaColumn v-else-if="column.type === 'antenna'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> +<XMentionsColumn v-else-if="column.type === 'mentions'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> +<XDirectColumn v-else-if="column.type === 'direct'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import XMainColumn from './main-column.vue'; import XTlColumn from './tl-column.vue'; import XAntennaColumn from './antenna-column.vue'; @@ -20,33 +21,24 @@ import XNotificationsColumn from './notifications-column.vue'; import XWidgetsColumn from './widgets-column.vue'; import XMentionsColumn from './mentions-column.vue'; import XDirectColumn from './direct-column.vue'; +import { Column } from './deck-store'; +defineProps<{ + column?: Column; + isStacked: boolean; +}>(); + +const emit = defineEmits<{ + (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; +}>(); + +/* export default defineComponent({ - components: { - XMainColumn, - XTlColumn, - XAntennaColumn, - XListColumn, - XNotificationsColumn, - XWidgetsColumn, - XMentionsColumn, - XDirectColumn - }, - props: { - column: { - type: Object, - required: true - }, - isStacked: { - type: Boolean, - required: false, - default: false - } - }, methods: { focus() { this.$children[0].focus(); } } }); +*/ </script> diff --git a/packages/client/src/ui/deck/column.vue b/packages/client/src/ui/deck/column.vue index f1ce3ca838..5f8da8cf8f 100644 --- a/packages/client/src/ui/deck/column.vue +++ b/packages/client/src/ui/deck/column.vue @@ -31,238 +31,211 @@ </template> <script lang="ts"> -import { defineComponent } from 'vue'; +export type DeckFunc = { + title: string; + handler: (payload: MouseEvent) => void; + icon?: string; +}; +</script> +<script lang="ts" setup> +import { onBeforeUnmount, onMounted, provide, watch } from 'vue'; import * as os from '@/os'; -import { updateColumn, swapLeftColumn, swapRightColumn, swapUpColumn, swapDownColumn, stackLeftColumn, popRightColumn, removeColumn, swapColumn } from './deck-store'; +import { updateColumn, swapLeftColumn, swapRightColumn, swapUpColumn, swapDownColumn, stackLeftColumn, popRightColumn, removeColumn, swapColumn, Column } from './deck-store'; import { deckStore } from './deck-store'; +import { i18n } from '@/i18n'; -export default defineComponent({ - provide: { - shouldHeaderThin: true, - shouldOmitHeaderTitle: true, - }, +provide('shouldHeaderThin', true); +provide('shouldOmitHeaderTitle', true); - props: { - column: { - type: Object, - required: false, - default: null - }, - isStacked: { - type: Boolean, - required: false, - default: false - }, - func: { - type: Object, - required: false, - default: null - }, - naked: { - type: Boolean, - required: false, - default: false - }, - indicated: { - type: Boolean, - required: false, - default: false - }, - }, +const props = withDefaults(defineProps<{ + column: Column; + isStacked?: boolean; + func?: DeckFunc | null; + naked?: boolean; + indicated?: boolean; +}>(), { + isStacked: false, + func: null, + naked: false, + indicated: false, +}); - data() { - return { - deckStore, - dragging: false, - draghover: false, - dropready: false, - }; - }, +const emit = defineEmits<{ + (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; + (e: 'change-active-state', v: boolean): void; +}>(); - computed: { - isMainColumn(): boolean { - return this.column.type === 'main'; - }, +let body = $ref<HTMLDivElement>(); - active(): boolean { - return this.column.active !== false; - }, +let dragging = $ref(false); +watch($$(dragging), v => os.deckGlobalEvents.emit(v ? 'column.dragStart' : 'column.dragEnd')); - keymap(): any { - return { - 'shift+up': () => this.$parent.$emit('parent-focus', 'up'), - 'shift+down': () => this.$parent.$emit('parent-focus', 'down'), - 'shift+left': () => this.$parent.$emit('parent-focus', 'left'), - 'shift+right': () => this.$parent.$emit('parent-focus', 'right'), - }; - } - }, +let draghover = $ref(false); +let dropready = $ref(false); - watch: { - active(v) { - this.$emit('change-active-state', v); - }, +const isMainColumn = $computed(() => props.column.type === 'main'); +const active = $computed(() => props.column.active !== false); +watch($$(active), v => emit('change-active-state', v)); - dragging(v) { - os.deckGlobalEvents.emit(v ? 'column.dragStart' : 'column.dragEnd'); - } - }, +const keymap = $computed(() => ({ + 'shift+up': () => emit('parent-focus', 'up'), + 'shift+down': () => emit('parent-focus', 'down'), + 'shift+left': () => emit('parent-focus', 'left'), + 'shift+right': () => emit('parent-focus', 'right'), +})); - mounted() { - os.deckGlobalEvents.on('column.dragStart', this.onOtherDragStart); - os.deckGlobalEvents.on('column.dragEnd', this.onOtherDragEnd); - }, +onMounted(() => { + os.deckGlobalEvents.on('column.dragStart', onOtherDragStart); + os.deckGlobalEvents.on('column.dragEnd', onOtherDragEnd); +}); - beforeUnmount() { - os.deckGlobalEvents.off('column.dragStart', this.onOtherDragStart); - os.deckGlobalEvents.off('column.dragEnd', this.onOtherDragEnd); - }, +onBeforeUnmount(() => { + os.deckGlobalEvents.off('column.dragStart', onOtherDragStart); + os.deckGlobalEvents.off('column.dragEnd', onOtherDragEnd); +}); - methods: { - onOtherDragStart() { - this.dropready = true; - }, - onOtherDragEnd() { - this.dropready = false; - }, +function onOtherDragStart() { + dropready = true; +} - toggleActive() { - if (!this.isStacked) return; - updateColumn(this.column.id, { - active: !this.column.active - }); - }, +function onOtherDragEnd() { + dropready = false; +} - getMenu() { - const items = [{ - icon: 'fas fa-pencil-alt', - text: this.$ts.edit, - action: async () => { - const { canceled, result } = await os.form(this.column.name, { - name: { - type: 'string', - label: this.$ts.name, - default: this.column.name - }, - width: { - type: 'number', - label: this.$ts.width, - default: this.column.width - }, - flexible: { - type: 'boolean', - label: this.$ts.flexible, - default: this.column.flexible - } - }); - if (canceled) return; - updateColumn(this.column.id, result); - } - }, null, { - icon: 'fas fa-arrow-left', - text: this.$ts._deck.swapLeft, - action: () => { - swapLeftColumn(this.column.id); - } - }, { - icon: 'fas fa-arrow-right', - text: this.$ts._deck.swapRight, - action: () => { - swapRightColumn(this.column.id); - } - }, this.isStacked ? { - icon: 'fas fa-arrow-up', - text: this.$ts._deck.swapUp, - action: () => { - swapUpColumn(this.column.id); - } - } : undefined, this.isStacked ? { - icon: 'fas fa-arrow-down', - text: this.$ts._deck.swapDown, - action: () => { - swapDownColumn(this.column.id); - } - } : undefined, null, { - icon: 'fas fa-window-restore', - text: this.$ts._deck.stackLeft, - action: () => { - stackLeftColumn(this.column.id); - } - }, this.isStacked ? { - icon: 'fas fa-window-maximize', - text: this.$ts._deck.popRight, - action: () => { - popRightColumn(this.column.id); - } - } : undefined, null, { - icon: 'fas fa-trash-alt', - text: this.$ts.remove, - danger: true, - action: () => { - removeColumn(this.column.id); - } - }]; +function toggleActive() { + if (!props.isStacked) return; + updateColumn(props.column.id, { + active: !props.column.active + }); +} - return items; - }, +function getMenu() { + const items = [{ + icon: 'fas fa-pencil-alt', + text: i18n.ts.edit, + action: async () => { + const { canceled, result } = await os.form(props.column.name, { + name: { + type: 'string', + label: i18n.ts.name, + default: props.column.name + }, + width: { + type: 'number', + label: i18n.ts.width, + default: props.column.width + }, + flexible: { + type: 'boolean', + label: i18n.ts.flexible, + default: props.column.flexible + } + }); + if (canceled) return; + updateColumn(props.column.id, result); + } + }, null, { + icon: 'fas fa-arrow-left', + text: i18n.ts._deck.swapLeft, + action: () => { + swapLeftColumn(props.column.id); + } + }, { + icon: 'fas fa-arrow-right', + text: i18n.ts._deck.swapRight, + action: () => { + swapRightColumn(props.column.id); + } + }, props.isStacked ? { + icon: 'fas fa-arrow-up', + text: i18n.ts._deck.swapUp, + action: () => { + swapUpColumn(props.column.id); + } + } : undefined, props.isStacked ? { + icon: 'fas fa-arrow-down', + text: i18n.ts._deck.swapDown, + action: () => { + swapDownColumn(props.column.id); + } + } : undefined, null, { + icon: 'fas fa-window-restore', + text: i18n.ts._deck.stackLeft, + action: () => { + stackLeftColumn(props.column.id); + } + }, props.isStacked ? { + icon: 'fas fa-window-maximize', + text: i18n.ts._deck.popRight, + action: () => { + popRightColumn(props.column.id); + } + } : undefined, null, { + icon: 'fas fa-trash-alt', + text: i18n.ts.remove, + danger: true, + action: () => { + removeColumn(props.column.id); + } + }]; + return items; +} - onContextmenu(ev: MouseEvent) { - os.contextMenu(this.getMenu(), ev); - }, +function onContextmenu(ev: MouseEvent) { + os.contextMenu(getMenu(), ev); +} - goTop() { - this.$refs.body.scrollTo({ - top: 0, - behavior: 'smooth' - }); - }, +function goTop() { + body.scrollTo({ + top: 0, + behavior: 'smooth' + }); +} - onDragstart(e) { - e.dataTransfer.effectAllowed = 'move'; - e.dataTransfer.setData(_DATA_TRANSFER_DECK_COLUMN_, this.column.id); +function onDragstart(e) { + e.dataTransfer.effectAllowed = 'move'; + e.dataTransfer.setData(_DATA_TRANSFER_DECK_COLUMN_, props.column.id); - // Chromeのバグで、Dragstartハンドラ内ですぐにDOMを変更する(=リアクティブなプロパティを変更する)とDragが終了してしまう - // SEE: https://stackoverflow.com/questions/19639969/html5-dragend-event-firing-immediately - window.setTimeout(() => { - this.dragging = true; - }, 10); - }, + // Chromeのバグで、Dragstartハンドラ内ですぐにDOMを変更する(=リアクティブなプロパティを変更する)とDragが終了してしまう + // SEE: https://stackoverflow.com/questions/19639969/html5-dragend-event-firing-immediately + window.setTimeout(() => { + dragging = true; + }, 10); +} - onDragend(e) { - this.dragging = false; - }, +function onDragend(e) { + dragging = false; +} - onDragover(e) { - // 自分自身がドラッグされている場合 - if (this.dragging) { - // 自分自身にはドロップさせない - e.dataTransfer.dropEffect = 'none'; - return; - } +function onDragover(e) { + // 自分自身がドラッグされている場合 + if (dragging) { + // 自分自身にはドロップさせない + e.dataTransfer.dropEffect = 'none'; + return; + } - const isDeckColumn = e.dataTransfer.types[0] == _DATA_TRANSFER_DECK_COLUMN_; + const isDeckColumn = e.dataTransfer.types[0] == _DATA_TRANSFER_DECK_COLUMN_; - e.dataTransfer.dropEffect = isDeckColumn ? 'move' : 'none'; + e.dataTransfer.dropEffect = isDeckColumn ? 'move' : 'none'; - if (!this.dragging && isDeckColumn) this.draghover = true; - }, + if (!dragging && isDeckColumn) draghover = true; +} - onDragleave() { - this.draghover = false; - }, +function onDragleave() { + draghover = false; +} - onDrop(e) { - this.draghover = false; - os.deckGlobalEvents.emit('column.dragEnd'); +function onDrop(e) { + draghover = false; + os.deckGlobalEvents.emit('column.dragEnd'); - const id = e.dataTransfer.getData(_DATA_TRANSFER_DECK_COLUMN_); - if (id != null && id != '') { - swapColumn(this.column.id, id); - } - } + const id = e.dataTransfer.getData(_DATA_TRANSFER_DECK_COLUMN_); + if (id != null && id != '') { + swapColumn(props.column.id, id); } -}); +} </script> <style lang="scss" scoped> @@ -399,9 +372,9 @@ export default defineComponent({ > div { height: calc(100% - var(--deckColumnHeaderHeight)); - overflow: auto; - overflow-x: hidden; - overscroll-behavior: contain; + overflow-y: auto; + overflow-x: hidden; // Safari does not supports clip + overflow-x: clip; -webkit-overflow-scrolling: touch; box-sizing: border-box; } diff --git a/packages/client/src/ui/deck/deck-store.ts b/packages/client/src/ui/deck/deck-store.ts index 66db5e83ed..f7c39ad8fd 100644 --- a/packages/client/src/ui/deck/deck-store.ts +++ b/packages/client/src/ui/deck/deck-store.ts @@ -1,8 +1,9 @@ import { throttle } from 'throttle-debounce'; import { i18n } from '@/i18n'; import { api } from '@/os'; -import { markRaw, watch } from 'vue'; +import { markRaw } from 'vue'; import { Storage } from '../../pizzax'; +import { notificationTypes } from 'misskey-js'; type ColumnWidget = { name: string; @@ -10,13 +11,18 @@ type ColumnWidget = { data: Record<string, any>; }; -type Column = { +export type Column = { id: string; type: string; name: string | null; width: number; widgets?: ColumnWidget[]; active?: boolean; + flexible?: boolean; + antennaId?: string; + listId?: string; + includingTypes?: typeof notificationTypes[number][]; + tl?: 'home' | 'local' | 'social' | 'global'; }; function copy<T>(x: T): T { diff --git a/packages/client/src/ui/deck/direct-column.vue b/packages/client/src/ui/deck/direct-column.vue index ca70f693c3..ebaba574f4 100644 --- a/packages/client/src/ui/deck/direct-column.vue +++ b/packages/client/src/ui/deck/direct-column.vue @@ -1,5 +1,5 @@ <template> -<XColumn :column="column" :is-stacked="isStacked"> +<XColumn :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)"> <template #header><i class="fas fa-envelope" style="margin-right: 8px;"></i>{{ column.name }}</template> <XNotes :pagination="pagination"/> @@ -7,21 +7,25 @@ </template> <script lang="ts" setup> -import { computed } from 'vue'; +import { } from 'vue'; import XColumn from './column.vue'; import XNotes from '@/components/notes.vue'; -import * as os from '@/os'; +import { Column } from './deck-store'; -const props = defineProps<{ - column: Record<string, unknown>; // TODO +defineProps<{ + column: Column; isStacked: boolean; }>(); +const emit = defineEmits<{ + (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; +}>(); + const pagination = { - point: 'notes/mentions' as const, + endpoint: 'notes/mentions' as const, limit: 10, - params: computed(() => ({ - visibility: 'specified' as const, - })), + params: { + visibility: 'specified' + }, }; </script> diff --git a/packages/client/src/ui/deck/list-column.vue b/packages/client/src/ui/deck/list-column.vue index ab04aee4e7..b990516d05 100644 --- a/packages/client/src/ui/deck/list-column.vue +++ b/packages/client/src/ui/deck/list-column.vue @@ -1,75 +1,65 @@ <template> -<XColumn :func="{ handler: setList, title: $ts.selectList }" :column="column" :is-stacked="isStacked"> +<XColumn :func="{ handler: setList, title: $ts.selectList }" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)"> <template #header> <i class="fas fa-list-ul"></i><span style="margin-left: 8px;">{{ column.name }}</span> </template> - <XTimeline v-if="column.listId" ref="timeline" src="list" :list="column.listId" @after="() => $emit('loaded')"/> + <XTimeline v-if="column.listId" ref="timeline" src="list" :list="column.listId" @after="() => emit('loaded')"/> </XColumn> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import XColumn from './column.vue'; import XTimeline from '@/components/timeline.vue'; import * as os from '@/os'; -import { updateColumn } from './deck-store'; +import { updateColumn, Column } from './deck-store'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XColumn, - XTimeline, - }, +const props = defineProps<{ + column: Column; + isStacked: boolean; +}>(); - props: { - column: { - type: Object, - required: true - }, - isStacked: { - type: Boolean, - required: true - } - }, +const emit = defineEmits<{ + (e: 'loaded'): void; + (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; +}>(); - data() { - return { - }; - }, +let timeline = $ref<InstanceType<typeof XTimeline>>(); - watch: { - mediaOnly() { - (this.$refs.timeline as any).reload(); - } - }, +if (props.column.listId == null) { + setList(); +} - mounted() { - if (this.column.listId == null) { - this.setList(); - } - }, +async function setList() { + const lists = await os.api('users/lists/list'); + const { canceled, result: list } = await os.select({ + title: i18n.ts.selectList, + items: lists.map(x => ({ + value: x, text: x.name + })), + default: props.column.listId + }); + if (canceled) return; + updateColumn(props.column.id, { + listId: list.id + }); +} - methods: { - async setList() { - const lists = await os.api('users/lists/list'); - const { canceled, result: list } = await os.select({ - title: this.$ts.selectList, - items: lists.map(x => ({ - value: x, text: x.name - })), - default: this.column.listId - }); - if (canceled) return; - updateColumn(this.column.id, { - listId: list.id - }); - }, +/* +function focus() { + timeline.focus(); +} - focus() { - (this.$refs.timeline as any).focus(); +export default defineComponent({ + watch: { + mediaOnly() { + (this.$refs.timeline as any).reload(); } } }); +*/ </script> <style lang="scss" scoped> diff --git a/packages/client/src/ui/deck/main-column.vue b/packages/client/src/ui/deck/main-column.vue index cb045e9a46..57caab44cb 100644 --- a/packages/client/src/ui/deck/main-column.vue +++ b/packages/client/src/ui/deck/main-column.vue @@ -1,5 +1,5 @@ <template> -<XColumn v-if="deckStore.state.alwaysShowMainColumn || $route.name !== 'index'" :column="column" :is-stacked="isStacked"> +<XColumn v-if="deckStore.state.alwaysShowMainColumn || $route.name !== 'index'" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)"> <template #header> <template v-if="pageInfo"> <i :class="pageInfo.icon"></i> @@ -20,72 +20,59 @@ </XColumn> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import XColumn from './column.vue'; -import XNotes from '@/components/notes.vue'; -import { deckStore } from '@/ui/deck/deck-store'; +import { deckStore, Column } from '@/ui/deck/deck-store'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; +import { router } from '@/router'; -export default defineComponent({ - components: { - XColumn, - XNotes - }, +defineProps<{ + column: Column; + isStacked: boolean; +}>(); - props: { - column: { - type: Object, - required: true - }, - isStacked: { - type: Boolean, - required: true - } - }, - - data() { - return { - deckStore, - pageInfo: null, - } - }, - - methods: { - changePage(page) { - if (page == null) return; - if (page[symbols.PAGE_INFO]) { - this.pageInfo = page[symbols.PAGE_INFO]; - } - }, +const emit = defineEmits<{ + (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; +}>(); - back() { - history.back(); - }, +let pageInfo = $ref<Record<string, any> | null>(null); - onContextmenu(ev: MouseEvent) { - const isLink = (el: HTMLElement) => { - if (el.tagName === 'A') return true; - if (el.parentElement) { - return isLink(el.parentElement); - } - }; - if (isLink(ev.target)) return; - if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(ev.target.tagName) || ev.target.attributes['contenteditable']) return; - if (window.getSelection().toString() !== '') return; - const path = this.$route.path; - os.contextMenu([{ - type: 'label', - text: path, - }, { - icon: 'fas fa-window-maximize', - text: this.$ts.openInWindow, - action: () => { - os.pageWindow(path); - } - }], ev); - }, +function changePage(page) { + if (page == null) return; + if (page[symbols.PAGE_INFO]) { + pageInfo = page[symbols.PAGE_INFO]; } -}); +} +/* +function back() { + history.back(); +} +*/ +function onContextmenu(ev: MouseEvent) { + if (!ev.target) return; + + const isLink = (el: HTMLElement) => { + if (el.tagName === 'A') return true; + if (el.parentElement) { + return isLink(el.parentElement); + } + }; + if (isLink(ev.target as HTMLElement)) return; + if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes((ev.target as HTMLElement).tagName) || (ev.target as HTMLElement).attributes['contenteditable']) return; + if (window.getSelection()?.toString() !== '') return; + const path = router.currentRoute.value.path; + os.contextMenu([{ + type: 'label', + text: path, + }, { + icon: 'fas fa-window-maximize', + text: i18n.ts.openInWindow, + action: () => { + os.pageWindow(path); + } + }], ev); +} </script> diff --git a/packages/client/src/ui/deck/mentions-column.vue b/packages/client/src/ui/deck/mentions-column.vue index 6822e7ef06..a7a012a7fb 100644 --- a/packages/client/src/ui/deck/mentions-column.vue +++ b/packages/client/src/ui/deck/mentions-column.vue @@ -1,5 +1,5 @@ <template> -<XColumn :column="column" :is-stacked="isStacked"> +<XColumn :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)"> <template #header><i class="fas fa-at" style="margin-right: 8px;"></i>{{ column.name }}</template> <XNotes :pagination="pagination"/> @@ -10,13 +10,17 @@ import { } from 'vue'; import XColumn from './column.vue'; import XNotes from '@/components/notes.vue'; -import * as os from '@/os'; +import { Column } from './deck-store'; -const props = defineProps<{ - column: Record<string, unknown>; // TODO +defineProps<{ + column: Column; isStacked: boolean; }>(); +const emit = defineEmits<{ + (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; +}>(); + const pagination = { endpoint: 'notes/mentions' as const, limit: 10, diff --git a/packages/client/src/ui/deck/notifications-column.vue b/packages/client/src/ui/deck/notifications-column.vue index f8f406cdd1..50ee12a275 100644 --- a/packages/client/src/ui/deck/notifications-column.vue +++ b/packages/client/src/ui/deck/notifications-column.vue @@ -1,53 +1,38 @@ <template> -<XColumn :column="column" :is-stacked="isStacked" :func="{ handler: func, title: $ts.notificationSetting }"> +<XColumn :column="column" :is-stacked="isStacked" :func="{ handler: func, title: $ts.notificationSetting }" @parent-focus="$event => emit('parent-focus', $event)"> <template #header><i class="fas fa-bell" style="margin-right: 8px;"></i>{{ column.name }}</template> <XNotifications :include-types="column.includingTypes"/> </XColumn> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import XColumn from './column.vue'; import XNotifications from '@/components/notifications.vue'; import * as os from '@/os'; import { updateColumn } from './deck-store'; +import { Column } from './deck-store'; -export default defineComponent({ - components: { - XColumn, - XNotifications - }, +const props = defineProps<{ + column: Column; + isStacked: boolean; +}>(); - props: { - column: { - type: Object, - required: true - }, - isStacked: { - type: Boolean, - required: true - } - }, - - data() { - return { - } - }, +const emit = defineEmits<{ + (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; +}>(); - methods: { - func() { - os.popup(import('@/components/notification-setting-window.vue'), { - includingTypes: this.column.includingTypes, - }, { - done: async (res) => { - const { includingTypes } = res; - updateColumn(this.column.id, { - includingTypes: includingTypes - }); - }, - }, 'closed'); - } - } -}); +function func() { + os.popup(import('@/components/notification-setting-window.vue'), { + includingTypes: props.column.includingTypes, + }, { + done: async (res) => { + const { includingTypes } = res; + updateColumn(props.column.id, { + includingTypes: includingTypes + }); + }, + }, 'closed'); +} </script> diff --git a/packages/client/src/ui/deck/tl-column.vue b/packages/client/src/ui/deck/tl-column.vue index 8b22d7efb9..02b9ef83a1 100644 --- a/packages/client/src/ui/deck/tl-column.vue +++ b/packages/client/src/ui/deck/tl-column.vue @@ -1,5 +1,5 @@ <template> -<XColumn :func="{ handler: setType, title: $ts.timeline }" :column="column" :is-stacked="isStacked" :indicated="indicated" @change-active-state="onChangeActiveState"> +<XColumn :func="{ handler: setType, title: $ts.timeline }" :column="column" :is-stacked="isStacked" :indicated="indicated" @change-active-state="onChangeActiveState" @parent-focus="$event => emit('parent-focus', $event)"> <template #header> <i v-if="column.tl === 'home'" class="fas fa-home"></i> <i v-else-if="column.tl === 'local'" class="fas fa-comments"></i> @@ -15,108 +15,103 @@ </p> <p class="desc">{{ $t('disabled-timeline.description') }}</p> </div> - <XTimeline v-else-if="column.tl" ref="timeline" :key="column.tl" :src="column.tl" @after="() => $emit('loaded')" @queue="queueUpdated" @note="onNote"/> + <XTimeline v-else-if="column.tl" ref="timeline" :key="column.tl" :src="column.tl" @after="() => emit('loaded')" @queue="queueUpdated" @note="onNote"/> </XColumn> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted } from 'vue'; import XColumn from './column.vue'; import XTimeline from '@/components/timeline.vue'; import * as os from '@/os'; -import { removeColumn, updateColumn } from './deck-store'; +import { removeColumn, updateColumn, Column } from './deck-store'; +import { $i } from '@/account'; +import { instance } from '@/instance'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - XColumn, - XTimeline, - }, +const props = defineProps<{ + column: Column; + isStacked: boolean; +}>(); - props: { - column: { - type: Object, - required: true - }, - isStacked: { - type: Boolean, - required: true - } - }, +const emit = defineEmits<{ + (e: 'loaded'): void; + (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; +}>(); - data() { - return { - disabled: false, - indicated: false, - columnActive: true, - }; - }, +let disabled = $ref(false); +let indicated = $ref(false); +let columnActive = $ref(true); - watch: { - mediaOnly() { - (this.$refs.timeline as any).reload(); - } - }, +onMounted(() => { + if (props.column.tl == null) { + setType(); + } else if ($i) { + disabled = !$i.isModerator && !$i.isAdmin && ( + instance.disableLocalTimeline && ['local', 'social'].includes(props.column.tl) || + instance.disableGlobalTimeline && ['global'].includes(props.column.tl)); + } +}); - mounted() { - if (this.column.tl == null) { - this.setType(); - } else { - this.disabled = !this.$i.isModerator && !this.$i.isAdmin && ( - this.$instance.disableLocalTimeline && ['local', 'social'].includes(this.column.tl) || - this.$instance.disableGlobalTimeline && ['global'].includes(this.column.tl)); +async function setType() { + const { canceled, result: src } = await os.select({ + title: i18n.ts.timeline, + items: [{ + value: 'home' as const, text: i18n.ts._timelines.home + }, { + value: 'local' as const, text: i18n.ts._timelines.local + }, { + value: 'social' as const, text: i18n.ts._timelines.social + }, { + value: 'global' as const, text: i18n.ts._timelines.global + }], + }); + if (canceled) { + if (props.column.tl == null) { + removeColumn(props.column.id); } - }, + return; + } + updateColumn(props.column.id, { + tl: src + }); +} - methods: { - async setType() { - const { canceled, result: src } = await os.select({ - title: this.$ts.timeline, - items: [{ - value: 'home', text: this.$ts._timelines.home - }, { - value: 'local', text: this.$ts._timelines.local - }, { - value: 'social', text: this.$ts._timelines.social - }, { - value: 'global', text: this.$ts._timelines.global - }] - }); - if (canceled) { - if (this.column.tl == null) { - removeColumn(this.column.id); - } - return; - } - updateColumn(this.column.id, { - tl: src - }); - }, +function queueUpdated(q) { + if (columnActive) { + indicated = q !== 0; + } +} - queueUpdated(q) { - if (this.columnActive) { - this.indicated = q !== 0; - } - }, +function onNote() { + if (!columnActive) { + indicated = true; + } +} - onNote() { - if (!this.columnActive) { - this.indicated = true; - } - }, +function onChangeActiveState(state) { + columnActive = state; - onChangeActiveState(state) { - this.columnActive = state; + if (columnActive) { + indicated = false; + } +} - if (this.columnActive) { - this.indicated = false; - } - }, +/* +export default defineComponent({ + watch: { + mediaOnly() { + (this.$refs.timeline as any).reload(); + } + }, + methods: { focus() { (this.$refs.timeline as any).focus(); } } }); +*/ </script> <style lang="scss" scoped> diff --git a/packages/client/src/ui/deck/widgets-column.vue b/packages/client/src/ui/deck/widgets-column.vue index 8c3a95ac2b..a2edc38357 100644 --- a/packages/client/src/ui/deck/widgets-column.vue +++ b/packages/client/src/ui/deck/widgets-column.vue @@ -1,64 +1,49 @@ <template> -<XColumn :func="{ handler: func, title: $ts.editWidgets }" :naked="true" :column="column" :is-stacked="isStacked"> +<XColumn :func="{ handler: func, title: $ts.editWidgets }" :naked="true" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)"> <template #header><i class="fas fa-window-maximize" style="margin-right: 8px;"></i>{{ column.name }}</template> <div class="wtdtxvec"> - <XWidgets :edit="edit" :widgets="column.widgets" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="edit = false"/> + <XWidgets v-if="column.widgets" :edit="edit" :widgets="column.widgets" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="edit = false"/> </div> </XColumn> </template> -<script lang="ts"> -import { defineComponent, defineAsyncComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import XWidgets from '@/components/widgets.vue'; import XColumn from './column.vue'; -import { addColumnWidget, removeColumnWidget, setColumnWidgets, updateColumnWidget } from './deck-store'; +import { addColumnWidget, Column, removeColumnWidget, setColumnWidgets, updateColumnWidget } from './deck-store'; -export default defineComponent({ - components: { - XColumn, - XWidgets, - }, +const props = defineProps<{ + column: Column; + isStacked: boolean; +}>(); - props: { - column: { - type: Object, - required: true, - }, - isStacked: { - type: Boolean, - required: true, - }, - }, +const emit = defineEmits<{ + (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; +}>(); - data() { - return { - edit: false, - }; - }, +let edit = $ref(false); - methods: { - addWidget(widget) { - addColumnWidget(this.column.id, widget); - }, +function addWidget(widget) { + addColumnWidget(props.column.id, widget); +} - removeWidget(widget) { - removeColumnWidget(this.column.id, widget); - }, +function removeWidget(widget) { + removeColumnWidget(props.column.id, widget); +} - updateWidget({ id, data }) { - updateColumnWidget(this.column.id, id, data); - }, +function updateWidget({ id, data }) { + updateColumnWidget(props.column.id, id, data); +} - updateWidgets(widgets) { - setColumnWidgets(this.column.id, widgets); - }, +function updateWidgets(widgets) { + setColumnWidgets(props.column.id, widgets); +} - func() { - this.edit = !this.edit; - } - } -}); +function func() { + edit = !edit; +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/ui/universal.widgets.vue b/packages/client/src/ui/universal.widgets.vue index fbfafd10ee..2660e80368 100644 --- a/packages/client/src/ui/universal.widgets.vue +++ b/packages/client/src/ui/universal.widgets.vue @@ -1,58 +1,50 @@ <template> <div class="efzpzdvf"> - <XWidgets :edit="editMode" :widgets="$store.reactiveState.widgets.value" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="editMode = false"/> + <XWidgets :edit="editMode" :widgets="defaultStore.reactiveState.widgets.value" @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="fas fa-check"></i> {{ $ts.editWidgetsExit }}</button> - <button v-else class="_textButton" style="font-size: 0.9em;" @click="editMode = true"><i class="fas fa-pencil-alt"></i> {{ $ts.editWidgets }}</button> + <button v-if="editMode" class="_textButton" style="font-size: 0.9em;" @click="editMode = false"><i class="fas fa-check"></i> {{ i18n.ts.editWidgetsExit }}</button> + <button v-else class="_textButton" style="font-size: 0.9em;" @click="editMode = true"><i class="fas fa-pencil-alt"></i> {{ i18n.ts.editWidgets }}</button> </div> </template> -<script lang="ts"> -import { defineComponent, defineAsyncComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted } from 'vue'; import XWidgets from '@/components/widgets.vue'; -import * as os from '@/os'; +import { i18n } from '@/i18n'; +import { defaultStore } from '@/store'; -export default defineComponent({ - components: { - XWidgets - }, +const emit = defineEmits<{ + (e: 'mounted', el: Element): void; +}>(); - emits: ['mounted'], +let editMode = $ref(false); +let rootEl = $ref<HTMLDivElement>(); - data() { - return { - editMode: false, - }; - }, - - mounted() { - this.$emit('mounted', this.$el); - }, +onMounted(() => { + emit('mounted', rootEl); +}); - methods: { - addWidget(widget) { - this.$store.set('widgets', [{ - ...widget, - place: null, - }, ...this.$store.state.widgets]); - }, +function addWidget(widget) { + defaultStore.set('widgets', [{ + ...widget, + place: null, + }, ...defaultStore.state.widgets]); +} - removeWidget(widget) { - this.$store.set('widgets', this.$store.state.widgets.filter(w => w.id != widget.id)); - }, +function removeWidget(widget) { + defaultStore.set('widgets', defaultStore.state.widgets.filter(w => w.id != widget.id)); +} - updateWidget({ id, data }) { - this.$store.set('widgets', this.$store.state.widgets.map(w => w.id === id ? { - ...w, - data: data - } : w)); - }, +function updateWidget({ id, data }) { + defaultStore.set('widgets', defaultStore.state.widgets.map(w => w.id === id ? { + ...w, + data: data + } : w)); +} - updateWidgets(widgets) { - this.$store.set('widgets', widgets); - } - } -}); +function updateWidgets(widgets) { + defaultStore.set('widgets', widgets); +} </script> <style lang="scss" scoped> |