Merge branch 'develop' into hazelnoot/following-timeline
This commit is contained in:
commit
97a16c2ebc
22 changed files with 174 additions and 76 deletions
|
|
@ -4,40 +4,49 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
-->
|
||||
|
||||
<template>
|
||||
<div class="bcekxzvu _margin _panel">
|
||||
<div class="target">
|
||||
<MkA v-user-preview="report.targetUserId" class="info" :to="`/admin/user/${report.targetUserId}`" :behavior="'window'">
|
||||
<MkAvatar class="avatar" :user="report.targetUser" indicator/>
|
||||
<div class="names">
|
||||
<MkUserName class="name" :user="report.targetUser"/>
|
||||
<MkAcct class="acct" :user="report.targetUser" style="display: block;"/>
|
||||
<div class="bcekxzvu _margin _panel">
|
||||
<div class="target">
|
||||
<MkA v-user-preview="report.targetUserId" class="info" :to="`/admin/user/${report.targetUserId}`" :behavior="'window'">
|
||||
<MkAvatar class="avatar" :user="report.targetUser" indicator/>
|
||||
<div class="names">
|
||||
<MkUserName class="name" :user="report.targetUser"/>
|
||||
<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>
|
||||
</MkA>
|
||||
<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'"/>
|
||||
<hr>
|
||||
</div>
|
||||
<hr/>
|
||||
<div>{{ i18n.ts.reporter }}: <MkA :to="`/admin/user/${report.reporter.id}`" class="_link" :behavior="'window'">@{{ report.reporter.username }}</MkA></div>
|
||||
<div v-if="report.assignee">
|
||||
{{ i18n.ts.moderator }}:
|
||||
<MkAcct :user="report.assignee"/>
|
||||
</div>
|
||||
<div><MkTime :time="report.createdAt"/></div>
|
||||
<div class="action">
|
||||
<MkSwitch v-model="forward" :disabled="report.targetUser.host == null || report.resolved">
|
||||
{{ i18n.ts.forwardReport }}
|
||||
<template #caption>{{ i18n.ts.forwardReportIsAnonymous }}</template>
|
||||
</MkSwitch>
|
||||
<MkButton v-if="!report.resolved" primary @click="resolve">{{ i18n.ts.abuseMarkAsResolved }}</MkButton>
|
||||
<div class="detail">
|
||||
<div>
|
||||
<Mfm :text="report.comment" :isBlock="true" :linkNavigationBehavior="'window'"/>
|
||||
</div>
|
||||
<hr/>
|
||||
<div v-if="report.assignee" class="assignee">
|
||||
{{ i18n.ts.moderator }}:
|
||||
<MkA :to="`/admin/user/${report.assignee.id}`" class="_link" :behavior="'window'">@{{ report.assignee.username }}</MkA>
|
||||
</div>
|
||||
<div class="action">
|
||||
<MkSwitch v-model="forward" c:disabled="report.targetUser.host == null || report.resolved">
|
||||
{{ i18n.ts.forwardReport }}
|
||||
<template #caption>{{ i18n.ts.forwardReportIsAnonymous }}</template>
|
||||
</MkSwitch>
|
||||
<MkButton v-if="!report.resolved" primary @click="resolve">{{ i18n.ts.abuseMarkAsResolved }}</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
@ -72,13 +81,13 @@ function resolve() {
|
|||
<style lang="scss" scoped>
|
||||
.bcekxzvu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transition: .1s;
|
||||
|
||||
> .target {
|
||||
width: 35%;
|
||||
box-sizing: border-box;
|
||||
text-align: left;
|
||||
padding: 24px;
|
||||
border-right: solid 1px var(--divider);
|
||||
padding: 24px 24px 0px 24px;
|
||||
|
||||
> .info {
|
||||
display: flex;
|
||||
|
|
@ -100,16 +109,36 @@ function resolve() {
|
|||
padding: 0 8px;
|
||||
flex: 1;
|
||||
|
||||
white-space: pre;
|
||||
overflow: hidden;
|
||||
|
||||
> .name {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .keyvalCtn {
|
||||
display: inline-flex;
|
||||
gap: 15px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
> .detail {
|
||||
flex: 1;
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0px 24px 24px 24px;
|
||||
|
||||
.assignee {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.action {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkLoading v-else-if="type === 'waiting'" :class="$style.iconInner" :em="true"/>
|
||||
</div>
|
||||
<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">
|
||||
<template v-if="input.type === 'password'" #prefix><i class="ti ti-lock"></i></template>
|
||||
<template #caption>
|
||||
|
|
@ -105,6 +105,7 @@ const props = withDefaults(defineProps<{
|
|||
cancelableByBgClick?: boolean;
|
||||
okText?: string;
|
||||
cancelText?: string;
|
||||
plain?: boolean;
|
||||
}>(), {
|
||||
type: 'info',
|
||||
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"
|
||||
:behavior="props.navigationBehavior"
|
||||
:title="url"
|
||||
@click.prevent="self ? true : promptConfirm()"
|
||||
@click.stop
|
||||
>
|
||||
<slot></slot>
|
||||
|
|
@ -22,6 +23,7 @@ import { useTooltip } from '@/scripts/use-tooltip.js';
|
|||
import * as os from '@/os.js';
|
||||
import { isEnabledUrlPreview } from '@/instance.js';
|
||||
import { MkABehavior } from '@/components/global/MkA.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
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>
|
||||
|
||||
<style lang="scss" module>
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<template>
|
||||
<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>
|
||||
<b>{{ i18n.ts.sensitive }}</b>
|
||||
<span>{{ i18n.ts.clickToShow }}</span>
|
||||
</div>
|
||||
<MkMediaAudio v-else-if="media.type.startsWith('audio') && media.type !== 'audio/midi'" :audio="media"/>
|
||||
<a
|
||||
v-else :class="$style.download"
|
||||
:href="media.url"
|
||||
|
|
|
|||
|
|
@ -565,7 +565,8 @@ function quote() {
|
|||
os.post({
|
||||
renote: appearNote.value,
|
||||
channel: appearNote.value.channel,
|
||||
}).then(() => {
|
||||
}).then((cancelled) => {
|
||||
if (cancelled) return;
|
||||
misskeyApi('notes/renotes', {
|
||||
noteId: appearNote.value.id,
|
||||
userId: $i?.id,
|
||||
|
|
@ -589,7 +590,8 @@ function quote() {
|
|||
} else {
|
||||
os.post({
|
||||
renote: appearNote.value,
|
||||
}).then(() => {
|
||||
}).then((cancelled) => {
|
||||
if (cancelled) return;
|
||||
misskeyApi('notes/renotes', {
|
||||
noteId: appearNote.value.id,
|
||||
userId: $i?.id,
|
||||
|
|
|
|||
|
|
@ -550,7 +550,8 @@ function quote() {
|
|||
os.post({
|
||||
renote: appearNote.value,
|
||||
channel: appearNote.value.channel,
|
||||
}).then(() => {
|
||||
}).then((cancelled) => {
|
||||
if (cancelled) return;
|
||||
misskeyApi('notes/renotes', {
|
||||
noteId: appearNote.value.id,
|
||||
userId: $i?.id,
|
||||
|
|
@ -574,7 +575,8 @@ function quote() {
|
|||
} else {
|
||||
os.post({
|
||||
renote: appearNote.value,
|
||||
}).then(() => {
|
||||
}).then((cancelled) => {
|
||||
if (cancelled) return;
|
||||
misskeyApi('notes/renotes', {
|
||||
noteId: appearNote.value.id,
|
||||
userId: $i?.id,
|
||||
|
|
|
|||
|
|
@ -339,7 +339,8 @@ function quote() {
|
|||
os.post({
|
||||
renote: appearNote.value,
|
||||
channel: appearNote.value.channel,
|
||||
}).then(() => {
|
||||
}).then((cancelled) => {
|
||||
if (cancelled) return;
|
||||
misskeyApi('notes/renotes', {
|
||||
noteId: props.note.id,
|
||||
userId: $i.id,
|
||||
|
|
@ -363,7 +364,8 @@ function quote() {
|
|||
} else {
|
||||
os.post({
|
||||
renote: appearNote.value,
|
||||
}).then(() => {
|
||||
}).then((cancelled) => {
|
||||
if (cancelled) return;
|
||||
misskeyApi('notes/renotes', {
|
||||
noteId: props.note.id,
|
||||
userId: $i.id,
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
<div :class="$style.headerRight">
|
||||
<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 === 'home'"><i class="ti ti-home"></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>
|
||||
</button>
|
||||
</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-else><i class="ti ti-rocket-off"></i></span>
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<template>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ const props = withDefaults(defineProps<{
|
|||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'closed'): void;
|
||||
(ev: 'closed', cancelled: boolean): void;
|
||||
}>();
|
||||
|
||||
const modal = shallowRef<InstanceType<typeof MkModal>>();
|
||||
|
|
@ -47,10 +47,18 @@ function onPosted() {
|
|||
modal.value?.close({
|
||||
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() {
|
||||
emit('closed');
|
||||
emit('closed', true);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -65,13 +65,6 @@ const props = defineProps<{
|
|||
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<{
|
||||
(ev: 'updateWidgets', widgets: Widget[]): void;
|
||||
(ev: 'addWidget', widget: Widget): void;
|
||||
|
|
|
|||
|
|
@ -565,7 +565,8 @@ function quote() {
|
|||
os.post({
|
||||
renote: appearNote.value,
|
||||
channel: appearNote.value.channel,
|
||||
}).then(() => {
|
||||
}).then((cancelled) => {
|
||||
if (cancelled) return;
|
||||
misskeyApi('notes/renotes', {
|
||||
noteId: appearNote.value.id,
|
||||
userId: $i?.id,
|
||||
|
|
@ -589,7 +590,8 @@ function quote() {
|
|||
} else {
|
||||
os.post({
|
||||
renote: appearNote.value,
|
||||
}).then(() => {
|
||||
}).then((cancelled) => {
|
||||
if (cancelled) return;
|
||||
misskeyApi('notes/renotes', {
|
||||
noteId: appearNote.value.id,
|
||||
userId: $i?.id,
|
||||
|
|
|
|||
|
|
@ -559,7 +559,8 @@ function quote() {
|
|||
os.post({
|
||||
renote: appearNote.value,
|
||||
channel: appearNote.value.channel,
|
||||
}).then(() => {
|
||||
}).then((cancelled) => {
|
||||
if (cancelled) return;
|
||||
misskeyApi('notes/renotes', {
|
||||
noteId: appearNote.value.id,
|
||||
userId: $i?.id,
|
||||
|
|
@ -583,7 +584,8 @@ function quote() {
|
|||
} else {
|
||||
os.post({
|
||||
renote: appearNote.value,
|
||||
}).then(() => {
|
||||
}).then((cancelled) => {
|
||||
if (cancelled) return;
|
||||
misskeyApi('notes/renotes', {
|
||||
noteId: appearNote.value.id,
|
||||
userId: $i?.id,
|
||||
|
|
|
|||
|
|
@ -353,7 +353,8 @@ function quote() {
|
|||
os.post({
|
||||
renote: appearNote.value,
|
||||
channel: appearNote.value.channel,
|
||||
}).then(() => {
|
||||
}).then((cancelled) => {
|
||||
if (cancelled) return;
|
||||
misskeyApi('notes/renotes', {
|
||||
noteId: props.note.id,
|
||||
userId: $i.id,
|
||||
|
|
@ -377,7 +378,8 @@ function quote() {
|
|||
} else {
|
||||
os.post({
|
||||
renote: appearNote.value,
|
||||
}).then(() => {
|
||||
}).then((cancelled) => {
|
||||
if (cancelled) return;
|
||||
misskeyApi('notes/renotes', {
|
||||
noteId: props.note.id,
|
||||
userId: $i.id,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue