summaryrefslogtreecommitdiff
path: root/packages/client/src
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2022-04-02 15:34:03 +0900
committersyuilo <Syuilotan@yahoo.co.jp>2022-04-02 15:34:03 +0900
commit2375359d129b63988b0658f735e1d9c014c10d71 (patch)
treec8b233d00e4b3cbb41b0d211845bdb0cca803267 /packages/client/src
parentMerge branch 'develop' (diff)
parent12.109.0 (diff)
downloadmisskey-2375359d129b63988b0658f735e1d9c014c10d71.tar.gz
misskey-2375359d129b63988b0658f735e1d9c014c10d71.tar.bz2
misskey-2375359d129b63988b0658f735e1d9c014c10d71.zip
Merge branch 'develop'
Diffstat (limited to 'packages/client/src')
-rw-r--r--packages/client/src/account.ts9
-rw-r--r--packages/client/src/components/form/link.vue2
-rw-r--r--packages/client/src/components/global/url.vue14
-rw-r--r--packages/client/src/components/launch-pad.vue4
-rw-r--r--packages/client/src/components/media-list.vue6
-rw-r--r--packages/client/src/components/notifications.vue4
-rw-r--r--packages/client/src/components/ui/modal.vue24
-rw-r--r--packages/client/src/os.ts16
-rw-r--r--packages/client/src/pages/admin/bot-protection.vue2
-rw-r--r--packages/client/src/pages/admin/email-settings.vue2
-rw-r--r--packages/client/src/pages/admin/instance-block.vue2
-rw-r--r--packages/client/src/pages/admin/integrations.discord.vue2
-rw-r--r--packages/client/src/pages/admin/integrations.github.vue2
-rw-r--r--packages/client/src/pages/admin/integrations.twitter.vue2
-rw-r--r--packages/client/src/pages/admin/integrations.vue2
-rw-r--r--packages/client/src/pages/admin/object-storage.vue2
-rw-r--r--packages/client/src/pages/admin/other-settings.vue2
-rw-r--r--packages/client/src/pages/admin/proxy-account.vue2
-rw-r--r--packages/client/src/pages/admin/queue.vue9
-rw-r--r--packages/client/src/pages/admin/security.vue2
-rw-r--r--packages/client/src/pages/admin/settings.vue2
-rw-r--r--packages/client/src/pages/my-antennas/editor.vue6
-rw-r--r--packages/client/src/pages/settings/index.vue8
-rw-r--r--packages/client/src/pages/settings/profile.vue2
-rw-r--r--packages/client/src/pages/settings/webhook.edit.vue89
-rw-r--r--packages/client/src/pages/settings/webhook.new.vue81
-rw-r--r--packages/client/src/pages/settings/webhook.vue52
-rw-r--r--packages/client/src/pages/settings/word-mute.vue2
-rw-r--r--packages/client/src/pages/user-info.vue1
-rw-r--r--packages/client/src/ui/classic.header.vue1
-rw-r--r--packages/client/src/ui/classic.sidebar.vue4
-rw-r--r--packages/client/src/ui/deck.vue163
-rw-r--r--packages/client/src/ui/deck/antenna-column.vue95
-rw-r--r--packages/client/src/ui/deck/column-core.vue54
-rw-r--r--packages/client/src/ui/deck/column.vue383
-rw-r--r--packages/client/src/ui/deck/deck-store.ts10
-rw-r--r--packages/client/src/ui/deck/direct-column.vue22
-rw-r--r--packages/client/src/ui/deck/list-column.vue92
-rw-r--r--packages/client/src/ui/deck/main-column.vue109
-rw-r--r--packages/client/src/ui/deck/mentions-column.vue12
-rw-r--r--packages/client/src/ui/deck/notifications-column.vue61
-rw-r--r--packages/client/src/ui/deck/tl-column.vue155
-rw-r--r--packages/client/src/ui/deck/widgets-column.vue71
-rw-r--r--packages/client/src/ui/universal.widgets.vue74
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>