Merge branch 'truncate-push-notification' into swn

This commit is contained in:
tamaina 2021-12-25 01:38:21 +09:00
commit 06f6c66732
18 changed files with 181 additions and 164 deletions

View file

@ -10,6 +10,13 @@
--> -->
## 12.x.x (unreleased)
### Improvements
### Bugfixes
- クライアント: 一部のコンポーネントが裏に隠れるのを修正
## 12.100.2 (2021/12/18) ## 12.100.2 (2021/12/18)
### Bugfixes ### Bugfixes

View file

@ -87,7 +87,7 @@ Configuration files are located in [`/.github/workflows`](/.github/workflows).
## Vue ## Vue
Misskey uses Vue(v3) as its front-end framework. Misskey uses Vue(v3) as its front-end framework.
**When creating a new component, please use the Composition API instead of the Options API.** **When creating a new component, please use the Composition API (and [setup sugar](https://v3.vuejs.org/api/sfc-script-setup.html)) instead of the Options API.**
Some of the existing components are implemented in the Options API, but it is an old implementation. Refactors that migrate those components to the Composition API are also welcome. Some of the existing components are implemented in the Options API, but it is an old implementation. Refactors that migrate those components to the Composition API are also welcome.
## Adding MisskeyRoom items ## Adding MisskeyRoom items

View file

@ -614,7 +614,6 @@ regenerateLoginToken: "ログイントークンを再生成"
regenerateLoginTokenDescription: "ログインに使用される内部トークンを再生成します。通常この操作を行う必要はありません。再生成すると、全てのデバイスでログアウトされます。" regenerateLoginTokenDescription: "ログインに使用される内部トークンを再生成します。通常この操作を行う必要はありません。再生成すると、全てのデバイスでログアウトされます。"
setMultipleBySeparatingWithSpace: "スペースで区切って複数設定できます。" setMultipleBySeparatingWithSpace: "スペースで区切って複数設定できます。"
fileIdOrUrl: "ファイルIDまたはURL" fileIdOrUrl: "ファイルIDまたはURL"
chatOpenBehavior: "チャットを開くときの動作"
behavior: "動作" behavior: "動作"
sample: "サンプル" sample: "サンプル"
abuseReports: "通報" abuseReports: "通報"

View file

@ -4,6 +4,7 @@ block vars
- const user = note.user; - const user = note.user;
- const title = user.name ? `${user.name} (@${user.username})` : `@${user.username}`; - const title = user.name ? `${user.name} (@${user.username})` : `@${user.username}`;
- const url = `${config.url}/notes/${note.id}`; - const url = `${config.url}/notes/${note.id}`;
- const isRenote = note.renote && note.text == null && note.fileIds.length == 0 && note.poll == null;
block title block title
= `${title} | ${instanceName}` = `${title} | ${instanceName}`
@ -19,7 +20,7 @@ block og
meta(property='og:image' content= user.avatarUrl) meta(property='og:image' content= user.avatarUrl)
block meta block meta
if user.host || profile.noCrawle if user.host || isRenote || profile.noCrawle
meta(name='robots' content='noindex') meta(name='robots' content='noindex')
meta(name='misskey:user-username' content=user.username) meta(name='misskey:user-username' content=user.username)

View file

@ -16,7 +16,7 @@ type pushNotificationsTypes = {
}; };
// プッシュメッセージサーバーには文字数制限があるため、内容を削減します // プッシュメッセージサーバーには文字数制限があるため、内容を削減します
function truncateNotification(notification: Packed<'Notification'>): Packed<'Notification'> { function truncateNotification(notification: Packed<'Notification'>): any {
if (notification.note) { if (notification.note) {
return { return {
...notification, ...notification,

View file

@ -106,11 +106,6 @@ export default defineComponent({
return; return;
} }
if (this.to.startsWith('/my/messaging')) {
if (ColdDeviceStorage.get('chatOpenBehavior') === 'window') return this.window();
if (ColdDeviceStorage.get('chatOpenBehavior') === 'popout') return this.popout();
}
if (this.behavior) { if (this.behavior) {
if (this.behavior === 'window') { if (this.behavior === 'window') {
return this.window(); return this.window();

View file

@ -1,7 +1,5 @@
<template> <template>
<component :is="self ? 'MkA' : 'a'" class="ieqqeuvs _link" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target" <component :is="self ? 'MkA' : 'a'" ref="el" class="ieqqeuvs _link" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target"
@mouseover="onMouseover"
@mouseleave="onMouseleave"
@contextmenu.stop="() => {}" @contextmenu.stop="() => {}"
> >
<template v-if="!self"> <template v-if="!self">
@ -20,11 +18,11 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent, ref } from 'vue';
import { toUnicode as decodePunycode } from 'punycode/'; import { toUnicode as decodePunycode } from 'punycode/';
import { url as local } from '@/config'; import { url as local } from '@/config';
import { isTouchUsing } from '@/scripts/touch';
import * as os from '@/os'; import * as os from '@/os';
import { useTooltip } from '@/scripts/use-tooltip';
export default defineComponent({ export default defineComponent({
props: { props: {
@ -35,74 +33,36 @@ export default defineComponent({
rel: { rel: {
type: String, type: String,
required: false, required: false,
default: null,
} }
}, },
data() { setup(props) {
const self = this.url.startsWith(local); const self = props.url.startsWith(local);
const url = new URL(props.url);
const el = ref();
useTooltip(el, (showing) => {
os.popup(import('@/components/url-preview-popup.vue'), {
showing,
url: props.url,
source: el.value,
}, {}, 'closed');
});
return { return {
local, local,
schema: null as string | null, schema: url.protocol,
hostname: null as string | null, hostname: decodePunycode(url.hostname),
port: null as string | null, port: url.port,
pathname: null as string | null, pathname: decodeURIComponent(url.pathname),
query: null as string | null, query: decodeURIComponent(url.search),
hash: null as string | null, hash: decodeURIComponent(url.hash),
self: self, self: self,
attr: self ? 'to' : 'href', attr: self ? 'to' : 'href',
target: self ? null : '_blank', target: self ? null : '_blank',
showTimer: null, el,
hideTimer: null,
checkTimer: null,
close: null,
}; };
}, },
created() {
const url = new URL(this.url);
this.schema = url.protocol;
this.hostname = decodePunycode(url.hostname);
this.port = url.port;
this.pathname = decodeURIComponent(url.pathname);
this.query = decodeURIComponent(url.search);
this.hash = decodeURIComponent(url.hash);
},
methods: {
async showPreview() {
if (!document.body.contains(this.$el)) return;
if (this.close) return;
const { dispose } = await os.popup(import('@/components/url-preview-popup.vue'), {
url: this.url,
source: this.$el
});
this.close = () => {
dispose();
};
this.checkTimer = setInterval(() => {
if (!document.body.contains(this.$el)) this.closePreview();
}, 1000);
},
closePreview() {
if (this.close) {
clearInterval(this.checkTimer);
this.close();
this.close = null;
}
},
onMouseover() {
if (isTouchUsing) return;
clearTimeout(this.showTimer);
clearTimeout(this.hideTimer);
this.showTimer = setTimeout(this.showPreview, 500);
},
onMouseleave() {
if (isTouchUsing) return;
clearTimeout(this.showTimer);
clearTimeout(this.hideTimer);
this.hideTimer = setTimeout(this.closePreview, 500);
}
}
}); });
</script> </script>

View file

@ -105,6 +105,7 @@ export default defineComponent({
return { return {
previewable, previewable,
gallery, gallery,
pswpZIndex: os.claimZIndex('middle'),
}; };
}, },
}); });
@ -188,3 +189,11 @@ export default defineComponent({
} }
} }
</style> </style>
<style lang="scss">
.pswp {
//
//z-index: v-bind(pswpZIndex);
z-index: 2000000;
}
</style>

View file

@ -16,7 +16,13 @@
<template #headerLeft> <template #headerLeft>
<button v-if="history.length > 0" v-tooltip="$ts.goBack" class="_button" @click="back()"><i class="fas fa-arrow-left"></i></button> <button v-if="history.length > 0" v-tooltip="$ts.goBack" class="_button" @click="back()"><i class="fas fa-arrow-left"></i></button>
</template> </template>
<div class="yrolvcoq"> <template #headerRight>
<button v-tooltip="$ts.showInPage" class="_button" @click="expand()"><i class="fas fa-expand-alt"></i></button>
<button v-tooltip="$ts.popout" class="_button" @click="popout()"><i class="fas fa-external-link-alt"></i></button>
<button class="_button" @click="menu"><i class="fas fa-ellipsis-h"></i></button>
</template>
<div class="yrolvcoq" :style="{ background: pageInfo?.bg }">
<MkStickyContainer> <MkStickyContainer>
<template #header><MkHeader v-if="pageInfo && !pageInfo.hideHeader" :info="pageInfo"/></template> <template #header><MkHeader v-if="pageInfo && !pageInfo.hideHeader" :info="pageInfo"/></template>
<component :is="component" v-bind="props" :ref="changePage"/> <component :is="component" v-bind="props" :ref="changePage"/>
@ -33,6 +39,7 @@ import copyToClipboard from '@/scripts/copy-to-clipboard';
import { resolve } from '@/router'; import { resolve } from '@/router';
import { url } from '@/config'; import { url } from '@/config';
import * as symbols from '@/symbols'; import * as symbols from '@/symbols';
import * as os from '@/os';
export default defineComponent({ export default defineComponent({
components: { components: {
@ -139,6 +146,23 @@ export default defineComponent({
this.props = props; this.props = props;
}, },
menu(ev) {
os.popupMenu([{
icon: 'fas fa-external-link-alt',
text: this.$ts.openInNewTab,
action: () => {
window.open(this.url, '_blank');
this.$refs.window.close();
}
}, {
icon: 'fas fa-link',
text: this.$ts.copyLink,
action: () => {
copyToClipboard(this.url);
}
}], ev.currentTarget || ev.target);
},
back() { back() {
this.navigate(this.history.pop(), false); this.navigate(this.history.pop(), false);
}, },

View file

@ -284,7 +284,7 @@ export default defineComponent({
} }
&.asDrawer { &.asDrawer {
padding: 12px 0; padding: 12px 0 calc(env(safe-area-inset-bottom, 0px) + 12px) 0;
width: 100%; width: 100%;
> .item { > .item {

View file

@ -5,7 +5,12 @@
<MkError v-else-if="error" @retry="init()"/> <MkError v-else-if="error" @retry="init()"/>
<div v-else-if="empty" key="_empty_" class="empty"> <div v-else-if="empty" key="_empty_" class="empty">
<slot name="empty"></slot> <slot name="empty">
<div class="_fullinfo">
<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
<div>{{ $ts.nothing }}</div>
</div>
</slot>
</div> </div>
<div v-else class="cxiknjgy"> <div v-else class="cxiknjgy">

View file

@ -414,6 +414,10 @@ export default defineComponent({
} }
} }
> .left {
min-width: 16px;
}
> .title { > .title {
flex: 1; flex: 1;
position: relative; position: relative;
@ -421,7 +425,6 @@ export default defineComponent({
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
text-align: center;
cursor: move; cursor: move;
} }
} }

View file

@ -1,12 +1,11 @@
<template> <template>
<div> <MkSpacer :content-max="700">
<div class="_section"> <div class="_formRoot">
<div class="_content"> <MkInput v-model="name" class="_formBlock">
<MkInput v-model="name">
<template #label>{{ $ts.name }}</template> <template #label>{{ $ts.name }}</template>
</MkInput> </MkInput>
<MkTextarea v-model="description"> <MkTextarea v-model="description" class="_formBlock">
<template #label>{{ $ts.description }}</template> <template #label>{{ $ts.description }}</template>
</MkTextarea> </MkTextarea>
@ -17,12 +16,11 @@
<MkButton @click="removeBannerImage()"><i class="fas fa-trash-alt"></i> {{ $ts._channel.removeBanner }}</MkButton> <MkButton @click="removeBannerImage()"><i class="fas fa-trash-alt"></i> {{ $ts._channel.removeBanner }}</MkButton>
</div> </div>
</div> </div>
</div> <div class="_formBlock">
<div class="_footer">
<MkButton primary @click="save()"><i class="fas fa-save"></i> {{ channelId ? $ts.save : $ts.create }}</MkButton> <MkButton primary @click="save()"><i class="fas fa-save"></i> {{ channelId ? $ts.save : $ts.create }}</MkButton>
</div> </div>
</div> </div>
</div> </MkSpacer>
</template> </template>
<script lang="ts"> <script lang="ts">
@ -51,9 +49,11 @@ export default defineComponent({
[symbols.PAGE_INFO]: computed(() => this.channelId ? { [symbols.PAGE_INFO]: computed(() => this.channelId ? {
title: this.$ts._channel.edit, title: this.$ts._channel.edit,
icon: 'fas fa-satellite-dish', icon: 'fas fa-satellite-dish',
bg: 'var(--bg)',
} : { } : {
title: this.$ts._channel.create, title: this.$ts._channel.create,
icon: 'fas fa-satellite-dish', icon: 'fas fa-satellite-dish',
bg: 'var(--bg)',
}), }),
channel: null, channel: null,
name: null, name: null,

View file

@ -1,6 +1,7 @@
<template> <template>
<div v-if="channel" class="_section"> <MkSpacer :content-max="700">
<div class="wpgynlbz _content _panel _gap" :class="{ hide: !showBanner }"> <div v-if="channel">
<div class="wpgynlbz _panel _gap" :class="{ hide: !showBanner }">
<XChannelFollowButton :channel="channel" :full="true" class="subscribe"/> <XChannelFollowButton :channel="channel" :full="true" class="subscribe"/>
<button class="_button toggle" @click="() => showBanner = !showBanner"> <button class="_button toggle" @click="() => showBanner = !showBanner">
<template v-if="showBanner"><i class="fas fa-angle-up"></i></template> <template v-if="showBanner"><i class="fas fa-angle-up"></i></template>
@ -20,10 +21,11 @@
</div> </div>
</div> </div>
<XPostForm v-if="$i" :channel="channel" class="post-form _content _panel _gap" fixed/> <XPostForm v-if="$i" :channel="channel" class="post-form _panel _gap" fixed/>
<XTimeline :key="channelId" class="_content _gap" src="channel" :channel="channelId" @before="before" @after="after"/> <XTimeline :key="channelId" class="_gap" src="channel" :channel="channelId" @before="before" @after="after"/>
</div> </div>
</MkSpacer>
</template> </template>
<script lang="ts"> <script lang="ts">
@ -55,6 +57,12 @@ export default defineComponent({
[symbols.PAGE_INFO]: computed(() => this.channel ? { [symbols.PAGE_INFO]: computed(() => this.channel ? {
title: this.channel.name, title: this.channel.name,
icon: 'fas fa-satellite-dish', icon: 'fas fa-satellite-dish',
bg: 'var(--bg)',
actions: [...(this.$i && this.$i.id === this.channel.userId ? [{
icon: 'fas fa-cog',
text: this.$ts.edit,
handler: this.edit,
}] : [])],
} : null), } : null),
channel: null, channel: null,
showBanner: true, showBanner: true,
@ -79,8 +87,10 @@ export default defineComponent({
} }
}, },
created() { methods: {
edit() {
this.$router.push(`/channels/${this.channel.id}/edit`);
}
}, },
}); });
</script> </script>

View file

@ -1,58 +1,63 @@
<template> <template>
<div> <MkSpacer :content-max="700">
<div v-if="$i" class="_section" style="padding: 0;">
<MkTab v-model="tab" class="_content">
<option value="featured"><i class="fas fa-fire-alt"></i> {{ $ts._channel.featured }}</option>
<option value="following"><i class="fas fa-heart"></i> {{ $ts._channel.following }}</option>
<option value="owned"><i class="fas fa-edit"></i> {{ $ts._channel.owned }}</option>
</MkTab>
</div>
<div class="_section">
<div v-if="tab === 'featured'" class="_content grwlizim featured"> <div v-if="tab === 'featured'" class="_content grwlizim featured">
<MkPagination v-slot="{items}" :pagination="featuredPagination"> <MkPagination v-slot="{items}" :pagination="featuredPagination">
<MkChannelPreview v-for="channel in items" :key="channel.id" class="_gap" :channel="channel"/> <MkChannelPreview v-for="channel in items" :key="channel.id" class="_gap" :channel="channel"/>
</MkPagination> </MkPagination>
</div> </div>
<div v-else-if="tab === 'following'" class="_content grwlizim following">
<div v-if="tab === 'following'" class="_content grwlizim following">
<MkPagination v-slot="{items}" :pagination="followingPagination"> <MkPagination v-slot="{items}" :pagination="followingPagination">
<MkChannelPreview v-for="channel in items" :key="channel.id" class="_gap" :channel="channel"/> <MkChannelPreview v-for="channel in items" :key="channel.id" class="_gap" :channel="channel"/>
</MkPagination> </MkPagination>
</div> </div>
<div v-else-if="tab === 'owned'" class="_content grwlizim owned">
<div v-if="tab === 'owned'" class="_content grwlizim owned">
<MkButton class="new" @click="create()"><i class="fas fa-plus"></i></MkButton> <MkButton class="new" @click="create()"><i class="fas fa-plus"></i></MkButton>
<MkPagination v-slot="{items}" :pagination="ownedPagination"> <MkPagination v-slot="{items}" :pagination="ownedPagination">
<MkChannelPreview v-for="channel in items" :key="channel.id" class="_gap" :channel="channel"/> <MkChannelPreview v-for="channel in items" :key="channel.id" class="_gap" :channel="channel"/>
</MkPagination> </MkPagination>
</div> </div>
</div> </MkSpacer>
</div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { computed, defineComponent } from 'vue';
import MkChannelPreview from '@/components/channel-preview.vue'; import MkChannelPreview from '@/components/channel-preview.vue';
import MkPagination from '@/components/ui/pagination.vue'; import MkPagination from '@/components/ui/pagination.vue';
import MkButton from '@/components/ui/button.vue'; import MkButton from '@/components/ui/button.vue';
import MkTab from '@/components/tab.vue';
import * as symbols from '@/symbols'; import * as symbols from '@/symbols';
export default defineComponent({ export default defineComponent({
components: { components: {
MkChannelPreview, MkPagination, MkButton, MkTab MkChannelPreview, MkPagination, MkButton,
}, },
data() { data() {
return { return {
[symbols.PAGE_INFO]: { [symbols.PAGE_INFO]: computed(() => ({
title: this.$ts.channel, title: this.$ts.channel,
icon: 'fas fa-satellite-dish', icon: 'fas fa-satellite-dish',
action: { bg: 'var(--bg)',
actions: [{
icon: 'fas fa-plus', icon: 'fas fa-plus',
handler: this.create text: this.$ts.create,
} handler: this.create,
}, }],
tabs: [{
active: this.tab === 'featured',
title: this.$ts._channel.featured,
icon: 'fas fa-fire-alt',
onClick: () => { this.tab = 'featured'; },
}, {
active: this.tab === 'following',
title: this.$ts._channel.following,
icon: 'fas fa-heart',
onClick: () => { this.tab = 'following'; },
}, {
active: this.tab === 'owned',
title: this.$ts._channel.owned,
icon: 'fas fa-edit',
onClick: () => { this.tab = 'owned'; },
},]
})),
tab: 'featured', tab: 'featured',
featuredPagination: { featuredPagination: {
endpoint: 'channels/featured', endpoint: 'channels/featured',

View file

@ -77,13 +77,6 @@
<FormSwitch v-model="defaultSideView">{{ $ts.openInSideView }}</FormSwitch> <FormSwitch v-model="defaultSideView">{{ $ts.openInSideView }}</FormSwitch>
</FormGroup> </FormGroup>
<FormSelect v-model="chatOpenBehavior" class="_formBlock">
<template #label>{{ $ts.chatOpenBehavior }}</template>
<option value="page">{{ $ts.showInPage }}</option>
<option value="window">{{ $ts.openInWindow }}</option>
<option value="popout">{{ $ts.popout }}</option>
</FormSelect>
<FormLink to="/settings/deck" class="_formBlock">{{ $ts.deck }}</FormLink> <FormLink to="/settings/deck" class="_formBlock">{{ $ts.deck }}</FormLink>
<FormLink to="/settings/custom-css" class="_formBlock"><template #icon><i class="fas fa-code"></i></template>{{ $ts.customCss }}</FormLink> <FormLink to="/settings/custom-css" class="_formBlock"><template #icon><i class="fas fa-code"></i></template>{{ $ts.customCss }}</FormLink>
@ -149,7 +142,6 @@ export default defineComponent({
disablePagesScript: defaultStore.makeGetterSetter('disablePagesScript'), disablePagesScript: defaultStore.makeGetterSetter('disablePagesScript'),
showFixedPostForm: defaultStore.makeGetterSetter('showFixedPostForm'), showFixedPostForm: defaultStore.makeGetterSetter('showFixedPostForm'),
defaultSideView: defaultStore.makeGetterSetter('defaultSideView'), defaultSideView: defaultStore.makeGetterSetter('defaultSideView'),
chatOpenBehavior: ColdDeviceStorage.makeGetterSetter('chatOpenBehavior'),
instanceTicker: defaultStore.makeGetterSetter('instanceTicker'), instanceTicker: defaultStore.makeGetterSetter('instanceTicker'),
enableInfiniteScroll: defaultStore.makeGetterSetter('enableInfiniteScroll'), enableInfiniteScroll: defaultStore.makeGetterSetter('enableInfiniteScroll'),
useReactionPickerForContextMenu: defaultStore.makeGetterSetter('useReactionPickerForContextMenu'), useReactionPickerForContextMenu: defaultStore.makeGetterSetter('useReactionPickerForContextMenu'),

View file

@ -1,4 +1,4 @@
import { Ref, ref, watch } from 'vue'; import { Ref, ref, watch, onUnmounted } from 'vue';
export function useTooltip( export function useTooltip(
elRef: Ref<HTMLElement | { $el: HTMLElement } | null | undefined>, elRef: Ref<HTMLElement | { $el: HTMLElement } | null | undefined>,
@ -18,6 +18,9 @@ export function useTooltip(
const open = () => { const open = () => {
close(); close();
if (!isHovering) return; if (!isHovering) return;
if (elRef.value == null) return;
const el = elRef.value instanceof Element ? elRef.value : elRef.value.$el;
if (!document.body.contains(el)) return; // openしようとしたときに既に元要素がDOMから消えている場合があるため
const showing = ref(true); const showing = ref(true);
onShow(showing); onShow(showing);
@ -69,9 +72,14 @@ export function useTooltip(
el.addEventListener('mouseleave', onMouseleave, { passive: true }); el.addEventListener('mouseleave', onMouseleave, { passive: true });
el.addEventListener('touchstart', onTouchstart, { passive: true }); el.addEventListener('touchstart', onTouchstart, { passive: true });
el.addEventListener('touchend', onTouchend, { passive: true }); el.addEventListener('touchend', onTouchend, { passive: true });
el.addEventListener('click', close, { passive: true });
} }
}, { }, {
immediate: true, immediate: true,
flush: 'post', flush: 'post',
}); });
onUnmounted(() => {
close();
});
} }

View file

@ -245,7 +245,6 @@ export class ColdDeviceStorage {
lightTheme: require('@/themes/l-light.json5') as Theme, lightTheme: require('@/themes/l-light.json5') as Theme,
darkTheme: require('@/themes/d-dark.json5') as Theme, darkTheme: require('@/themes/d-dark.json5') as Theme,
syncDeviceDarkMode: true, syncDeviceDarkMode: true,
chatOpenBehavior: 'page' as 'page' | 'window' | 'popout',
plugins: [] as Plugin[], plugins: [] as Plugin[],
mediaVolume: 0.5, mediaVolume: 0.5,
sound_masterVolume: 0.3, sound_masterVolume: 0.3,