Merge remote-tracking branch 'refs/remotes/github-prismisskey/develop' into develop
# Conflicts: # locales/index.d.ts # packages/frontend/src/components/MkPostForm.vue # pnpm-lock.yaml
This commit is contained in:
commit
6445591350
99 changed files with 1734 additions and 417 deletions
|
|
@ -276,8 +276,11 @@ const align = () => {
|
|||
const onOpened = () => {
|
||||
emit('opened');
|
||||
|
||||
// NOTE: Chromatic テストの際に undefined になる場合がある
|
||||
if (content.value == null) return;
|
||||
|
||||
// モーダルコンテンツにマウスボタンが押され、コンテンツ外でマウスボタンが離されたときにモーダルバックグラウンドクリックと判定させないためにマウスイベントを監視しフラグ管理する
|
||||
const el = content.value!.children[0];
|
||||
const el = content.value.children[0];
|
||||
el.addEventListener('mousedown', ev => {
|
||||
contentClicking = true;
|
||||
window.addEventListener('mouseup', ev => {
|
||||
|
|
|
|||
|
|
@ -212,7 +212,7 @@ const localOnly = ref(props.initialLocalOnly ?? (defaultStore.state.rememberNote
|
|||
const visibility = ref(props.initialVisibility ?? (defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility));
|
||||
const visibleUsers = ref<Misskey.entities.UserDetailed[]>([]);
|
||||
if (props.initialVisibleUsers) {
|
||||
props.initialVisibleUsers.forEach(pushVisibleUser);
|
||||
props.initialVisibleUsers.forEach(u => pushVisibleUser(u));
|
||||
}
|
||||
const reactionAcceptance = ref(defaultStore.state.reactionAcceptance);
|
||||
const autocomplete = ref(null);
|
||||
|
|
@ -442,7 +442,7 @@ function initialize() {
|
|||
misskeyApi('users/show', {
|
||||
userIds: reply.value.visibleUserIds.filter(uid => uid !== $i.id && uid !== reply.value?.userId),
|
||||
}).then(users => {
|
||||
users.forEach(pushVisibleUser);
|
||||
users.forEach(u => pushVisibleUser(u));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -728,6 +728,23 @@ async function onPaste(ev: ClipboardEvent) {
|
|||
quoteId.value = paste.substring(url.length).match(/^\/notes\/(.+?)\/?$/)?.[1] ?? null;
|
||||
});
|
||||
}
|
||||
|
||||
if (paste.length > 1000) {
|
||||
ev.preventDefault();
|
||||
os.confirm({
|
||||
type: 'info',
|
||||
text: i18n.ts.attachAsFileQuestion,
|
||||
}).then(({ canceled }) => {
|
||||
if (canceled) {
|
||||
insertTextAtCursor(textareaEl.value, paste);
|
||||
return;
|
||||
}
|
||||
|
||||
const fileName = formatTimeString(new Date(), defaultStore.state.pastedFileName).replace(/{{number}}/g, "0");
|
||||
const file = new File([paste], `${fileName}.txt`, { type: "text/plain" });
|
||||
upload(file, `${fileName}.txt`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function onDragover(ev) {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,10 @@ import { i18n } from '@/i18n.js';
|
|||
|
||||
let lock: Promise<undefined> | undefined;
|
||||
|
||||
function sleep(ms: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
const common = {
|
||||
render(args) {
|
||||
return {
|
||||
|
|
@ -43,6 +47,8 @@ const common = {
|
|||
lock = new Promise(r => resolve = r);
|
||||
|
||||
try {
|
||||
// NOTE: sleep しないと何故か落ちる
|
||||
await sleep(100);
|
||||
const canvas = within(canvasElement);
|
||||
const a = canvas.getByRole<HTMLAnchorElement>('link');
|
||||
// await expect(a.href).toMatch(/^https?:\/\/.*#test$/);
|
||||
|
|
@ -53,7 +59,7 @@ const common = {
|
|||
const i = buttons[0];
|
||||
await expect(i).toBeInTheDocument();
|
||||
await userEvent.click(i);
|
||||
// await expect(canvasElement).toHaveTextContent(i18n.ts._ad.back);
|
||||
await expect(canvasElement).toHaveTextContent(i18n.ts._ad.back);
|
||||
await expect(a).not.toBeInTheDocument();
|
||||
await expect(i).not.toBeInTheDocument();
|
||||
buttons = canvas.getAllByRole<HTMLButtonElement>('button');
|
||||
|
|
|
|||
|
|
@ -235,6 +235,15 @@ const patronsWithIcon = [{
|
|||
}, {
|
||||
name: 'Takeno',
|
||||
icon: 'https://assets.misskey-hub.net/patrons/6fba81536aea48fe94a30909c502dfa1.jpg',
|
||||
}, {
|
||||
name: 'くびすじ',
|
||||
icon: 'https://assets.misskey-hub.net/patrons/aa5789850b2149aeb5b89ebe2e9083db.jpg',
|
||||
}, {
|
||||
name: '古道京紗@ぷらいべったー',
|
||||
icon: 'https://assets.misskey-hub.net/patrons/18346d0519704963a4beabe6abc170af.jpg',
|
||||
}, {
|
||||
name: '越貝鯛丸',
|
||||
icon: 'https://assets.misskey-hub.net/patrons/86c7374de37849b882d8ebbc833dc968.jpg',
|
||||
}];
|
||||
|
||||
const patrons = [
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { computed, ref } from 'vue';
|
||||
import XHeader from './_header_.vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
|
|
@ -90,8 +91,17 @@ const pagination = {
|
|||
})),
|
||||
};
|
||||
|
||||
function getStatus(instance) {
|
||||
if (instance.isSuspended) return 'Suspended';
|
||||
function getStatus(instance: Misskey.entities.FederationInstance) {
|
||||
switch (instance.suspensionState) {
|
||||
case 'manuallySuspended':
|
||||
return 'Manually Suspended';
|
||||
case 'goneSuspended':
|
||||
return 'Automatically Suspended (Gone)';
|
||||
case 'autoSuspendedForNotResponding':
|
||||
return 'Automatically Suspended (Not Responding)';
|
||||
case 'none':
|
||||
break;
|
||||
}
|
||||
if (instance.isBlocked) return 'Blocked';
|
||||
if (instance.isSilenced) return 'Silenced';
|
||||
if (instance.isNotResponding) return 'Error';
|
||||
|
|
|
|||
|
|
@ -29,6 +29,9 @@ const paginationForPolls = {
|
|||
endpoint: 'notes/polls/recommendation' as const,
|
||||
limit: 10,
|
||||
offsetMode: true,
|
||||
params: {
|
||||
excludeChannels: true,
|
||||
},
|
||||
};
|
||||
|
||||
const tab = ref('notes');
|
||||
|
|
|
|||
|
|
@ -35,7 +35,16 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<FormSection v-if="iAmModerator">
|
||||
<template #label>Moderation</template>
|
||||
<div class="_gaps_s">
|
||||
<MkSwitch v-model="suspended" :disabled="!instance" @update:modelValue="toggleSuspend">{{ i18n.ts.stopActivityDelivery }}</MkSwitch>
|
||||
<MkKeyValue>
|
||||
<template #key>
|
||||
{{ i18n.ts._delivery.status }}
|
||||
</template>
|
||||
<template #value>
|
||||
{{ i18n.ts._delivery._type[suspensionState] }}
|
||||
</template>
|
||||
</MkKeyValue>
|
||||
<MkButton v-if="suspensionState === 'none'" :disabled="!instance" danger @click="stopDelivery">{{ i18n.ts._delivery.stop }}</MkButton>
|
||||
<MkButton v-if="suspensionState !== 'none'" :disabled="!instance" @click="resumeDelivery">{{ i18n.ts._delivery.resume }}</MkButton>
|
||||
<MkSwitch v-model="isBlocked" :disabled="!meta || !instance" @update:modelValue="toggleBlock">{{ i18n.ts.blockThisInstance }}</MkSwitch>
|
||||
<MkSwitch v-model="isSilenced" :disabled="!meta || !instance" @update:modelValue="toggleSilenced">{{ i18n.ts.silenceThisInstance }}</MkSwitch>
|
||||
<MkButton @click="refreshMetadata"><i class="ti ti-refresh"></i> Refresh metadata</MkButton>
|
||||
|
|
@ -155,7 +164,7 @@ const tab = ref('overview');
|
|||
const chartSrc = ref('instance-requests');
|
||||
const meta = ref<Misskey.entities.AdminMetaResponse | null>(null);
|
||||
const instance = ref<Misskey.entities.FederationInstance | null>(null);
|
||||
const suspended = ref(false);
|
||||
const suspensionState = ref<'none' | 'manuallySuspended' | 'goneSuspended' | 'autoSuspendedForNotResponding'>('none');
|
||||
const isBlocked = ref(false);
|
||||
const isSilenced = ref(false);
|
||||
const faviconUrl = ref<string | null>(null);
|
||||
|
|
@ -183,7 +192,7 @@ async function fetch(): Promise<void> {
|
|||
instance.value = await misskeyApi('federation/show-instance', {
|
||||
host: props.host,
|
||||
});
|
||||
suspended.value = instance.value?.isSuspended ?? false;
|
||||
suspensionState.value = instance.value?.suspensionState ?? 'none';
|
||||
isBlocked.value = instance.value?.isBlocked ?? false;
|
||||
isSilenced.value = instance.value?.isSilenced ?? false;
|
||||
faviconUrl.value = getProxiedImageUrlNullable(instance.value?.faviconUrl, 'preview') ?? getProxiedImageUrlNullable(instance.value?.iconUrl, 'preview');
|
||||
|
|
@ -209,11 +218,21 @@ async function toggleSilenced(): Promise<void> {
|
|||
});
|
||||
}
|
||||
|
||||
async function toggleSuspend(): Promise<void> {
|
||||
async function stopDelivery(): Promise<void> {
|
||||
if (!instance.value) throw new Error('No instance?');
|
||||
suspensionState.value = 'manuallySuspended';
|
||||
await misskeyApi('admin/federation/update-instance', {
|
||||
host: instance.value.host,
|
||||
isSuspended: suspended.value,
|
||||
isSuspended: true,
|
||||
});
|
||||
}
|
||||
|
||||
async function resumeDelivery(): Promise<void> {
|
||||
if (!instance.value) throw new Error('No instance?');
|
||||
suspensionState.value = 'none';
|
||||
await misskeyApi('admin/federation/update-instance', {
|
||||
host: instance.value.host,
|
||||
isSuspended: false,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -64,7 +64,34 @@ async function init() {
|
|||
// Googleニュース対策
|
||||
if (text?.startsWith(`${title.value}.\n`)) noteText += text.replace(`${title.value}.\n`, '');
|
||||
else if (text && title.value !== text) noteText += `${text}\n`;
|
||||
if (url) noteText += `${url}`;
|
||||
if (url) {
|
||||
try {
|
||||
// Normalize the URL to URL-encoded and puny-coded from with the URL constructor.
|
||||
//
|
||||
// It's common to use unicode characters in the URL for better visibility of URL
|
||||
// like: https://ja.wikipedia.org/wiki/ミスキー
|
||||
// or like: https://藍.moe/
|
||||
// However, in the MFM, the unicode characters must be URL-encoded to be parsed as `url` node
|
||||
// like: https://ja.wikipedia.org/wiki/%E3%83%9F%E3%82%B9%E3%82%AD%E3%83%BC
|
||||
// or like: https://xn--931a.moe/
|
||||
// Therefore, we need to normalize the URL to URL-encoded form.
|
||||
//
|
||||
// The URL constructor will parse the URL and normalize unicode characters
|
||||
// in the host to punycode and in the path component to URL-encoded form.
|
||||
// (see url.spec.whatwg.org)
|
||||
//
|
||||
// In addition, the current MFM renderer decodes the URL-encoded path and / punycode encoded host name so
|
||||
// this normalization doesn't make the visible URL ugly.
|
||||
// (see MkUrl.vue)
|
||||
|
||||
noteText += new URL(url).href;
|
||||
} catch {
|
||||
// fallback to original URL if the URL is invalid.
|
||||
// note that this is extremely rare since the `url` parameter is designed to share a URL and
|
||||
// the URL constructor will throw TypeError only if failure, which means the URL is not valid.
|
||||
noteText += url;
|
||||
}
|
||||
}
|
||||
initialText.value = noteText.trim();
|
||||
|
||||
if (visibility.value === 'specified') {
|
||||
|
|
|
|||
|
|
@ -524,6 +524,7 @@ export function getRenoteMenu(props: {
|
|||
|
||||
const channelRenoteItems: MenuItem[] = [];
|
||||
const normalRenoteItems: MenuItem[] = [];
|
||||
const normalExternalChannelRenoteItems: MenuItem[] = [];
|
||||
|
||||
if (appearNote.channel) {
|
||||
channelRenoteItems.push(...[{
|
||||
|
|
@ -602,12 +603,49 @@ export function getRenoteMenu(props: {
|
|||
});
|
||||
},
|
||||
}]);
|
||||
|
||||
normalExternalChannelRenoteItems.push({
|
||||
type: 'parent',
|
||||
icon: 'ti ti-repeat',
|
||||
text: appearNote.channel ? i18n.ts.renoteToOtherChannel : i18n.ts.renoteToChannel,
|
||||
children: async () => {
|
||||
const channels = await misskeyApi('channels/my-favorites', {
|
||||
limit: 30,
|
||||
});
|
||||
return channels.filter((channel) => {
|
||||
if (!appearNote.channelId) return true;
|
||||
return channel.id !== appearNote.channelId;
|
||||
}).map((channel) => ({
|
||||
text: channel.name,
|
||||
action: () => {
|
||||
const el = props.renoteButton.value;
|
||||
if (el) {
|
||||
const rect = el.getBoundingClientRect();
|
||||
const x = rect.left + (el.offsetWidth / 2);
|
||||
const y = rect.top + (el.offsetHeight / 2);
|
||||
os.popup(MkRippleEffect, { x, y }, {}, 'end');
|
||||
}
|
||||
|
||||
if (!props.mock) {
|
||||
misskeyApi('notes/create', {
|
||||
renoteId: appearNote.id,
|
||||
channelId: channel.id,
|
||||
}).then(() => {
|
||||
os.toast(i18n.tsx.renotedToX({ name: channel.name }));
|
||||
});
|
||||
}
|
||||
},
|
||||
}));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const renoteItems = [
|
||||
...normalRenoteItems,
|
||||
...(channelRenoteItems.length > 0 && normalRenoteItems.length > 0) ? [{ type: 'divider' }] as MenuItem[] : [],
|
||||
...channelRenoteItems,
|
||||
...(normalExternalChannelRenoteItems.length > 0 && (normalRenoteItems.length > 0 || channelRenoteItems.length > 0)) ? [{ type: 'divider' }] as MenuItem[] : [],
|
||||
...normalExternalChannelRenoteItems,
|
||||
];
|
||||
|
||||
return {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue