Merge branch 'develop' into hazelnoot/following-timeline
This commit is contained in:
commit
97a16c2ebc
|
@ -292,6 +292,7 @@ removeAreYouSure: "Are you sure that you want to remove \"{x}\"?"
|
||||||
deleteAreYouSure: "Are you sure that you want to delete \"{x}\"?"
|
deleteAreYouSure: "Are you sure that you want to delete \"{x}\"?"
|
||||||
resetAreYouSure: "Really reset?"
|
resetAreYouSure: "Really reset?"
|
||||||
areYouSure: "Are you sure?"
|
areYouSure: "Are you sure?"
|
||||||
|
confirmRemoteUrl: "Are you sure that you want to go to \"{x}\"?"
|
||||||
saved: "Saved"
|
saved: "Saved"
|
||||||
messaging: "Chat"
|
messaging: "Chat"
|
||||||
upload: "Upload"
|
upload: "Upload"
|
||||||
|
@ -1410,6 +1411,7 @@ _initialTutorial:
|
||||||
home: "You can view notes from accounts you follow."
|
home: "You can view notes from accounts you follow."
|
||||||
local: "You can view notes from all users on this server."
|
local: "You can view notes from all users on this server."
|
||||||
social: "Notes from the Home and Local timelines will be displayed."
|
social: "Notes from the Home and Local timelines will be displayed."
|
||||||
|
bubble: "You can view notes from connected servers picked by your admins."
|
||||||
global: "You can view notes from all connected servers."
|
global: "You can view notes from all connected servers."
|
||||||
description2: "You can switch between timelines at the top of the screen at any time."
|
description2: "You can switch between timelines at the top of the screen at any time."
|
||||||
description3: "Additionally, there are list timelines and channel timelines. For more details, please refer to {link}."
|
description3: "Additionally, there are list timelines and channel timelines. For more details, please refer to {link}."
|
||||||
|
@ -1448,6 +1450,7 @@ _timelineDescription:
|
||||||
home: "In the Home timeline, you can see notes from accounts you follow."
|
home: "In the Home timeline, you can see notes from accounts you follow."
|
||||||
local: "In the Local timeline, you can see notes from all users on this server."
|
local: "In the Local timeline, you can see notes from all users on this server."
|
||||||
social: "The Social timeline displays notes from both the Home and Local timelines."
|
social: "The Social timeline displays notes from both the Home and Local timelines."
|
||||||
|
bubble: "In the Bubble timeline, you can see notes from connected servers picked by your admins."
|
||||||
global: "In the Global timeline, you can see notes from all connected servers."
|
global: "In the Global timeline, you can see notes from all connected servers."
|
||||||
_serverRules:
|
_serverRules:
|
||||||
description: "A set of rules to be displayed before registration. Setting a summary of the Terms of Service is recommended."
|
description: "A set of rules to be displayed before registration. Setting a summary of the Terms of Service is recommended."
|
||||||
|
|
|
@ -1184,6 +1184,10 @@ export interface Locale extends ILocale {
|
||||||
* よろしいですか?
|
* よろしいですか?
|
||||||
*/
|
*/
|
||||||
"areYouSure": string;
|
"areYouSure": string;
|
||||||
|
/**
|
||||||
|
* 「{x}」を開きますか?
|
||||||
|
*/
|
||||||
|
"confirmRemoteUrl": ParameterizedString<"x">;
|
||||||
/**
|
/**
|
||||||
* 保存しました
|
* 保存しました
|
||||||
*/
|
*/
|
||||||
|
@ -5635,6 +5639,10 @@ export interface Locale extends ILocale {
|
||||||
* 接続している他のすべてのサーバーからの投稿を見られます。
|
* 接続している他のすべてのサーバーからの投稿を見られます。
|
||||||
*/
|
*/
|
||||||
"global": string;
|
"global": string;
|
||||||
|
/**
|
||||||
|
* 管理者が選択した他の接続サーバーの投稿を見ることができます。
|
||||||
|
*/
|
||||||
|
"bubble": string;
|
||||||
/**
|
/**
|
||||||
* それぞれのタイムラインは、画面上部でいつでも切り替えられます。
|
* それぞれのタイムラインは、画面上部でいつでも切り替えられます。
|
||||||
*/
|
*/
|
||||||
|
@ -5768,6 +5776,10 @@ export interface Locale extends ILocale {
|
||||||
* ソーシャルタイムラインには、ホームタイムラインとローカルタイムラインの投稿が両方表示されます。
|
* ソーシャルタイムラインには、ホームタイムラインとローカルタイムラインの投稿が両方表示されます。
|
||||||
*/
|
*/
|
||||||
"social": string;
|
"social": string;
|
||||||
|
/**
|
||||||
|
* バッッブルタイムラインでは、管理者が選択した接続サーバーからのメモを表示できます。
|
||||||
|
*/
|
||||||
|
"bubble": string;
|
||||||
/**
|
/**
|
||||||
* グローバルタイムラインでは、接続している他のすべてのサーバーからの投稿を見られます。
|
* グローバルタイムラインでは、接続している他のすべてのサーバーからの投稿を見られます。
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -292,6 +292,7 @@ removeAreYouSure: "「{x}」を削除しますか?"
|
||||||
deleteAreYouSure: "「{x}」を削除しますか?"
|
deleteAreYouSure: "「{x}」を削除しますか?"
|
||||||
resetAreYouSure: "リセットしますか?"
|
resetAreYouSure: "リセットしますか?"
|
||||||
areYouSure: "よろしいですか?"
|
areYouSure: "よろしいですか?"
|
||||||
|
confirmRemoteUrl: "「{x}」を開きますか?"
|
||||||
saved: "保存しました"
|
saved: "保存しました"
|
||||||
messaging: "チャット"
|
messaging: "チャット"
|
||||||
upload: "アップロード"
|
upload: "アップロード"
|
||||||
|
@ -1416,6 +1417,7 @@ _initialTutorial:
|
||||||
local: "このサーバーにいるユーザー全員の投稿を見られます。"
|
local: "このサーバーにいるユーザー全員の投稿を見られます。"
|
||||||
social: "ホームタイムラインとローカルタイムラインの投稿が両方表示されます。"
|
social: "ホームタイムラインとローカルタイムラインの投稿が両方表示されます。"
|
||||||
global: "接続している他のすべてのサーバーからの投稿を見られます。"
|
global: "接続している他のすべてのサーバーからの投稿を見られます。"
|
||||||
|
bubble: "管理者が選択した他の接続サーバーの投稿を見ることができます。"
|
||||||
description2: "それぞれのタイムラインは、画面上部でいつでも切り替えられます。"
|
description2: "それぞれのタイムラインは、画面上部でいつでも切り替えられます。"
|
||||||
description3: "その他にも、リストタイムラインやチャンネルタイムラインなどがあります。詳しくは{link}をご覧ください。"
|
description3: "その他にも、リストタイムラインやチャンネルタイムラインなどがあります。詳しくは{link}をご覧ください。"
|
||||||
_postNote:
|
_postNote:
|
||||||
|
@ -1454,6 +1456,7 @@ _timelineDescription:
|
||||||
home: "ホームタイムラインでは、あなたがフォローしているアカウントの投稿を見られます。"
|
home: "ホームタイムラインでは、あなたがフォローしているアカウントの投稿を見られます。"
|
||||||
local: "ローカルタイムラインでは、このサーバーにいるユーザー全員の投稿を見られます。"
|
local: "ローカルタイムラインでは、このサーバーにいるユーザー全員の投稿を見られます。"
|
||||||
social: "ソーシャルタイムラインには、ホームタイムラインとローカルタイムラインの投稿が両方表示されます。"
|
social: "ソーシャルタイムラインには、ホームタイムラインとローカルタイムラインの投稿が両方表示されます。"
|
||||||
|
bubble: "バブルタイムラインでは、管理者が選択した接続サーバーからの投稿を表示できます。"
|
||||||
global: "グローバルタイムラインでは、接続している他のすべてのサーバーからの投稿を見られます。"
|
global: "グローバルタイムラインでは、接続している他のすべてのサーバーからの投稿を見られます。"
|
||||||
|
|
||||||
_serverRules:
|
_serverRules:
|
||||||
|
@ -2403,7 +2406,7 @@ _timelines:
|
||||||
local: "ローカル"
|
local: "ローカル"
|
||||||
social: "ソーシャル"
|
social: "ソーシャル"
|
||||||
global: "グローバル"
|
global: "グローバル"
|
||||||
bubble: "バッッブル"
|
bubble: "バブル"
|
||||||
|
|
||||||
_play:
|
_play:
|
||||||
new: "Playの作成"
|
new: "Playの作成"
|
||||||
|
|
|
@ -247,6 +247,11 @@ export class NoteEditService implements OnApplicationShutdown {
|
||||||
data.reply = undefined;
|
data.reply = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// changing visibility on an edit is ill-defined, let's try to
|
||||||
|
// keep the same visibility as the original note
|
||||||
|
data.visibility = oldnote.visibility;
|
||||||
|
data.localOnly = oldnote.localOnly;
|
||||||
|
|
||||||
// チャンネル外にリプライしたら対象のスコープに合わせる
|
// チャンネル外にリプライしたら対象のスコープに合わせる
|
||||||
// (クライアントサイドでやっても良い処理だと思うけどとりあえずサーバーサイドで)
|
// (クライアントサイドでやっても良い処理だと思うけどとりあえずサーバーサイドで)
|
||||||
if (data.reply && data.channel && data.reply.channelId !== data.channel.id) {
|
if (data.reply && data.channel && data.reply.channelId !== data.channel.id) {
|
||||||
|
@ -429,9 +434,6 @@ export class NoteEditService implements OnApplicationShutdown {
|
||||||
if (data.cw !== oldnote.cw) {
|
if (data.cw !== oldnote.cw) {
|
||||||
update.cw = data.cw;
|
update.cw = data.cw;
|
||||||
}
|
}
|
||||||
if (data.localOnly !== oldnote.localOnly) {
|
|
||||||
update.localOnly = data.localOnly;
|
|
||||||
}
|
|
||||||
if (oldnote.hasPoll !== !!data.poll) {
|
if (oldnote.hasPoll !== !!data.poll) {
|
||||||
update.hasPoll = !!data.poll;
|
update.hasPoll = !!data.poll;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,40 +4,49 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="bcekxzvu _margin _panel">
|
<div class="bcekxzvu _margin _panel">
|
||||||
<div class="target">
|
<div class="target">
|
||||||
<MkA v-user-preview="report.targetUserId" class="info" :to="`/admin/user/${report.targetUserId}`" :behavior="'window'">
|
<MkA v-user-preview="report.targetUserId" class="info" :to="`/admin/user/${report.targetUserId}`" :behavior="'window'">
|
||||||
<MkAvatar class="avatar" :user="report.targetUser" indicator/>
|
<MkAvatar class="avatar" :user="report.targetUser" indicator/>
|
||||||
<div class="names">
|
<div class="names">
|
||||||
<MkUserName class="name" :user="report.targetUser"/>
|
<MkUserName class="name" :user="report.targetUser"/>
|
||||||
<MkAcct class="acct" :user="report.targetUser" style="display: block;"/>
|
<MkAcct class="acct" :user="report.targetUser" style="display: block;"/>
|
||||||
|
</div>
|
||||||
|
</MkA>
|
||||||
|
<div class="keyvalCtn">
|
||||||
|
<MkKeyValue>
|
||||||
|
<template #key>{{ i18n.ts.registeredDate }}</template>
|
||||||
|
<template #value>{{ dateString(report.targetUser.createdAt) }} (<MkTime :time="report.targetUser.createdAt"/>)</template>
|
||||||
|
</MkKeyValue>
|
||||||
|
<MkKeyValue>
|
||||||
|
<template #key>{{ i18n.ts.reporter }}</template>
|
||||||
|
<template #value><MkA :to="`/admin/user/${report.reporter.id}`" class="_link" :behavior="'window'">@{{ report.reporter.username }}</MkA></template>
|
||||||
|
</MkKeyValue>
|
||||||
|
<MkKeyValue>
|
||||||
|
<template #key>{{ i18n.ts.createdAt }}</template>
|
||||||
|
<template #value><MkTime :time="report.createdAt" mode="absolute"/> (<MkTime :time="report.createdAt" mode="relative"/>)</template>
|
||||||
|
</MkKeyValue>
|
||||||
</div>
|
</div>
|
||||||
</MkA>
|
<hr>
|
||||||
<MkKeyValue>
|
|
||||||
<template #key>{{ i18n.ts.registeredDate }}</template>
|
|
||||||
<template #value>{{ dateString(report.targetUser.createdAt) }} (<MkTime :time="report.targetUser.createdAt"/>)</template>
|
|
||||||
</MkKeyValue>
|
|
||||||
</div>
|
|
||||||
<div class="detail">
|
|
||||||
<div>
|
|
||||||
<Mfm :text="report.comment" :isBlock="true" :linkNavigationBehavior="'window'"/>
|
|
||||||
</div>
|
</div>
|
||||||
<hr/>
|
<div class="detail">
|
||||||
<div>{{ i18n.ts.reporter }}: <MkA :to="`/admin/user/${report.reporter.id}`" class="_link" :behavior="'window'">@{{ report.reporter.username }}</MkA></div>
|
<div>
|
||||||
<div v-if="report.assignee">
|
<Mfm :text="report.comment" :isBlock="true" :linkNavigationBehavior="'window'"/>
|
||||||
{{ i18n.ts.moderator }}:
|
</div>
|
||||||
<MkAcct :user="report.assignee"/>
|
<hr/>
|
||||||
</div>
|
<div v-if="report.assignee" class="assignee">
|
||||||
<div><MkTime :time="report.createdAt"/></div>
|
{{ i18n.ts.moderator }}:
|
||||||
<div class="action">
|
<MkA :to="`/admin/user/${report.assignee.id}`" class="_link" :behavior="'window'">@{{ report.assignee.username }}</MkA>
|
||||||
<MkSwitch v-model="forward" :disabled="report.targetUser.host == null || report.resolved">
|
</div>
|
||||||
{{ i18n.ts.forwardReport }}
|
<div class="action">
|
||||||
<template #caption>{{ i18n.ts.forwardReportIsAnonymous }}</template>
|
<MkSwitch v-model="forward" c:disabled="report.targetUser.host == null || report.resolved">
|
||||||
</MkSwitch>
|
{{ i18n.ts.forwardReport }}
|
||||||
<MkButton v-if="!report.resolved" primary @click="resolve">{{ i18n.ts.abuseMarkAsResolved }}</MkButton>
|
<template #caption>{{ i18n.ts.forwardReportIsAnonymous }}</template>
|
||||||
|
</MkSwitch>
|
||||||
|
<MkButton v-if="!report.resolved" primary @click="resolve">{{ i18n.ts.abuseMarkAsResolved }}</MkButton>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
@ -72,13 +81,13 @@ function resolve() {
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.bcekxzvu {
|
.bcekxzvu {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
transition: .1s;
|
||||||
|
|
||||||
> .target {
|
> .target {
|
||||||
width: 35%;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
padding: 24px;
|
padding: 24px 24px 0px 24px;
|
||||||
border-right: solid 1px var(--divider);
|
|
||||||
|
|
||||||
> .info {
|
> .info {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -100,16 +109,36 @@ function resolve() {
|
||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
||||||
|
white-space: pre;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
> .name {
|
> .name {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> .keyvalCtn {
|
||||||
|
display: inline-flex;
|
||||||
|
gap: 15px;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .detail {
|
> .detail {
|
||||||
flex: 1;
|
display: flex;
|
||||||
padding: 24px;
|
flex-direction: column;
|
||||||
|
padding: 0px 24px 24px 24px;
|
||||||
|
|
||||||
|
.assignee {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkLoading v-else-if="type === 'waiting'" :class="$style.iconInner" :em="true"/>
|
<MkLoading v-else-if="type === 'waiting'" :class="$style.iconInner" :em="true"/>
|
||||||
</div>
|
</div>
|
||||||
<header v-if="title" :class="$style.title"><Mfm :text="title"/></header>
|
<header v-if="title" :class="$style.title"><Mfm :text="title"/></header>
|
||||||
<div v-if="text" :class="$style.text"><Mfm :text="text" :isBlock="true" /></div>
|
<div v-if="text" :class="$style.text"><Mfm :text="text" :isBlock="true" :plain="plain" /></div>
|
||||||
<MkInput v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder || undefined" :autocomplete="input.autocomplete" @keydown="onInputKeydown">
|
<MkInput v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder || undefined" :autocomplete="input.autocomplete" @keydown="onInputKeydown">
|
||||||
<template v-if="input.type === 'password'" #prefix><i class="ti ti-lock"></i></template>
|
<template v-if="input.type === 'password'" #prefix><i class="ti ti-lock"></i></template>
|
||||||
<template #caption>
|
<template #caption>
|
||||||
|
@ -105,6 +105,7 @@ const props = withDefaults(defineProps<{
|
||||||
cancelableByBgClick?: boolean;
|
cancelableByBgClick?: boolean;
|
||||||
okText?: string;
|
okText?: string;
|
||||||
cancelText?: string;
|
cancelText?: string;
|
||||||
|
plain?: boolean;
|
||||||
}>(), {
|
}>(), {
|
||||||
type: 'info',
|
type: 'info',
|
||||||
showOkButton: true,
|
showOkButton: true,
|
||||||
|
|
|
@ -8,6 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
:is="self ? 'MkA' : 'a'" ref="el" style="word-break: break-all;" class="_link" :[attr]="self ? url.substring(local.length) : url" :rel="rel ?? 'nofollow noopener'" :target="target"
|
:is="self ? 'MkA' : 'a'" ref="el" style="word-break: break-all;" class="_link" :[attr]="self ? url.substring(local.length) : url" :rel="rel ?? 'nofollow noopener'" :target="target"
|
||||||
:behavior="props.navigationBehavior"
|
:behavior="props.navigationBehavior"
|
||||||
:title="url"
|
:title="url"
|
||||||
|
@click.prevent="self ? true : promptConfirm()"
|
||||||
@click.stop
|
@click.stop
|
||||||
>
|
>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
|
@ -22,6 +23,7 @@ import { useTooltip } from '@/scripts/use-tooltip.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { isEnabledUrlPreview } from '@/instance.js';
|
import { isEnabledUrlPreview } from '@/instance.js';
|
||||||
import { MkABehavior } from '@/components/global/MkA.vue';
|
import { MkABehavior } from '@/components/global/MkA.vue';
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
url: string;
|
url: string;
|
||||||
|
@ -47,6 +49,16 @@ if (isEnabledUrlPreview.value) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function promptConfirm() {
|
||||||
|
const { canceled } = await os.confirm({
|
||||||
|
type: 'question',
|
||||||
|
text: i18n.tsx.confirmRemoteUrl({ x: props.url }),
|
||||||
|
plain: true,
|
||||||
|
});
|
||||||
|
if (canceled) return;
|
||||||
|
window.open(props.url, '_blank', 'nofollow noopener popup=false');
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
|
@ -5,12 +5,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="$style.root">
|
<div :class="$style.root">
|
||||||
<div v-if="media.isSensitive && hide" :class="$style.sensitive" @click="show">
|
<MkMediaAudio v-if="media.type.startsWith('audio') && media.type !== 'audio/midi'" :audio="media"/>
|
||||||
|
<div v-else-if="media.isSensitive && hide" :class="$style.sensitive" @click="show">
|
||||||
<span style="font-size: 1.6em;"><i class="ti ti-alert-triangle"></i></span>
|
<span style="font-size: 1.6em;"><i class="ti ti-alert-triangle"></i></span>
|
||||||
<b>{{ i18n.ts.sensitive }}</b>
|
<b>{{ i18n.ts.sensitive }}</b>
|
||||||
<span>{{ i18n.ts.clickToShow }}</span>
|
<span>{{ i18n.ts.clickToShow }}</span>
|
||||||
</div>
|
</div>
|
||||||
<MkMediaAudio v-else-if="media.type.startsWith('audio') && media.type !== 'audio/midi'" :audio="media"/>
|
|
||||||
<a
|
<a
|
||||||
v-else :class="$style.download"
|
v-else :class="$style.download"
|
||||||
:href="media.url"
|
:href="media.url"
|
||||||
|
|
|
@ -565,7 +565,8 @@ function quote() {
|
||||||
os.post({
|
os.post({
|
||||||
renote: appearNote.value,
|
renote: appearNote.value,
|
||||||
channel: appearNote.value.channel,
|
channel: appearNote.value.channel,
|
||||||
}).then(() => {
|
}).then((cancelled) => {
|
||||||
|
if (cancelled) return;
|
||||||
misskeyApi('notes/renotes', {
|
misskeyApi('notes/renotes', {
|
||||||
noteId: appearNote.value.id,
|
noteId: appearNote.value.id,
|
||||||
userId: $i?.id,
|
userId: $i?.id,
|
||||||
|
@ -589,7 +590,8 @@ function quote() {
|
||||||
} else {
|
} else {
|
||||||
os.post({
|
os.post({
|
||||||
renote: appearNote.value,
|
renote: appearNote.value,
|
||||||
}).then(() => {
|
}).then((cancelled) => {
|
||||||
|
if (cancelled) return;
|
||||||
misskeyApi('notes/renotes', {
|
misskeyApi('notes/renotes', {
|
||||||
noteId: appearNote.value.id,
|
noteId: appearNote.value.id,
|
||||||
userId: $i?.id,
|
userId: $i?.id,
|
||||||
|
|
|
@ -550,7 +550,8 @@ function quote() {
|
||||||
os.post({
|
os.post({
|
||||||
renote: appearNote.value,
|
renote: appearNote.value,
|
||||||
channel: appearNote.value.channel,
|
channel: appearNote.value.channel,
|
||||||
}).then(() => {
|
}).then((cancelled) => {
|
||||||
|
if (cancelled) return;
|
||||||
misskeyApi('notes/renotes', {
|
misskeyApi('notes/renotes', {
|
||||||
noteId: appearNote.value.id,
|
noteId: appearNote.value.id,
|
||||||
userId: $i?.id,
|
userId: $i?.id,
|
||||||
|
@ -574,7 +575,8 @@ function quote() {
|
||||||
} else {
|
} else {
|
||||||
os.post({
|
os.post({
|
||||||
renote: appearNote.value,
|
renote: appearNote.value,
|
||||||
}).then(() => {
|
}).then((cancelled) => {
|
||||||
|
if (cancelled) return;
|
||||||
misskeyApi('notes/renotes', {
|
misskeyApi('notes/renotes', {
|
||||||
noteId: appearNote.value.id,
|
noteId: appearNote.value.id,
|
||||||
userId: $i?.id,
|
userId: $i?.id,
|
||||||
|
|
|
@ -339,7 +339,8 @@ function quote() {
|
||||||
os.post({
|
os.post({
|
||||||
renote: appearNote.value,
|
renote: appearNote.value,
|
||||||
channel: appearNote.value.channel,
|
channel: appearNote.value.channel,
|
||||||
}).then(() => {
|
}).then((cancelled) => {
|
||||||
|
if (cancelled) return;
|
||||||
misskeyApi('notes/renotes', {
|
misskeyApi('notes/renotes', {
|
||||||
noteId: props.note.id,
|
noteId: props.note.id,
|
||||||
userId: $i.id,
|
userId: $i.id,
|
||||||
|
@ -363,7 +364,8 @@ function quote() {
|
||||||
} else {
|
} else {
|
||||||
os.post({
|
os.post({
|
||||||
renote: appearNote.value,
|
renote: appearNote.value,
|
||||||
}).then(() => {
|
}).then((cancelled) => {
|
||||||
|
if (cancelled) return;
|
||||||
misskeyApi('notes/renotes', {
|
misskeyApi('notes/renotes', {
|
||||||
noteId: props.note.id,
|
noteId: props.note.id,
|
||||||
userId: $i.id,
|
userId: $i.id,
|
||||||
|
|
|
@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.headerRight">
|
<div :class="$style.headerRight">
|
||||||
<template v-if="!(channel != null && fixed)">
|
<template v-if="!(channel != null && fixed)">
|
||||||
<button v-if="channel == null" ref="visibilityButton" v-click-anime v-tooltip="i18n.ts.visibility" :class="['_button', $style.headerRightItem, $style.visibility]" @click="setVisibility">
|
<button v-if="channel == null" ref="visibilityButton" v-click-anime v-tooltip="i18n.ts.visibility" :class="['_button', $style.headerRightItem, $style.visibility]" :disabled="editId != null" @click="setVisibility">
|
||||||
<span v-if="visibility === 'public'"><i class="ti ti-world"></i></span>
|
<span v-if="visibility === 'public'"><i class="ti ti-world"></i></span>
|
||||||
<span v-if="visibility === 'home'"><i class="ti ti-home"></i></span>
|
<span v-if="visibility === 'home'"><i class="ti ti-home"></i></span>
|
||||||
<span v-if="visibility === 'followers'"><i class="ti ti-lock"></i></span>
|
<span v-if="visibility === 'followers'"><i class="ti ti-lock"></i></span>
|
||||||
|
@ -32,7 +32,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<span :class="$style.headerRightButtonText">{{ channel.name }}</span>
|
<span :class="$style.headerRightButtonText">{{ channel.name }}</span>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
<button v-click-anime v-tooltip="i18n.ts._visibility.disableFederation" class="_button" :class="[$style.headerRightItem, { [$style.danger]: localOnly }]" :disabled="channel != null || visibility === 'specified'" @click="toggleLocalOnly">
|
<button v-click-anime v-tooltip="i18n.ts._visibility.disableFederation" class="_button" :class="[$style.headerRightItem, { [$style.danger]: localOnly }]" :disabled="channel != null || visibility === 'specified' || editId != null" @click="toggleLocalOnly">
|
||||||
<span v-if="!localOnly"><i class="ti ti-rocket"></i></span>
|
<span v-if="!localOnly"><i class="ti ti-rocket"></i></span>
|
||||||
<span v-else><i class="ti ti-rocket-off"></i></span>
|
<span v-else><i class="ti ti-rocket-off"></i></span>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<MkModal ref="modal" :preferType="'dialog'" @click="modal?.close()" @closed="onModalClosed()" @esc="modal?.close()">
|
<MkModal ref="modal" :preferType="'dialog'" @click="modal?.close()" @closed="onModalClosed()" @esc="modal?.close()">
|
||||||
<MkPostForm ref="form" :class="$style.form" v-bind="props" autofocus freezeAfterPosted @posted="onPosted" @cancel="modal?.close()" @esc="modal?.close()"/>
|
<MkPostForm ref="form" :class="$style.form" v-bind="props" autofocus freezeAfterPosted @posted="onPosted" @cancel="onCancel" @esc="onCancel"/>
|
||||||
</MkModal>
|
</MkModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ const props = withDefaults(defineProps<{
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'closed'): void;
|
(ev: 'closed', cancelled: boolean): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const modal = shallowRef<InstanceType<typeof MkModal>>();
|
const modal = shallowRef<InstanceType<typeof MkModal>>();
|
||||||
|
@ -47,10 +47,18 @@ function onPosted() {
|
||||||
modal.value?.close({
|
modal.value?.close({
|
||||||
useSendAnimation: true,
|
useSendAnimation: true,
|
||||||
});
|
});
|
||||||
|
emit('closed', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCancel() {
|
||||||
|
// for some reason onModalClosed does not get called properly when closing the model through other functions.
|
||||||
|
modal.value?.close();
|
||||||
|
// emit is required so that the dialog gets properly disposed otherwise it will float around as a "zombie"
|
||||||
|
emit('closed', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onModalClosed() {
|
function onModalClosed() {
|
||||||
emit('closed');
|
emit('closed', true);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -65,13 +65,6 @@ const props = defineProps<{
|
||||||
edit: boolean;
|
edit: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
// This will not be available for now as I don't think this is needed
|
|
||||||
// const notesSearchAvailable = (($i == null && instance.policies.canSearchNotes) || ($i != null && $i.policies.canSearchNotes));
|
|
||||||
/* if (!notesSearchAvailable) {
|
|
||||||
const wid = widgetDefs.findIndex(widget => widget === 'search');
|
|
||||||
widgetDefs.splice(wid, 1);
|
|
||||||
} */
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'updateWidgets', widgets: Widget[]): void;
|
(ev: 'updateWidgets', widgets: Widget[]): void;
|
||||||
(ev: 'addWidget', widget: Widget): void;
|
(ev: 'addWidget', widget: Widget): void;
|
||||||
|
|
|
@ -565,7 +565,8 @@ function quote() {
|
||||||
os.post({
|
os.post({
|
||||||
renote: appearNote.value,
|
renote: appearNote.value,
|
||||||
channel: appearNote.value.channel,
|
channel: appearNote.value.channel,
|
||||||
}).then(() => {
|
}).then((cancelled) => {
|
||||||
|
if (cancelled) return;
|
||||||
misskeyApi('notes/renotes', {
|
misskeyApi('notes/renotes', {
|
||||||
noteId: appearNote.value.id,
|
noteId: appearNote.value.id,
|
||||||
userId: $i?.id,
|
userId: $i?.id,
|
||||||
|
@ -589,7 +590,8 @@ function quote() {
|
||||||
} else {
|
} else {
|
||||||
os.post({
|
os.post({
|
||||||
renote: appearNote.value,
|
renote: appearNote.value,
|
||||||
}).then(() => {
|
}).then((cancelled) => {
|
||||||
|
if (cancelled) return;
|
||||||
misskeyApi('notes/renotes', {
|
misskeyApi('notes/renotes', {
|
||||||
noteId: appearNote.value.id,
|
noteId: appearNote.value.id,
|
||||||
userId: $i?.id,
|
userId: $i?.id,
|
||||||
|
|
|
@ -559,7 +559,8 @@ function quote() {
|
||||||
os.post({
|
os.post({
|
||||||
renote: appearNote.value,
|
renote: appearNote.value,
|
||||||
channel: appearNote.value.channel,
|
channel: appearNote.value.channel,
|
||||||
}).then(() => {
|
}).then((cancelled) => {
|
||||||
|
if (cancelled) return;
|
||||||
misskeyApi('notes/renotes', {
|
misskeyApi('notes/renotes', {
|
||||||
noteId: appearNote.value.id,
|
noteId: appearNote.value.id,
|
||||||
userId: $i?.id,
|
userId: $i?.id,
|
||||||
|
@ -583,7 +584,8 @@ function quote() {
|
||||||
} else {
|
} else {
|
||||||
os.post({
|
os.post({
|
||||||
renote: appearNote.value,
|
renote: appearNote.value,
|
||||||
}).then(() => {
|
}).then((cancelled) => {
|
||||||
|
if (cancelled) return;
|
||||||
misskeyApi('notes/renotes', {
|
misskeyApi('notes/renotes', {
|
||||||
noteId: appearNote.value.id,
|
noteId: appearNote.value.id,
|
||||||
userId: $i?.id,
|
userId: $i?.id,
|
||||||
|
|
|
@ -353,7 +353,8 @@ function quote() {
|
||||||
os.post({
|
os.post({
|
||||||
renote: appearNote.value,
|
renote: appearNote.value,
|
||||||
channel: appearNote.value.channel,
|
channel: appearNote.value.channel,
|
||||||
}).then(() => {
|
}).then((cancelled) => {
|
||||||
|
if (cancelled) return;
|
||||||
misskeyApi('notes/renotes', {
|
misskeyApi('notes/renotes', {
|
||||||
noteId: props.note.id,
|
noteId: props.note.id,
|
||||||
userId: $i.id,
|
userId: $i.id,
|
||||||
|
@ -377,7 +378,8 @@ function quote() {
|
||||||
} else {
|
} else {
|
||||||
os.post({
|
os.post({
|
||||||
renote: appearNote.value,
|
renote: appearNote.value,
|
||||||
}).then(() => {
|
}).then((cancelled) => {
|
||||||
|
if (cancelled) return;
|
||||||
misskeyApi('notes/renotes', {
|
misskeyApi('notes/renotes', {
|
||||||
noteId: props.note.id,
|
noteId: props.note.id,
|
||||||
userId: $i.id,
|
userId: $i.id,
|
||||||
|
|
|
@ -245,6 +245,7 @@ export function confirm(props: {
|
||||||
text?: string;
|
text?: string;
|
||||||
okText?: string;
|
okText?: string;
|
||||||
cancelText?: string;
|
cancelText?: string;
|
||||||
|
plain?: boolean;
|
||||||
}): Promise<{ canceled: boolean }> {
|
}): Promise<{ canceled: boolean }> {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
const { dispose } = popup(MkDialog, {
|
const { dispose } = popup(MkDialog, {
|
||||||
|
@ -691,7 +692,7 @@ export function contextMenu(items: MenuItem[], ev: MouseEvent): Promise<void> {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function post(props: Record<string, any> = {}): Promise<void> {
|
export function post(props: Record<string, any> = {}): Promise<void | boolean> {
|
||||||
pleaseLogin(undefined, (props.initialText || props.initialNote ? {
|
pleaseLogin(undefined, (props.initialText || props.initialNote ? {
|
||||||
type: 'share',
|
type: 'share',
|
||||||
params: {
|
params: {
|
||||||
|
@ -709,8 +710,8 @@ export function post(props: Record<string, any> = {}): Promise<void> {
|
||||||
// 複数のpost formを開いたときに場合によってはエラーになる
|
// 複数のpost formを開いたときに場合によってはエラーになる
|
||||||
// もちろん複数のpost formを開けること自体Misskeyサイドのバグなのだが
|
// もちろん複数のpost formを開けること自体Misskeyサイドのバグなのだが
|
||||||
const { dispose } = popup(MkPostFormDialog, props, {
|
const { dispose } = popup(MkPostFormDialog, props, {
|
||||||
closed: () => {
|
closed: (cancelled) => {
|
||||||
resolve();
|
resolve(cancelled);
|
||||||
dispose();
|
dispose();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkKeyValue>
|
</MkKeyValue>
|
||||||
</button>
|
</button>
|
||||||
<button class="_button" :class="$style.kvEditBtn" @click="describe()">
|
<button class="_button" :class="$style.kvEditBtn" @click="describe()">
|
||||||
<MkKeyValue>
|
<MkKeyValue :class="$style.multiline">
|
||||||
<template #key>{{ i18n.ts.description }}</template>
|
<template #key>{{ i18n.ts.description }}</template>
|
||||||
<template #value>{{ file.comment ? file.comment : `(${i18n.ts.none})` }}<i class="ti ti-pencil" :class="$style.kvEditIcon"></i></template>
|
<template #value>{{ file.comment ? file.comment : `(${i18n.ts.none})` }}<i class="ti ti-pencil" :class="$style.kvEditIcon"></i></template>
|
||||||
</MkKeyValue>
|
</MkKeyValue>
|
||||||
|
@ -313,6 +313,10 @@ onMounted(async () => {
|
||||||
padding: .5rem 1rem;
|
padding: .5rem 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.multiline {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
.kvEditBtn {
|
.kvEditBtn {
|
||||||
text-align: start;
|
text-align: start;
|
||||||
display: block;
|
display: block;
|
||||||
|
|
|
@ -6,7 +6,8 @@
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { Interpreter, Parser, utils, values } from '@syuilo/aiscript';
|
import { Interpreter, Parser, utils, values } from '@syuilo/aiscript';
|
||||||
import { aiScriptReadline, createAiScriptEnv } from '@/scripts/aiscript/api.js';
|
import { aiScriptReadline, createAiScriptEnv } from '@/scripts/aiscript/api.js';
|
||||||
import { inputText } from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
import { Plugin, noteActions, notePostInterruptors, noteViewInterruptors, postFormActions, userActions, pageViewInterruptors } from '@/store.js';
|
import { Plugin, noteActions, notePostInterruptors, noteViewInterruptors, postFormActions, userActions, pageViewInterruptors } from '@/store.js';
|
||||||
|
|
||||||
const parser = new Parser();
|
const parser = new Parser();
|
||||||
|
@ -91,8 +92,16 @@ function createPluginEnv(opts: { plugin: Plugin; storageKey: string }): Record<s
|
||||||
registerPageViewInterruptor({ pluginId: opts.plugin.id, handler });
|
registerPageViewInterruptor({ pluginId: opts.plugin.id, handler });
|
||||||
}),
|
}),
|
||||||
'Plugin:open_url': values.FN_NATIVE(([url]) => {
|
'Plugin:open_url': values.FN_NATIVE(([url]) => {
|
||||||
utils.assertString(url);
|
(async () => {
|
||||||
window.open(url.value, '_blank', 'noopener');
|
utils.assertString(url);
|
||||||
|
const { canceled } = await os.confirm({
|
||||||
|
type: 'question',
|
||||||
|
text: i18n.tsx.confirmRemoteUrl({ x: url.value }),
|
||||||
|
plain: true,
|
||||||
|
});
|
||||||
|
if (canceled) return;
|
||||||
|
window.open(url.value, '_blank', 'noopener');
|
||||||
|
})();
|
||||||
}),
|
}),
|
||||||
'Plugin:config': values.OBJ(config),
|
'Plugin:config': values.OBJ(config),
|
||||||
};
|
};
|
||||||
|
|
|
@ -66,7 +66,6 @@ import { i18n } from '@/i18n.js';
|
||||||
const WINDOW_THRESHOLD = 1400;
|
const WINDOW_THRESHOLD = 1400;
|
||||||
|
|
||||||
const menu = ref(defaultStore.state.menu);
|
const menu = ref(defaultStore.state.menu);
|
||||||
const menuDisplay = computed(defaultStore.makeGetterSetter('menuDisplay'));
|
|
||||||
const otherNavItemIndicated = computed<boolean>(() => {
|
const otherNavItemIndicated = computed<boolean>(() => {
|
||||||
for (const def in navbarItemDef) {
|
for (const def in navbarItemDef) {
|
||||||
if (menu.value.includes(def)) continue;
|
if (menu.value.includes(def)) continue;
|
||||||
|
@ -81,10 +80,12 @@ const iconOnly = ref(false);
|
||||||
const settingsWindowed = ref(false);
|
const settingsWindowed = ref(false);
|
||||||
|
|
||||||
function calcViewState() {
|
function calcViewState() {
|
||||||
iconOnly.value = (window.innerWidth <= WINDOW_THRESHOLD) || (menuDisplay.value === 'sideIcon');
|
iconOnly.value = (window.innerWidth <= WINDOW_THRESHOLD) || (defaultStore.state.menuDisplay === 'sideIcon');
|
||||||
settingsWindowed.value = (window.innerWidth > WINDOW_THRESHOLD);
|
settingsWindowed.value = (window.innerWidth > WINDOW_THRESHOLD);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
calcViewState();
|
||||||
|
|
||||||
function more(ev: MouseEvent) {
|
function more(ev: MouseEvent) {
|
||||||
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkLaunchPad.vue')), {
|
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkLaunchPad.vue')), {
|
||||||
src: ev.currentTarget ?? ev.target,
|
src: ev.currentTarget ?? ev.target,
|
||||||
|
@ -166,7 +167,6 @@ watch(defaultStore.reactiveState.menuDisplay, () => {
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
padding: 16px 0;
|
padding: 16px 0;
|
||||||
background: var(--bg);
|
|
||||||
|
|
||||||
> .button {
|
> .button {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
|
|
@ -24,6 +24,7 @@ import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { useRouter } from '@/router/supplier.js';
|
import { useRouter } from '@/router/supplier.js';
|
||||||
import { GetFormResultType } from '@/scripts/form.js';
|
import { GetFormResultType } from '@/scripts/form.js';
|
||||||
|
import { notesSearchAvailable } from '@/scripts/check-permissions.js';
|
||||||
|
|
||||||
const name = 'search';
|
const name = 'search';
|
||||||
|
|
||||||
|
@ -128,6 +129,14 @@ async function search() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!notesSearchAvailable) {
|
||||||
|
os.alert({
|
||||||
|
type: 'warning',
|
||||||
|
text: i18n.ts.notesSearchNotAvailable,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
notePagination.value = {
|
notePagination.value = {
|
||||||
endpoint: 'notes/search',
|
endpoint: 'notes/search',
|
||||||
limit: 10,
|
limit: 10,
|
||||||
|
|
Loading…
Reference in New Issue