Merge branch 'develop' into pag-back

This commit is contained in:
tamaina
2023-08-24 14:54:47 +09:00
committed by GitHub
616 changed files with 5021 additions and 2993 deletions

View File

@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div :class="[$style.root, { [$style.children]: depth > 1 }]">
<div v-if="!muted" :class="[$style.root, { [$style.children]: depth > 1 }]">
<div :class="$style.main">
<div v-if="note.channel" :class="$style.colorBar" :style="{ background: note.channel.color }"></div>
<MkAvatar :class="$style.avatar" :user="note.user" link preview/>
@@ -28,10 +28,19 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkA class="_link" :to="notePage(note)">{{ i18n.ts.continueThread }} <i class="ti ti-chevron-double-right"></i></MkA>
</div>
</div>
<div v-else :class="$style.muted" @click="muted = false">
<I18n :src="i18n.ts.userSaysSomething" tag="small">
<template #name>
<MkA v-user-preview="note.userId" :to="userPage(note.user)">
<MkUserName :user="note.user"/>
</MkA>
</template>
</I18n>
</div>
</template>
<script lang="ts" setup>
import { } from 'vue';
import { ref } from 'vue';
import * as misskey from 'misskey-js';
import MkNoteHeader from '@/components/MkNoteHeader.vue';
import MkSubNoteContent from '@/components/MkSubNoteContent.vue';
@@ -40,6 +49,9 @@ import { notePage } from '@/filters/note';
import * as os from '@/os';
import { i18n } from '@/i18n';
import { $i } from '@/account';
import { userPage } from "@/filters/user";
import { checkWordMute } from "@/scripts/check-word-mute";
import { defaultStore } from "@/store";
const props = withDefaults(defineProps<{
note: misskey.entities.Note;
@@ -51,6 +63,8 @@ const props = withDefaults(defineProps<{
depth: 1,
});
const muted = ref(checkWordMute(props.note, $i, defaultStore.state.mutedWords));
let showContent = $ref(false);
let replies: misskey.entities.Note[] = $ref([]);
@@ -139,4 +153,12 @@ if (props.detail) {
}
}
}
.muted {
text-align: center;
padding: 8px !important;
border: 1px solid var(--divider);
margin: 8px 8px 0 8px;
border-radius: 8px;
}
</style>

View File

@@ -171,7 +171,8 @@ let poll = $ref<{
expiredAfter: string | null;
} | null>(null);
let useCw = $ref(false);
let showPreview = $ref(false);
let showPreview = $ref(defaultStore.state.showPreview);
watch($$(showPreview), () => defaultStore.set('showPreview', showPreview));
let cw = $ref<string | null>(null);
let localOnly = $ref<boolean>(props.initialLocalOnly ?? defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly);
let visibility = $ref(props.initialVisibility ?? (defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility) as typeof misskey.noteVisibilities[number]);

View File

@@ -133,7 +133,7 @@ let unknownUrl = $ref(false);
const requestUrl = new URL(props.url);
if (!['http:', 'https:'].includes(requestUrl.protocol)) throw new Error('invalid url');
if (requestUrl.hostname === 'twitter.com' || requestUrl.hostname === 'mobile.twitter.com') {
if (requestUrl.hostname === 'twitter.com' || requestUrl.hostname === 'mobile.twitter.com' || requestUrl.hostname === 'x.com' || requestUrl.hostname === 'mobile.x.com') {
const m = requestUrl.pathname.match(/^\/.+\/status(?:es)?\/(\d+)/);
if (m) tweetId = m[1];
}

View File

@@ -175,8 +175,13 @@ export default function(props: {
}, genEl(token.children, scale));
}
case 'rainbow': {
if (!useAnim) {
return h('span', {
class: '_mfm_rainbow_fallback_',
}, genEl(token.children, scale));
}
const speed = validTime(token.props.args.speed) ?? '1s';
style = useAnim ? `animation: mfm-rainbow ${speed} linear infinite;` : '';
style = `animation: mfm-rainbow ${speed} linear infinite;`;
break;
}
case 'sparkle': {

View File

@@ -57,6 +57,9 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</div>
</MkFolder>
<MkButton class="button" @click="more()">
<i class="ti ti-reload"></i>{{ i18n.ts.more }}
</MkButton>
</div>
</MkSpacer>
</MkStickyContainer>
@@ -124,6 +127,12 @@ async function save(announcement) {
}
}
function more() {
os.api('admin/announcements/list', { untilId: announcements.reduce((acc, announcement) => announcement.id != null ? announcement : acc).id }).then(announcementResponse => {
announcements = announcements.concat(announcementResponse);
});
}
function refresh() {
os.api('admin/announcements/list').then(announcementResponse => {
announcements = announcementResponse;

View File

@@ -23,6 +23,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkButton @click="show"><i class="ti ti-eye"></i> {{ i18n.ts.show }}</MkButton>
<MkButton v-if="flash" danger @click="del"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
</div>
<MkSelect v-model="visibility">
<template #label>{{ i18n.ts.visibility }}</template>
<option :key="'public'" :value="'public'">{{ i18n.ts.public }}</option>
<option :key="'private'" :value="'private'">{{ i18n.ts.private }}</option>
</MkSelect>
</div>
</MkSpacer>
</MkStickyContainer>
@@ -36,6 +41,7 @@ import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import MkTextarea from '@/components/MkTextarea.vue';
import MkInput from '@/components/MkInput.vue';
import MkSelect from '@/components/MkSelect.vue';
import { useRouter } from '@/router';
const PRESET_DEFAULT = `/// @ 0.15.0
@@ -358,6 +364,7 @@ const props = defineProps<{
}>();
let flash = $ref(null);
let visibility = $ref('public');
if (props.id) {
flash = await os.api('flash/show', {
@@ -402,6 +409,7 @@ async function save() {
summary,
permissions,
script,
visibility,
});
} else {
const created = await os.apiWithDialog('flash/create', {

View File

@@ -12,18 +12,22 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkFolder>
<template #label>{{ i18n.ts.options }}</template>
<MkFolder>
<template #label>{{ i18n.ts.specifyUser }}</template>
<template v-if="user" #suffix>@{{ user.username }}</template>
<div class="_gaps_m">
<MkSwitch v-model="isLocalOnly">{{ i18n.ts.localOnly }}</MkSwitch>
<div style="text-align: center;" class="_gaps">
<div v-if="user">@{{ user.username }}</div>
<div>
<MkButton v-if="user == null" primary rounded inline @click="selectUser">{{ i18n.ts.selectUser }}</MkButton>
<MkButton v-else danger rounded inline @click="user = null">{{ i18n.ts.remove }}</MkButton>
<MkFolder>
<template #label>{{ i18n.ts.specifyUser }}</template>
<template v-if="user" #suffix>@{{ user.username }}</template>
<div style="text-align: center;" class="_gaps">
<div v-if="user">@{{ user.username }}</div>
<div>
<MkButton v-if="user == null" primary rounded inline @click="selectUser">{{ i18n.ts.selectUser }}</MkButton>
<MkButton v-else danger rounded inline @click="user = null">{{ i18n.ts.remove }}</MkButton>
</div>
</div>
</div>
</MkFolder>
</MkFolder>
</div>
</MkFolder>
<div>
<MkButton large primary gradate rounded style="margin: 0 auto;" @click="search">{{ i18n.ts.search }}</MkButton>
@@ -43,6 +47,7 @@ import MkNotes from '@/components/MkNotes.vue';
import MkInput from '@/components/MkInput.vue';
import MkRadios from '@/components/MkRadios.vue';
import MkButton from '@/components/MkButton.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import { i18n } from '@/i18n';
import * as os from '@/os';
import MkFoldableSection from '@/components/MkFoldableSection.vue';
@@ -59,6 +64,7 @@ let searchQuery = $ref('');
let searchOrigin = $ref('combined');
let notePagination = $ref();
let user = $ref(null);
let isLocalOnly = $ref(false);
function selectUser() {
os.selectUser().then(_user => {
@@ -98,6 +104,8 @@ async function search() {
},
};
if (isLocalOnly) notePagination.params.host = '.';
key++;
}
</script>

View File

@@ -19,6 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { defineAsyncComponent, nextTick, ref } from 'vue';
import { compareVersions } from 'compare-versions';
import { Interpreter, Parser, utils } from '@syuilo/aiscript';
import { v4 as uuid } from 'uuid';
import MkTextarea from '@/components/MkTextarea.vue';
@@ -44,6 +45,14 @@ function installPlugin({ id, meta, src, token }) {
}));
}
function isSupportedAiScriptVersion(version: string): boolean {
try {
return (compareVersions(version, '0.12.0') >= 0);
} catch (err) {
return false;
}
}
async function install() {
if (code.value == null) return;
@@ -54,7 +63,7 @@ async function install() {
text: 'No language version annotation found :(',
});
return;
} else if (!(lv.startsWith('0.12.') || lv.startsWith('0.13.'))) {
} else if (!isSupportedAiScriptVersion(lv)) {
os.alert({
type: 'error',
text: `aiscript version '${lv}' is not supported :(`,

View File

@@ -0,0 +1,31 @@
<!--
SPDX-FileCopyrightText: syuilo and other misskey contributors
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<MkSpacer :contentMax="700">
<MkPagination v-slot="{items}" ref="list" :pagination="pagination">
<MkFlashPreview v-for="flash in items" :key="flash.id" :flash="flash" class="_margin"/>
</MkPagination>
</MkSpacer>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import * as misskey from 'misskey-js';
import MkFlashPreview from '@/components/MkFlashPreview.vue';
import MkPagination from '@/components/MkPagination.vue';
const props = defineProps<{
user: misskey.entities.User;
}>();
const pagination = {
endpoint: 'users/flashs' as const,
limit: 20,
params: computed(() => ({
userId: props.user.id,
})),
};
</script>

View File

@@ -16,6 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<XClips v-else-if="tab === 'clips'" :user="user"/>
<XLists v-else-if="tab === 'lists'" :user="user"/>
<XPages v-else-if="tab === 'pages'" :user="user"/>
<XFlashs v-else-if="tab === 'flashs'" :user="user"/>
<XGallery v-else-if="tab === 'gallery'" :user="user"/>
</div>
<MkError v-else-if="error" @retry="fetchUser()"/>
@@ -42,6 +43,7 @@ const XReactions = defineAsyncComponent(() => import('./reactions.vue'));
const XClips = defineAsyncComponent(() => import('./clips.vue'));
const XLists = defineAsyncComponent(() => import('./lists.vue'));
const XPages = defineAsyncComponent(() => import('./pages.vue'));
const XFlashs = defineAsyncComponent(() => import('./flashs.vue'));
const XGallery = defineAsyncComponent(() => import('./gallery.vue'));
const props = withDefaults(defineProps<{
@@ -103,6 +105,10 @@ const headerTabs = $computed(() => user ? [{
key: 'pages',
title: i18n.ts.pages,
icon: 'ti ti-news',
}, {
key: 'flashs',
title: 'Play',
icon: 'ti ti-player-play',
}, {
key: 'gallery',
title: i18n.ts.gallery,

View File

@@ -8,6 +8,7 @@ import * as os from '@/os';
import { $i } from '@/account';
import { miLocalStorage } from '@/local-storage';
import { customEmojis } from '@/custom-emojis';
import { lang } from '@/config';
export function createAiScriptEnv(opts) {
let apiRequests = 0;
@@ -16,6 +17,7 @@ export function createAiScriptEnv(opts) {
USER_NAME: $i ? values.STR($i.name) : values.NULL,
USER_USERNAME: $i ? values.STR($i.username) : values.NULL,
CUSTOM_EMOJIS: utils.jsToVal(customEmojis.value),
LOCALE: values.STR(lang),
'Mk:dialog': values.FN_NATIVE(async ([title, text, type]) => {
await os.alert({
type: type ? type.value : 'info',

View File

@@ -156,7 +156,8 @@ export function getUserMenu(user: misskey.entities.UserDetailed, router: Router
icon: 'ti ti-mail',
text: i18n.ts.sendMessage,
action: () => {
os.post({ specified: user, initialText: `@${user.username} ` });
const canonical = user.host === null ? `@${user.username}` : `@${user.username}@${user.host}`;
os.post({ specified: user, initialText: `${canonical} ` });
},
}, null, {
icon: 'ti ti-pencil',

View File

@@ -136,6 +136,10 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'deviceAccount',
default: false,
},
showPreview: {
where: 'device',
default: false,
},
statusbars: {
where: 'deviceAccount',
default: [] as {

View File

@@ -527,6 +527,13 @@ hr {
}
}
._mfm_rainbow_fallback_ {
background-image: linear-gradient(to right, rgb(255, 0, 0) 0%, rgb(255, 165, 0) 17%, rgb(255, 255, 0) 33%, rgb(0, 255, 0) 50%, rgb(0, 255, 255) 67%, rgb(0, 0, 255) 83%, rgb(255, 0, 255) 100%);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
@keyframes mfm-spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }

View File

@@ -152,4 +152,13 @@ describe('MkUrlPreview', () => {
assert.strictEqual(iframe?.getAttribute('allow'), 'fullscreen;web-share');
assert.strictEqual(iframe?.getAttribute('sandbox'), 'allow-popups allow-scripts allow-same-origin');
});
test('Loading a post in iframe', async () => {
const iframe = await renderAndOpenPreview({
url: 'https://x.com/i/web/status/1685072521782325249',
});
assert.exists(iframe, 'iframe should exist');
assert.strictEqual(iframe?.getAttribute('allow'), 'fullscreen;web-share');
assert.strictEqual(iframe?.getAttribute('sandbox'), 'allow-popups allow-scripts allow-same-origin');
});
});