Merge branch 'develop' into fix/postform-footer-button-overflow

This commit is contained in:
1Step621 2024-01-08 15:07:53 +09:00 committed by GitHub
commit b5d676ae7b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
356 changed files with 4411 additions and 1563 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 128 128" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M0,0L128,0L64,64L0,0Z" style="fill:rgb(255,61,0);"/>
<path d="M0,0L128,0L64,64L0,0ZM28.971,12L64,47.029C64,47.029 99.029,12 99.029,12L28.971,12Z" style="fill:rgb(255,122,0);"/>
</svg>

After

Width:  |  Height:  |  Size: 646 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 67 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View file

@ -4,7 +4,7 @@
"type": "module",
"scripts": {
"watch": "vite",
"dev": "vite --config vite.config.local-dev.ts",
"dev": "vite --config vite.config.local-dev.ts --debug hmr",
"build": "vite build",
"storybook-dev": "nodemon --verbose --watch src --ext \"mdx,ts,vue\" --ignore \"*.stories.ts\" --exec \"pnpm build-storybook-pre && pnpm exec storybook dev -p 6006 --ci\"",
"build-storybook-pre": "(tsc -p .storybook || echo done.) && node .storybook/generate.js && node .storybook/preload-locale.js && node .storybook/preload-theme.js",
@ -19,6 +19,7 @@
"dependencies": {
"@discordapp/twemoji": "15.0.2",
"@github/webauthn-json": "2.1.1",
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
"@misskey-dev/browser-image-resizer": "2.2.1-misskey.10",
"@rollup/plugin-json": "6.1.0",
"@rollup/plugin-replace": "5.0.5",

View file

@ -11,7 +11,8 @@ import { miLocalStorage } from '@/local-storage.js';
import { MenuButton } from '@/types/menu.js';
import { del, get, set } from '@/scripts/idb-proxy.js';
import { apiUrl } from '@/config.js';
import { waiting, api, popup, popupMenu, success, alert } from '@/os.js';
import { waiting, popup, popupMenu, success, alert } from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { unisonReload, reloadChannel } from '@/scripts/unison-reload.js';
// TODO: 他のタブと永続化されたstateを同期
@ -23,9 +24,14 @@ const accountData = miLocalStorage.getItem('account');
// TODO: 外部からはreadonlyに
export const $i = accountData ? reactive(JSON.parse(accountData) as Account) : null;
export const iAmModerator = $i != null && ($i.isAdmin || $i.isModerator);
export const iAmModerator = $i != null && ($i.isAdmin === true || $i.isModerator === true);
export const iAmAdmin = $i != null && $i.isAdmin;
export function signinRequired() {
if ($i == null) throw new Error('signin required');
return $i;
}
export let notesCount = $i == null ? 0 : $i.notesCount;
export function incNotesCount() {
notesCount++;
@ -246,7 +252,7 @@ export async function openAccountMenu(opts: {
}
const storedAccounts = await getAccounts().then(accounts => accounts.filter(x => x.id !== $i.id));
const accountsPromise = api('users/show', { userIds: storedAccounts.map(x => x.id) });
const accountsPromise = misskeyApi('users/show', { userIds: storedAccounts.map(x => x.id) });
function createItem(account: Misskey.entities.UserDetailed) {
return {

View file

@ -22,6 +22,7 @@ import { getAccountFromId } from '@/scripts/get-account-from-id.js';
import { deckStore } from '@/ui/deck/deck-store.js';
import { miLocalStorage } from '@/local-storage.js';
import { fetchCustomEmojis } from '@/custom-emojis.js';
import { setupRouter } from '@/global/router/definition.js';
export async function common(createVue: () => App<Element>) {
console.info(`Misskey v${version}`);
@ -241,6 +242,8 @@ export async function common(createVue: () => App<Element>) {
const app = createVue();
setupRouter(app);
if (_DEV_) {
app.config.performance = true;
}

View file

@ -3,23 +3,23 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { createApp, markRaw, defineAsyncComponent } from 'vue';
import { createApp, defineAsyncComponent, markRaw } from 'vue';
import { common } from './common.js';
import { ui } from '@/config.js';
import { i18n } from '@/i18n.js';
import { confirm, alert, post, popup, toast } from '@/os.js';
import { alert, confirm, popup, post, toast } from '@/os.js';
import { useStream } from '@/stream.js';
import * as sound from '@/scripts/sound.js';
import { $i, updateAccount, signout } from '@/account.js';
import { defaultStore, ColdDeviceStorage } from '@/store.js';
import { $i, signout, updateAccount } from '@/account.js';
import { ColdDeviceStorage, defaultStore } from '@/store.js';
import { makeHotkey } from '@/scripts/hotkey.js';
import { reactionPicker } from '@/scripts/reaction-picker.js';
import { miLocalStorage } from '@/local-storage.js';
import { claimAchievement, claimedAchievements } from '@/scripts/achievements.js';
import { mainRouter } from '@/router.js';
import { initializeSw } from '@/scripts/initialize-sw.js';
import { deckStore } from '@/ui/deck/deck-store.js';
import { emojiPicker } from '@/scripts/emoji-picker.js';
import { mainRouter } from '@/global/router/main.js';
export async function mainBoot() {
const { isClientUpdated } = await common(() => createApp(

View file

@ -5,9 +5,9 @@
import * as Misskey from 'misskey-js';
import { Cache } from '@/scripts/cache.js';
import { api } from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
export const clipsCache = new Cache<Misskey.entities.Clip[]>(1000 * 60 * 30, () => api('clips/list'));
export const rolesCache = new Cache(1000 * 60 * 30, () => api('admin/roles/list'));
export const userListsCache = new Cache<Misskey.entities.UserList[]>(1000 * 60 * 30, () => api('users/lists/list'));
export const antennasCache = new Cache<Misskey.entities.Antenna[]>(1000 * 60 * 30, () => api('antennas/list'));
export const clipsCache = new Cache<Misskey.entities.Clip[]>(1000 * 60 * 30, () => misskeyApi('clips/list'));
export const rolesCache = new Cache(1000 * 60 * 30, () => misskeyApi('admin/roles/list'));
export const userListsCache = new Cache<Misskey.entities.UserList[]>(1000 * 60 * 30, () => misskeyApi('users/lists/list'));
export const antennasCache = new Cache<Misskey.entities.Antenna[]>(1000 * 60 * 30, () => misskeyApi('antennas/list'));

View file

@ -17,7 +17,7 @@ import * as Misskey from 'misskey-js';
import MkMention from './MkMention.vue';
import { i18n } from '@/i18n.js';
import { host as localHost } from '@/config.js';
import { api } from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
const user = ref<Misskey.entities.UserLite>();
@ -25,7 +25,7 @@ const props = defineProps<{
movedTo: string; // user id
}>();
api('users/show', { userId: props.movedTo }).then(u => user.value = u);
misskeyApi('users/show', { userId: props.movedTo }).then(u => user.value = u);
</script>
<style lang="scss" module>

View file

@ -55,6 +55,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import * as Misskey from 'misskey-js';
import { onMounted, ref, computed } from 'vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { i18n } from '@/i18n.js';
import { ACHIEVEMENT_TYPES, ACHIEVEMENT_BADGES, claimAchievement } from '@/scripts/achievements.js';
@ -71,7 +72,7 @@ const achievements = ref<Misskey.entities.UsersAchievementsResponse | null>(null
const lockedAchievements = computed(() => ACHIEVEMENT_TYPES.filter(x => !(achievements.value ?? []).some(a => a.name === x)));
function fetch() {
os.api('users/achievements', { userId: props.user.id }).then(res => {
misskeyApi('users/achievements', { userId: props.user.id }).then(res => {
achievements.value = [];
for (const t of ACHIEVEMENT_TYPES) {
const a = res.find(x => x.name === t);

View file

@ -25,6 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import { onMounted, shallowRef } from 'vue';
import * as Misskey from 'misskey-js';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import MkModal from '@/components/MkModal.vue';
import MkButton from '@/components/MkButton.vue';
import { i18n } from '@/i18n.js';
@ -49,7 +50,7 @@ async function ok() {
}
modal.value.close();
os.api('i/read-announcement', { announcementId: props.announcement.id });
misskeyApi('i/read-announcement', { announcementId: props.announcement.id });
updateAccount({
unreadAnnouncements: $i!.unreadAnnouncements.filter(a => a.id !== props.announcement.id),
});

View file

@ -45,6 +45,7 @@ import contains from '@/scripts/contains.js';
import { char2twemojiFilePath, char2fluentEmojiFilePath } from '@/scripts/emoji-base.js';
import { acct } from '@/filters/user.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { defaultStore } from '@/store.js';
import { emojilist, getEmojiName } from '@/scripts/emojilist.js';
import { i18n } from '@/i18n.js';
@ -201,7 +202,7 @@ function exec() {
users.value = JSON.parse(cache);
fetching.value = false;
} else {
os.api('users/search-by-username-and-host', {
misskeyApi('users/search-by-username-and-host', {
username: props.q,
limit: 10,
detail: false,
@ -224,7 +225,7 @@ function exec() {
hashtags.value = hashtags;
fetching.value = false;
} else {
os.api('hashtags/search', {
misskeyApi('hashtags/search', {
query: props.q,
limit: 30,
}).then(searchedHashtags => {

View file

@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import * as Misskey from 'misskey-js';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
const props = withDefaults(defineProps<{
userIds: string[];
@ -27,7 +27,7 @@ const props = withDefaults(defineProps<{
const users = ref<Misskey.entities.UserLite[]>([]);
onMounted(async () => {
users.value = await os.api('users/show', {
users.value = await misskeyApi('users/show', {
userIds: props.userIds,
}) as unknown as Misskey.entities.UserLite[];
});

View file

@ -6,12 +6,16 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div>
<span v-if="!available">{{ i18n.ts.waiting }}<MkEllipsis/></span>
<div ref="captchaEl"></div>
<div v-if="props.provider == 'mcaptcha'">
<div id="mcaptcha__widget-container" class="m-captcha-style"></div>
<div ref="captchaEl"></div>
</div>
<div v-else ref="captchaEl"></div>
</div>
</template>
<script lang="ts" setup>
import { ref, shallowRef, computed, onMounted, onBeforeUnmount, watch } from 'vue';
import { ref, shallowRef, computed, onMounted, onBeforeUnmount, watch, onUnmounted } from 'vue';
import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';
@ -26,7 +30,7 @@ export type Captcha = {
getResponse(id: string): string;
};
export type CaptchaProvider = 'hcaptcha' | 'recaptcha' | 'turnstile';
export type CaptchaProvider = 'hcaptcha' | 'recaptcha' | 'turnstile' | 'mcaptcha';
type CaptchaContainer = {
readonly [_ in CaptchaProvider]?: Captcha;
@ -39,6 +43,7 @@ declare global {
const props = defineProps<{
provider: CaptchaProvider;
sitekey: string | null; // null will show error on request
instanceUrl?: string | null;
modelValue?: string | null;
}>();
@ -55,6 +60,7 @@ const variable = computed(() => {
case 'hcaptcha': return 'hcaptcha';
case 'recaptcha': return 'grecaptcha';
case 'turnstile': return 'turnstile';
case 'mcaptcha': return 'mcaptcha';
}
});
@ -65,6 +71,7 @@ const src = computed(() => {
case 'hcaptcha': return 'https://js.hcaptcha.com/1/api.js?render=explicit&recaptchacompat=off';
case 'recaptcha': return 'https://www.recaptcha.net/recaptcha/api.js?render=explicit';
case 'turnstile': return 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit';
case 'mcaptcha': return null;
}
});
@ -72,9 +79,9 @@ const scriptId = computed(() => `script-${props.provider}`);
const captcha = computed<Captcha>(() => window[variable.value] || {} as unknown as Captcha);
if (loaded) {
if (loaded || props.provider === 'mcaptcha') {
available.value = true;
} else {
} else if (src.value !== null) {
(document.getElementById(scriptId.value) ?? document.head.appendChild(Object.assign(document.createElement('script'), {
async: true,
id: scriptId.value,
@ -87,7 +94,7 @@ function reset() {
if (captcha.value.reset) captcha.value.reset();
}
function requestRender() {
async function requestRender() {
if (captcha.value.render && captchaEl.value instanceof Element) {
captcha.value.render(captchaEl.value, {
sitekey: props.sitekey,
@ -96,6 +103,15 @@ function requestRender() {
'expired-callback': callback,
'error-callback': callback,
});
} else if (props.provider === 'mcaptcha' && props.instanceUrl && props.sitekey) {
const { default: Widget } = await import('@mcaptcha/vanilla-glue');
// @ts-expect-error avoid typecheck error
new Widget({
siteKey: {
instanceUrl: new URL(props.instanceUrl),
key: props.sitekey,
},
});
} else {
window.setTimeout(requestRender, 1);
}
@ -105,14 +121,27 @@ function callback(response?: string) {
emit('update:modelValue', typeof response === 'string' ? response : null);
}
function onReceivedMessage(message: MessageEvent) {
if (message.data.token) {
if (props.instanceUrl && new URL(message.origin).host === new URL(props.instanceUrl).host) {
callback(<string>message.data.token);
}
}
}
onMounted(() => {
if (available.value) {
window.addEventListener('message', onReceivedMessage);
requestRender();
} else {
watch(available, requestRender);
}
});
onUnmounted(() => {
window.removeEventListener('message', onReceivedMessage);
});
onBeforeUnmount(() => {
reset();
});

View file

@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { i18n } from '@/i18n.js';
const props = withDefaults(defineProps<{
@ -44,12 +44,12 @@ async function onClick() {
try {
if (isFollowing.value) {
await os.api('channels/unfollow', {
await misskeyApi('channels/unfollow', {
channelId: props.channel.id,
});
isFollowing.value = false;
} else {
await os.api('channels/follow', {
await misskeyApi('channels/follow', {
channelId: props.channel.id,
});
isFollowing.value = true;

View file

@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import { onMounted, ref, shallowRef, watch, PropType } from 'vue';
import { Chart } from 'chart.js';
import gradient from 'chartjs-plugin-gradient';
import * as os from '@/os.js';
import { misskeyApiGet } from '@/scripts/misskey-api.js';
import { defaultStore } from '@/store.js';
import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
import { chartVLine } from '@/scripts/chart-vline.js';
@ -277,7 +277,7 @@ const exportData = () => {
};
const fetchFederationChart = async (): Promise<typeof chartData> => {
const raw = await os.apiGet('charts/federation', { limit: props.limit, span: props.span });
const raw = await misskeyApiGet('charts/federation', { limit: props.limit, span: props.span });
return {
series: [{
name: 'Received',
@ -327,7 +327,7 @@ const fetchFederationChart = async (): Promise<typeof chartData> => {
};
const fetchApRequestChart = async (): Promise<typeof chartData> => {
const raw = await os.apiGet('charts/ap-request', { limit: props.limit, span: props.span });
const raw = await misskeyApiGet('charts/ap-request', { limit: props.limit, span: props.span });
return {
series: [{
name: 'In',
@ -349,7 +349,7 @@ const fetchApRequestChart = async (): Promise<typeof chartData> => {
};
const fetchNotesChart = async (type: string): Promise<typeof chartData> => {
const raw = await os.apiGet('charts/notes', { limit: props.limit, span: props.span });
const raw = await misskeyApiGet('charts/notes', { limit: props.limit, span: props.span });
return {
series: [{
name: 'All',
@ -396,7 +396,7 @@ const fetchNotesChart = async (type: string): Promise<typeof chartData> => {
};
const fetchNotesTotalChart = async (): Promise<typeof chartData> => {
const raw = await os.apiGet('charts/notes', { limit: props.limit, span: props.span });
const raw = await misskeyApiGet('charts/notes', { limit: props.limit, span: props.span });
return {
series: [{
name: 'Combined',
@ -415,7 +415,7 @@ const fetchNotesTotalChart = async (): Promise<typeof chartData> => {
};
const fetchUsersChart = async (total: boolean): Promise<typeof chartData> => {
const raw = await os.apiGet('charts/users', { limit: props.limit, span: props.span });
const raw = await misskeyApiGet('charts/users', { limit: props.limit, span: props.span });
return {
series: [{
name: 'Combined',
@ -443,7 +443,7 @@ const fetchUsersChart = async (total: boolean): Promise<typeof chartData> => {
};
const fetchActiveUsersChart = async (): Promise<typeof chartData> => {
const raw = await os.apiGet('charts/active-users', { limit: props.limit, span: props.span });
const raw = await misskeyApiGet('charts/active-users', { limit: props.limit, span: props.span });
return {
series: [{
name: 'Read & Write',
@ -495,7 +495,7 @@ const fetchActiveUsersChart = async (): Promise<typeof chartData> => {
};
const fetchDriveChart = async (): Promise<typeof chartData> => {
const raw = await os.apiGet('charts/drive', { limit: props.limit, span: props.span });
const raw = await misskeyApiGet('charts/drive', { limit: props.limit, span: props.span });
return {
bytes: true,
series: [{
@ -531,7 +531,7 @@ const fetchDriveChart = async (): Promise<typeof chartData> => {
};
const fetchDriveFilesChart = async (): Promise<typeof chartData> => {
const raw = await os.apiGet('charts/drive', { limit: props.limit, span: props.span });
const raw = await misskeyApiGet('charts/drive', { limit: props.limit, span: props.span });
return {
series: [{
name: 'All',
@ -566,7 +566,7 @@ const fetchDriveFilesChart = async (): Promise<typeof chartData> => {
};
const fetchInstanceRequestsChart = async (): Promise<typeof chartData> => {
const raw = await os.apiGet('charts/instance', { host: props.args.host, limit: props.limit, span: props.span });
const raw = await misskeyApiGet('charts/instance', { host: props.args.host, limit: props.limit, span: props.span });
return {
series: [{
name: 'In',
@ -588,7 +588,7 @@ const fetchInstanceRequestsChart = async (): Promise<typeof chartData> => {
};
const fetchInstanceUsersChart = async (total: boolean): Promise<typeof chartData> => {
const raw = await os.apiGet('charts/instance', { host: props.args.host, limit: props.limit, span: props.span });
const raw = await misskeyApiGet('charts/instance', { host: props.args.host, limit: props.limit, span: props.span });
return {
series: [{
name: 'Users',
@ -603,7 +603,7 @@ const fetchInstanceUsersChart = async (total: boolean): Promise<typeof chartData
};
const fetchInstanceNotesChart = async (total: boolean): Promise<typeof chartData> => {
const raw = await os.apiGet('charts/instance', { host: props.args.host, limit: props.limit, span: props.span });
const raw = await misskeyApiGet('charts/instance', { host: props.args.host, limit: props.limit, span: props.span });
return {
series: [{
name: 'Notes',
@ -618,7 +618,7 @@ const fetchInstanceNotesChart = async (total: boolean): Promise<typeof chartData
};
const fetchInstanceFfChart = async (total: boolean): Promise<typeof chartData> => {
const raw = await os.apiGet('charts/instance', { host: props.args.host, limit: props.limit, span: props.span });
const raw = await misskeyApiGet('charts/instance', { host: props.args.host, limit: props.limit, span: props.span });
return {
series: [{
name: 'Following',
@ -641,7 +641,7 @@ const fetchInstanceFfChart = async (total: boolean): Promise<typeof chartData> =
};
const fetchInstanceDriveUsageChart = async (total: boolean): Promise<typeof chartData> => {
const raw = await os.apiGet('charts/instance', { host: props.args.host, limit: props.limit, span: props.span });
const raw = await misskeyApiGet('charts/instance', { host: props.args.host, limit: props.limit, span: props.span });
return {
bytes: true,
series: [{
@ -657,7 +657,7 @@ const fetchInstanceDriveUsageChart = async (total: boolean): Promise<typeof char
};
const fetchInstanceDriveFilesChart = async (total: boolean): Promise<typeof chartData> => {
const raw = await os.apiGet('charts/instance', { host: props.args.host, limit: props.limit, span: props.span });
const raw = await misskeyApiGet('charts/instance', { host: props.args.host, limit: props.limit, span: props.span });
return {
series: [{
name: 'Drive files',
@ -672,7 +672,7 @@ const fetchInstanceDriveFilesChart = async (total: boolean): Promise<typeof char
};
const fetchPerUserNotesChart = async (): Promise<typeof chartData> => {
const raw = await os.apiGet('charts/user/notes', { userId: props.args.user.id, limit: props.limit, span: props.span });
const raw = await misskeyApiGet('charts/user/notes', { userId: props.args.user.id, limit: props.limit, span: props.span });
return {
series: [...(props.args.withoutAll ? [] : [{
name: 'All',
@ -704,7 +704,7 @@ const fetchPerUserNotesChart = async (): Promise<typeof chartData> => {
};
const fetchPerUserPvChart = async (): Promise<typeof chartData> => {
const raw = await os.apiGet('charts/user/pv', { userId: props.args.user.id, limit: props.limit, span: props.span });
const raw = await misskeyApiGet('charts/user/pv', { userId: props.args.user.id, limit: props.limit, span: props.span });
return {
series: [{
name: 'Unique PV (user)',
@ -731,7 +731,7 @@ const fetchPerUserPvChart = async (): Promise<typeof chartData> => {
};
const fetchPerUserFollowingChart = async (): Promise<typeof chartData> => {
const raw = await os.apiGet('charts/user/following', { userId: props.args.user.id, limit: props.limit, span: props.span });
const raw = await misskeyApiGet('charts/user/following', { userId: props.args.user.id, limit: props.limit, span: props.span });
return {
series: [{
name: 'Local',
@ -746,7 +746,7 @@ const fetchPerUserFollowingChart = async (): Promise<typeof chartData> => {
};
const fetchPerUserFollowersChart = async (): Promise<typeof chartData> => {
const raw = await os.apiGet('charts/user/following', { userId: props.args.user.id, limit: props.limit, span: props.span });
const raw = await misskeyApiGet('charts/user/following', { userId: props.args.user.id, limit: props.limit, span: props.span });
return {
series: [{
name: 'Local',
@ -761,7 +761,7 @@ const fetchPerUserFollowersChart = async (): Promise<typeof chartData> => {
};
const fetchPerUserDriveChart = async (): Promise<typeof chartData> => {
const raw = await os.apiGet('charts/user/drive', { userId: props.args.user.id, limit: props.limit, span: props.span });
const raw = await misskeyApiGet('charts/user/drive', { userId: props.args.user.id, limit: props.limit, span: props.span });
return {
series: [{
name: 'Inc',

View file

@ -45,9 +45,9 @@ import bytes from '@/filters/bytes.js';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { $i } from '@/account.js';
import { useRouter } from '@/router.js';
import { getDriveFileMenu } from '@/scripts/get-drive-file-menu.js';
import { deviceKind } from '@/scripts/device-kind.js';
import { useRouter } from '@/global/router/supplier.js';
const router = useRouter();

View file

@ -35,6 +35,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import { computed, defineAsyncComponent, ref } from 'vue';
import * as Misskey from 'misskey-js';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { i18n } from '@/i18n.js';
import { defaultStore } from '@/store.js';
import { claimAchievement } from '@/scripts/achievements.js';
@ -144,7 +145,7 @@ function onDrop(ev: DragEvent) {
if (driveFile != null && driveFile !== '') {
const file = JSON.parse(driveFile);
emit('removeFile', file.id);
os.api('drive/files/update', {
misskeyApi('drive/files/update', {
fileId: file.id,
folderId: props.folder.id,
});
@ -160,7 +161,7 @@ function onDrop(ev: DragEvent) {
if (folder.id === props.folder.id) return;
emit('removeFolder', folder.id);
os.api('drive/folders/update', {
misskeyApi('drive/folders/update', {
folderId: folder.id,
parentId: props.folder.id,
}).then(() => {
@ -214,7 +215,7 @@ function rename() {
default: props.folder.name,
}).then(({ canceled, result: name }) => {
if (canceled) return;
os.api('drive/folders/update', {
misskeyApi('drive/folders/update', {
folderId: props.folder.id,
name: name,
});
@ -222,7 +223,7 @@ function rename() {
}
function deleteFolder() {
os.api('drive/folders/delete', {
misskeyApi('drive/folders/delete', {
folderId: props.folder.id,
}).then(() => {
if (defaultStore.state.uploadFolder === props.folder.id) {

View file

@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
import * as Misskey from 'misskey-js';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { i18n } from '@/i18n.js';
const props = defineProps<{
@ -112,7 +112,7 @@ function onDrop(ev: DragEvent) {
if (driveFile != null && driveFile !== '') {
const file = JSON.parse(driveFile);
emit('removeFile', file.id);
os.api('drive/files/update', {
misskeyApi('drive/files/update', {
fileId: file.id,
folderId: props.folder ? props.folder.id : null,
});
@ -126,7 +126,7 @@ function onDrop(ev: DragEvent) {
// reject
if (props.folder && folder.id === props.folder.id) return;
emit('removeFolder', folder.id);
os.api('drive/folders/update', {
misskeyApi('drive/folders/update', {
folderId: folder.id,
parentId: props.folder ? props.folder.id : null,
});

View file

@ -102,6 +102,7 @@ import XNavFolder from '@/components/MkDrive.navFolder.vue';
import XFolder from '@/components/MkDrive.folder.vue';
import XFile from '@/components/MkDrive.file.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { useStream } from '@/stream.js';
import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';
@ -254,7 +255,7 @@ function onDrop(ev: DragEvent): any {
const file = JSON.parse(driveFile);
if (files.value.some(f => f.id === file.id)) return;
removeFile(file.id);
os.api('drive/files/update', {
misskeyApi('drive/files/update', {
fileId: file.id,
folderId: folder.value ? folder.value.id : null,
});
@ -270,7 +271,7 @@ function onDrop(ev: DragEvent): any {
if (folder.value && droppedFolder.id === folder.value.id) return false;
if (folders.value.some(f => f.id === droppedFolder.id)) return false;
removeFolder(droppedFolder.id);
os.api('drive/folders/update', {
misskeyApi('drive/folders/update', {
folderId: droppedFolder.id,
parentId: folder.value ? folder.value.id : null,
}).then(() => {
@ -307,7 +308,7 @@ function urlUpload() {
placeholder: i18n.ts.uploadFromUrlDescription,
}).then(({ canceled, result: url }) => {
if (canceled || !url) return;
os.api('drive/files/upload-from-url', {
misskeyApi('drive/files/upload-from-url', {
url: url,
folderId: folder.value ? folder.value.id : undefined,
});
@ -325,7 +326,7 @@ function createFolder() {
placeholder: i18n.ts.folderName,
}).then(({ canceled, result: name }) => {
if (canceled) return;
os.api('drive/folders/create', {
misskeyApi('drive/folders/create', {
name: name,
parentId: folder.value ? folder.value.id : undefined,
}).then(createdFolder => {
@ -341,7 +342,7 @@ function renameFolder(folderToRename: Misskey.entities.DriveFolder) {
default: folderToRename.name,
}).then(({ canceled, result: name }) => {
if (canceled) return;
os.api('drive/folders/update', {
misskeyApi('drive/folders/update', {
folderId: folderToRename.id,
name: name,
}).then(updatedFolder => {
@ -352,7 +353,7 @@ function renameFolder(folderToRename: Misskey.entities.DriveFolder) {
}
function deleteFolder(folderToDelete: Misskey.entities.DriveFolder) {
os.api('drive/folders/delete', {
misskeyApi('drive/folders/delete', {
folderId: folderToDelete.id,
}).then(() => {
//
@ -436,7 +437,7 @@ function move(target?: Misskey.entities.DriveFolder) {
fetching.value = true;
os.api('drive/folders/show', {
misskeyApi('drive/folders/show', {
folderId: target,
}).then(folderToMove => {
folder.value = folderToMove;
@ -535,7 +536,7 @@ async function fetch() {
const foldersMax = 30;
const filesMax = 30;
const foldersPromise = os.api('drive/folders', {
const foldersPromise = misskeyApi('drive/folders', {
folderId: folder.value ? folder.value.id : null,
limit: foldersMax + 1,
}).then(fetchedFolders => {
@ -546,7 +547,7 @@ async function fetch() {
return fetchedFolders;
});
const filesPromise = os.api('drive/files', {
const filesPromise = misskeyApi('drive/files', {
folderId: folder.value ? folder.value.id : null,
type: props.type,
limit: filesMax + 1,
@ -571,7 +572,7 @@ function fetchMoreFolders() {
const max = 30;
os.api('drive/folders', {
misskeyApi('drive/folders', {
folderId: folder.value ? folder.value.id : null,
type: props.type,
untilId: folders.value.at(-1)?.id,
@ -594,7 +595,7 @@ function fetchMoreFiles() {
const max = 30;
//
os.api('drive/files', {
misskeyApi('drive/files', {
folderId: folder.value ? folder.value.id : null,
type: props.type,
untilId: files.value.at(-1)?.id,

View file

@ -10,11 +10,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
import * as Misskey from 'misskey-js';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
const meta = ref<Misskey.entities.MetaResponse>();
os.api('meta', { detail: true }).then(gotMeta => {
misskeyApi('meta', { detail: true }).then(gotMeta => {
meta.value = gotMeta;
});
</script>

View file

@ -38,11 +38,12 @@ SPDX-License-Identifier: AGPL-3.0-only
import { onBeforeUnmount, onMounted, ref } from 'vue';
import * as Misskey from 'misskey-js';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { useStream } from '@/stream.js';
import { i18n } from '@/i18n.js';
import { claimAchievement } from '@/scripts/achievements.js';
import { $i } from '@/account.js';
import { defaultStore } from "@/store.js";
import { defaultStore } from '@/store.js';
const props = withDefaults(defineProps<{
user: Misskey.entities.UserDetailed,
@ -63,7 +64,7 @@ const wait = ref(false);
const connection = useStream().useChannel('main');
if (props.user.isFollowing == null) {
os.api('users/show', {
misskeyApi('users/show', {
userId: props.user.id,
})
.then(onFollowChange);
@ -88,17 +89,17 @@ async function onClick() {
if (canceled) return;
await os.api('following/delete', {
await misskeyApi('following/delete', {
userId: props.user.id,
});
} else {
if (hasPendingFollowRequestFromYou.value) {
await os.api('following/requests/cancel', {
await misskeyApi('following/requests/cancel', {
userId: props.user.id,
});
hasPendingFollowRequestFromYou.value = false;
} else {
await os.api('following/create', {
await misskeyApi('following/create', {
userId: props.user.id,
withReplies: defaultStore.state.defaultWithReplies,
});

View file

@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<MkSpacer :marginMin="20" :marginMax="32">
<div class="_gaps_m">
<div v-if="Object.keys(form).filter(item => !form[item].hidden).length > 0" class="_gaps_m">
<template v-for="item in Object.keys(form).filter(item => !form[item].hidden)">
<MkInput v-if="form[item].type === 'number'" v-model="values[item]" type="number" :step="form[item].step || 1">
<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ i18n.ts.optional }})</span></template>
@ -55,6 +55,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkButton>
</template>
</div>
<div v-else class="_fullinfo">
<img :src="infoImageUrl" class="_ghost"/>
<div>{{ i18n.ts.nothing }}</div>
</div>
</MkSpacer>
</MkModalWindow>
</template>
@ -70,6 +74,7 @@ import MkButton from './MkButton.vue';
import MkRadios from './MkRadios.vue';
import MkModalWindow from '@/components/MkModalWindow.vue';
import { i18n } from '@/i18n.js';
import { infoImageUrl } from '@/instance.js';
const props = defineProps<{
title: string;

View file

@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onMounted, nextTick, watch, shallowRef, ref } from 'vue';
import { Chart } from 'chart.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { defaultStore } from '@/store.js';
import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
import { alpha } from '@/scripts/color.js';
@ -72,19 +72,19 @@ async function renderChart() {
let values;
if (props.src === 'active-users') {
const raw = await os.api('charts/active-users', { limit: chartLimit, span: 'day' });
const raw = await misskeyApi('charts/active-users', { limit: chartLimit, span: 'day' });
values = raw.readWrite;
} else if (props.src === 'notes') {
const raw = await os.api('charts/notes', { limit: chartLimit, span: 'day' });
const raw = await misskeyApi('charts/notes', { limit: chartLimit, span: 'day' });
values = raw.local.inc;
} else if (props.src === 'ap-requests-inbox-received') {
const raw = await os.api('charts/ap-request', { limit: chartLimit, span: 'day' });
const raw = await misskeyApi('charts/ap-request', { limit: chartLimit, span: 'day' });
values = raw.inboxReceived;
} else if (props.src === 'ap-requests-deliver-succeeded') {
const raw = await os.api('charts/ap-request', { limit: chartLimit, span: 'day' });
const raw = await misskeyApi('charts/ap-request', { limit: chartLimit, span: 'day' });
values = raw.deliverSucceeded;
} else if (props.src === 'ap-requests-deliver-failed') {
const raw = await os.api('charts/ap-request', { limit: chartLimit, span: 'day' });
const raw = await misskeyApi('charts/ap-request', { limit: chartLimit, span: 'day' });
values = raw.deliverFailed;
}

View file

@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import { ref } from 'vue';
import * as Misskey from 'misskey-js';
import MkMiniChart from '@/components/MkMiniChart.vue';
import * as os from '@/os.js';
import { misskeyApiGet } from '@/scripts/misskey-api.js';
import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js';
const props = defineProps<{
@ -27,7 +27,7 @@ const props = defineProps<{
const chartValues = ref<number[] | null>(null);
os.apiGet('charts/instance', { host: props.instance.host, limit: 16 + 1, span: 'day' }).then(res => {
misskeyApiGet('charts/instance', { host: props.instance.host, limit: 16 + 1, span: 'day' }).then(res => {
//
res['requests.received'].splice(0, 1);
chartValues.value = res['requests.received'];

View file

@ -90,6 +90,7 @@ import MkSelect from '@/components/MkSelect.vue';
import MkChart from '@/components/MkChart.vue';
import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
import * as os from '@/os.js';
import { misskeyApiGet } from '@/scripts/misskey-api.js';
import { i18n } from '@/i18n.js';
import MkHeatmap from '@/components/MkHeatmap.vue';
import MkFoldableSection from '@/components/MkFoldableSection.vue';
@ -162,7 +163,7 @@ function createDoughnut(chartEl, tooltip, data) {
}
onMounted(() => {
os.apiGet('federation/stats', { limit: 30 }).then(fedStats => {
misskeyApiGet('federation/stats', { limit: 30 }).then(fedStats => {
createDoughnut(subDoughnutEl.value, externalTooltipHandler1, fedStats.topSubInstances.map(x => ({
name: x.host,
color: x.themeColor,

View file

@ -170,6 +170,7 @@ import { checkWordMute } from '@/scripts/check-word-mute.js';
import { userPage } from '@/filters/user.js';
import * as os from '@/os.js';
import * as sound from '@/scripts/sound.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { defaultStore, noteViewInterruptors } from '@/store.js';
import { reactionPicker } from '@/scripts/reaction-picker.js';
import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm.js';
@ -277,7 +278,7 @@ const keymap = {
};
provide('react', (reaction: string) => {
os.api('notes/reactions/create', {
misskeyApi('notes/reactions/create', {
noteId: appearNote.value.id,
reaction: reaction,
});
@ -298,7 +299,7 @@ if (props.mock) {
if (!props.mock) {
useTooltip(renoteButton, async (showing) => {
const renotes = await os.api('notes/renotes', {
const renotes = await misskeyApi('notes/renotes', {
noteId: appearNote.value.id,
limit: 11,
});
@ -350,7 +351,7 @@ function react(viaKeyboard = false): void {
return;
}
os.api('notes/reactions/create', {
misskeyApi('notes/reactions/create', {
noteId: appearNote.value.id,
reaction: '❤️',
});
@ -371,7 +372,7 @@ function react(viaKeyboard = false): void {
return;
}
os.api('notes/reactions/create', {
misskeyApi('notes/reactions/create', {
noteId: appearNote.value.id,
reaction: reaction,
});
@ -393,7 +394,7 @@ function undoReact(note): void {
return;
}
os.api('notes/reactions/delete', {
misskeyApi('notes/reactions/delete', {
noteId: note.id,
});
}
@ -453,7 +454,7 @@ function showRenoteMenu(viaKeyboard = false): void {
icon: 'ti ti-trash',
danger: true,
action: () => {
os.api('notes/delete', {
misskeyApi('notes/delete', {
noteId: note.value.id,
});
isDeleted.value = true;
@ -499,7 +500,7 @@ function focusAfter() {
}
function readPromo() {
os.api('promo/read', {
misskeyApi('promo/read', {
noteId: appearNote.value.id,
});
isDeleted.value = true;

View file

@ -210,6 +210,7 @@ import { checkWordMute } from '@/scripts/check-word-mute.js';
import { userPage } from '@/filters/user.js';
import { notePage } from '@/filters/note.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import * as sound from '@/scripts/sound.js';
import { defaultStore, noteViewInterruptors } from '@/store.js';
import { reactionPicker } from '@/scripts/reaction-picker.js';
@ -292,7 +293,7 @@ const keymap = {
};
provide('react', (reaction: string) => {
os.api('notes/reactions/create', {
misskeyApi('notes/reactions/create', {
noteId: appearNote.value.id,
reaction: reaction,
});
@ -326,7 +327,7 @@ useNoteCapture({
});
useTooltip(renoteButton, async (showing) => {
const renotes = await os.api('notes/renotes', {
const renotes = await misskeyApi('notes/renotes', {
noteId: appearNote.value.id,
limit: 11,
});
@ -371,7 +372,7 @@ function react(viaKeyboard = false): void {
if (appearNote.value.reactionAcceptance === 'likeOnly') {
sound.play('reaction');
os.api('notes/reactions/create', {
misskeyApi('notes/reactions/create', {
noteId: appearNote.value.id,
reaction: '❤️',
});
@ -387,7 +388,7 @@ function react(viaKeyboard = false): void {
reactionPicker.show(reactButton.value, reaction => {
sound.play('reaction');
os.api('notes/reactions/create', {
misskeyApi('notes/reactions/create', {
noteId: appearNote.value.id,
reaction: reaction,
});
@ -403,7 +404,7 @@ function react(viaKeyboard = false): void {
function undoReact(note): void {
const oldReaction = note.myReaction;
if (!oldReaction) return;
os.api('notes/reactions/delete', {
misskeyApi('notes/reactions/delete', {
noteId: note.id,
});
}
@ -446,7 +447,7 @@ function showRenoteMenu(viaKeyboard = false): void {
icon: 'ti ti-trash',
danger: true,
action: () => {
os.api('notes/delete', {
misskeyApi('notes/delete', {
noteId: note.value.id,
});
isDeleted.value = true;
@ -468,7 +469,7 @@ const repliesLoaded = ref(false);
function loadReplies() {
repliesLoaded.value = true;
os.api('notes/children', {
misskeyApi('notes/children', {
noteId: appearNote.value.id,
limit: 30,
}).then(res => {
@ -480,7 +481,7 @@ const conversationLoaded = ref(false);
function loadConversation() {
conversationLoaded.value = true;
os.api('notes/conversation', {
misskeyApi('notes/conversation', {
noteId: appearNote.value.replyId,
}).then(res => {
conversation.value = res.reverse();

View file

@ -46,7 +46,7 @@ import MkNoteHeader from '@/components/MkNoteHeader.vue';
import MkSubNoteContent from '@/components/MkSubNoteContent.vue';
import MkCwButton from '@/components/MkCwButton.vue';
import { notePage } from '@/filters/note.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { i18n } from '@/i18n.js';
import { $i } from '@/account.js';
import { userPage } from '@/filters/user.js';
@ -68,7 +68,7 @@ const showContent = ref(false);
const replies = ref<Misskey.entities.Note[]>([]);
if (props.detail) {
os.api('notes/children', {
misskeyApi('notes/children', {
noteId: props.note.id,
limit: 5,
}).then(res => {

View file

@ -145,7 +145,7 @@ import { getNoteSummary } from '@/scripts/get-note-summary.js';
import { notePage } from '@/filters/note.js';
import { userPage } from '@/filters/user.js';
import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { $i } from '@/account.js';
import { infoImageUrl } from '@/instance.js';
@ -162,12 +162,12 @@ const followRequestDone = ref(false);
const acceptFollowRequest = () => {
followRequestDone.value = true;
os.api('following/requests/accept', { userId: props.notification.user.id });
misskeyApi('following/requests/accept', { userId: props.notification.user.id });
};
const rejectFollowRequest = () => {
followRequestDone.value = true;
os.api('following/requests/reject', { userId: props.notification.user.id });
misskeyApi('following/requests/reject', { userId: props.notification.user.id });
};
</script>

View file

@ -23,26 +23,26 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<div ref="contents" :class="$style.root" style="container-type: inline-size;">
<RouterView :key="reloadCount" :router="router"/>
<RouterView :key="reloadCount" :router="windowRouter"/>
</div>
</MkWindow>
</template>
<script lang="ts" setup>
import { ComputedRef, onMounted, onUnmounted, provide, shallowRef, ref, computed } from 'vue';
import { computed, ComputedRef, onMounted, onUnmounted, provide, ref, shallowRef } from 'vue';
import RouterView from '@/components/global/RouterView.vue';
import MkWindow from '@/components/MkWindow.vue';
import { popout as _popout } from '@/scripts/popout.js';
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
import { url } from '@/config.js';
import { mainRouter, routes, page } from '@/router.js';
import { $i } from '@/account.js';
import { Router, useScrollPositionManager } from '@/nirax.js';
import { useScrollPositionManager } from '@/nirax.js';
import { i18n } from '@/i18n.js';
import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata.js';
import { openingWindowsCount } from '@/os.js';
import { claimAchievement } from '@/scripts/achievements.js';
import { getScrollContainer } from '@/scripts/scroll.js';
import { useRouterFactory } from '@/global/router/supplier.js';
import { mainRouter } from '@/global/router/main.js';
const props = defineProps<{
initialPath: string;
@ -52,14 +52,15 @@ defineEmits<{
(ev: 'closed'): void;
}>();
const router = new Router(routes, props.initialPath, !!$i, page(() => import('@/pages/not-found.vue')));
const routerFactory = useRouterFactory();
const windowRouter = routerFactory(props.initialPath);
const contents = shallowRef<HTMLElement>();
const pageMetadata = ref<null | ComputedRef<PageMetadata>>();
const windowEl = shallowRef<InstanceType<typeof MkWindow>>();
const history = ref<{ path: string; key: any; }[]>([{
path: router.getCurrentPath(),
key: router.getCurrentKey(),
path: windowRouter.getCurrentPath(),
key: windowRouter.getCurrentKey(),
}]);
const buttonsLeft = computed(() => {
const buttons = [];
@ -88,11 +89,11 @@ const buttonsRight = computed(() => {
});
const reloadCount = ref(0);
router.addListener('push', ctx => {
windowRouter.addListener('push', ctx => {
history.value.push({ path: ctx.path, key: ctx.key });
});
provide('router', router);
provide('router', windowRouter);
provideMetadataReceiver((info) => {
pageMetadata.value = info;
});
@ -112,20 +113,20 @@ const contextmenu = computed(() => ([{
icon: 'ti ti-external-link',
text: i18n.ts.openInNewTab,
action: () => {
window.open(url + router.getCurrentPath(), '_blank', 'noopener');
window.open(url + windowRouter.getCurrentPath(), '_blank', 'noopener');
windowEl.value.close();
},
}, {
icon: 'ti ti-link',
text: i18n.ts.copyLink,
action: () => {
copyToClipboard(url + router.getCurrentPath());
copyToClipboard(url + windowRouter.getCurrentPath());
},
}]));
function back() {
history.value.pop();
router.replace(history.value.at(-1)!.path, history.value.at(-1)!.key);
windowRouter.replace(history.value.at(-1)!.path, history.value.at(-1)!.key);
}
function reload() {
@ -137,16 +138,16 @@ function close() {
}
function expand() {
mainRouter.push(router.getCurrentPath(), 'forcePage');
mainRouter.push(windowRouter.getCurrentPath(), 'forcePage');
windowEl.value.close();
}
function popout() {
_popout(router.getCurrentPath(), windowEl.value.$el);
_popout(windowRouter.getCurrentPath(), windowEl.value.$el);
windowEl.value.close();
}
useScrollPositionManager(() => getScrollContainer(contents.value), router);
useScrollPositionManager(() => getScrollContainer(contents.value), windowRouter);
onMounted(() => {
openingWindowsCount.value++;

View file

@ -46,6 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import { computed, ComputedRef, isRef, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onDeactivated, ref, shallowRef, watch } from 'vue';
import * as Misskey from 'misskey-js';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { onScrollTop, isTopVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isBottomVisible } from '@/scripts/scroll.js';
import { useDocumentVisibility } from '@/scripts/use-document-visibility.js';
import { defaultStore } from '@/store.js';
@ -203,7 +204,7 @@ async function init(): Promise<void> {
queue.value = new Map();
fetching.value = true;
const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {};
await os.api(props.pagination.endpoint, {
await misskeyApi(props.pagination.endpoint, {
...params,
limit: props.pagination.limit ?? 10,
allowPartial: true,
@ -239,7 +240,7 @@ const fetchMore = async (): Promise<void> => {
if (!more.value || fetching.value || moreFetching.value || items.value.size === 0) return;
moreFetching.value = true;
const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {};
await os.api(props.pagination.endpoint, {
await misskeyApi(props.pagination.endpoint, {
...params,
limit: SECOND_FETCH_LIMIT,
...(props.pagination.offsetMode ? {
@ -303,7 +304,7 @@ const fetchMoreAhead = async (): Promise<void> => {
if (!more.value || fetching.value || moreFetching.value || items.value.size === 0) return;
moreFetching.value = true;
const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {};
await os.api(props.pagination.endpoint, {
await misskeyApi(props.pagination.endpoint, {
...params,
limit: SECOND_FETCH_LIMIT,
...(props.pagination.offsetMode ? {

View file

@ -41,7 +41,9 @@ import MkInput from '@/components/MkInput.vue';
import MkButton from '@/components/MkButton.vue';
import MkModalWindow from '@/components/MkModalWindow.vue';
import { i18n } from '@/i18n.js';
import { $i } from '@/account.js';
import { signinRequired } from '@/account.js';
const $i = signinRequired();
const emit = defineEmits<{
(ev: 'done', v: { password: string; token: string | null; }): void;

View file

@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div :class="$style.root" :style="{ zIndex, top: `${y - 64}px`, left: `${x - 64}px` }">
<span class="text" :class="{ up }">+1</span>
<span class="text" :class="{ up }">+{{ value }}</span>
</div>
</template>
@ -16,7 +16,9 @@ import * as os from '@/os.js';
const props = withDefaults(defineProps<{
x: number;
y: number;
value?: number;
}>(), {
value: 1,
});
const emit = defineEmits<{
@ -40,6 +42,7 @@ onMounted(() => {
<style lang="scss" module>
.root {
user-select: none;
pointer-events: none;
position: fixed;
width: 128px;

View file

@ -32,11 +32,13 @@ import * as Misskey from 'misskey-js';
import { sum } from '@/scripts/array.js';
import { pleaseLogin } from '@/scripts/please-login.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { i18n } from '@/i18n.js';
import { useInterval } from '@/scripts/use-interval.js';
import { WithNonNullable } from '@/type.js';
const props = defineProps<{
note: Misskey.entities.Note;
note: WithNonNullable<Misskey.entities.Note, 'poll'>;
readOnly?: boolean;
}>();
@ -83,7 +85,7 @@ const vote = async (id) => {
});
if (canceled) return;
await os.api('notes/polls/vote', {
await misskeyApi('notes/polls/vote', {
noteId: props.note.id,
choice: id,
});

View file

@ -115,12 +115,13 @@ import { extractMentions } from '@/scripts/extract-mentions.js';
import { formatTimeString } from '@/scripts/format-time-string.js';
import { Autocomplete } from '@/scripts/autocomplete.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { selectFiles } from '@/scripts/select-file.js';
import { defaultStore, notePostInterruptors, postFormActions } from '@/store.js';
import MkInfo from '@/components/MkInfo.vue';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
import { $i, notesCount, incNotesCount, getAccounts, openAccountMenu as openAccountMenu_ } from '@/account.js';
import { signinRequired, notesCount, incNotesCount, getAccounts, openAccountMenu as openAccountMenu_ } from '@/account.js';
import { uploadFile } from '@/scripts/upload.js';
import { deepClone } from '@/scripts/clone.js';
import MkRippleEffect from '@/components/MkRippleEffect.vue';
@ -129,6 +130,8 @@ import { claimAchievement } from '@/scripts/achievements.js';
import { emojiPicker } from '@/scripts/emoji-picker.js';
import { mfmFunctionPicker } from '@/scripts/mfm-function-picker.js';
const $i = signinRequired();
const modal = inject('modal');
const props = withDefaults(defineProps<{
@ -307,7 +310,7 @@ if (props.reply && props.reply.text != null) {
}
}
if ($i?.isSilenced && visibility.value === 'public') {
if ($i.isSilenced && visibility.value === 'public') {
visibility.value = 'home';
}
@ -328,7 +331,7 @@ if (props.reply && ['home', 'followers', 'specified'].includes(props.reply.visib
if (visibility.value === 'specified') {
if (props.reply.visibleUserIds) {
os.api('users/show', {
misskeyApi('users/show', {
userIds: props.reply.visibleUserIds.filter(uid => uid !== $i.id && uid !== props.reply.userId),
}).then(users => {
users.forEach(pushVisibleUser);
@ -336,7 +339,7 @@ if (props.reply && ['home', 'followers', 'specified'].includes(props.reply.visib
}
if (props.reply.userId !== $i.id) {
os.api('users/show', { userId: props.reply.userId }).then(user => {
misskeyApi('users/show', { userId: props.reply.userId }).then(user => {
pushVisibleUser(user);
});
}
@ -383,7 +386,7 @@ function addMissingMention() {
for (const x of extractMentions(ast)) {
if (!visibleUsers.value.some(u => (u.username === x.username) && (u.host === x.host))) {
os.api('users/show', { username: x.username, host: x.host }).then(user => {
misskeyApi('users/show', { username: x.username, host: x.host }).then(user => {
visibleUsers.value.push(user);
});
}
@ -460,7 +463,7 @@ function setVisibility() {
os.popup(defineAsyncComponent(() => import('@/components/MkVisibilityPicker.vue')), {
currentVisibility: visibility.value,
isSilenced: $i?.isSilenced,
isSilenced: $i.isSilenced,
localOnly: localOnly.value,
src: visibilityButton.value,
}, {
@ -784,7 +787,7 @@ async function post(ev?: MouseEvent) {
}
posting.value = true;
os.api('notes/create', postData, token).then(() => {
misskeyApi('notes/create', postData, token).then(() => {
if (props.freezeAfterPosted) {
posted.value = true;
} else {

View file

@ -24,6 +24,7 @@ import { defineAsyncComponent, inject } from 'vue';
import * as Misskey from 'misskey-js';
import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { i18n } from '@/i18n.js';
const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
@ -61,7 +62,7 @@ function toggleSensitive(file) {
return;
}
os.api('drive/files/update', {
misskeyApi('drive/files/update', {
fileId: file.id,
isSensitive: !file.isSensitive,
}).then(() => {
@ -78,7 +79,7 @@ async function rename(file) {
allowEmpty: false,
});
if (canceled) return;
os.api('drive/files/update', {
misskeyApi('drive/files/update', {
fileId: file.id,
name: result,
}).then(() => {
@ -96,7 +97,7 @@ async function describe(file) {
}, {
done: caption => {
let comment = caption.length === 0 ? null : caption;
os.api('drive/files/update', {
misskeyApi('drive/files/update', {
fileId: file.id,
comment: comment,
}).then(() => {

View file

@ -45,7 +45,8 @@ import { ref } from 'vue';
import { $i, getAccounts } from '@/account.js';
import MkButton from '@/components/MkButton.vue';
import { instance } from '@/instance.js';
import { api, apiWithDialog, promiseDialog } from '@/os.js';
import { apiWithDialog, promiseDialog } from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { i18n } from '@/i18n.js';
defineProps<{
@ -82,7 +83,7 @@ function subscribe() {
pushSubscription.value = subscription;
// Register
pushRegistrationInServer.value = await api('sw/register', {
pushRegistrationInServer.value = await misskeyApi('sw/register', {
endpoint: subscription.endpoint,
auth: encode(subscription.getKey('auth')),
publickey: encode(subscription.getKey('p256dh')),
@ -159,7 +160,7 @@ if (navigator.serviceWorker == null) {
supported.value = true;
if (pushSubscription.value) {
const res = await api('sw/show-registration', {
const res = await misskeyApi('sw/show-registration', {
endpoint: pushSubscription.value.endpoint,
});

View file

@ -22,6 +22,7 @@ import * as Misskey from 'misskey-js';
import XDetails from '@/components/MkReactionsViewer.details.vue';
import MkReactionIcon from '@/components/MkReactionIcon.vue';
import * as os from '@/os.js';
import { misskeyApi, misskeyApiGet } from '@/scripts/misskey-api.js';
import { useTooltip } from '@/scripts/use-tooltip.js';
import { $i } from '@/account.js';
import MkReactionEffect from '@/components/MkReactionEffect.vue';
@ -69,11 +70,11 @@ async function toggleReaction() {
return;
}
os.api('notes/reactions/delete', {
misskeyApi('notes/reactions/delete', {
noteId: props.note.id,
}).then(() => {
if (oldReaction !== props.reaction) {
os.api('notes/reactions/create', {
misskeyApi('notes/reactions/create', {
noteId: props.note.id,
reaction: props.reaction,
});
@ -87,7 +88,7 @@ async function toggleReaction() {
return;
}
os.api('notes/reactions/create', {
misskeyApi('notes/reactions/create', {
noteId: props.note.id,
reaction: props.reaction,
});
@ -117,7 +118,7 @@ onMounted(() => {
if (!mock) {
useTooltip(buttonEl, async (showing) => {
const reactions = await os.apiGet('notes/reactions', {
const reactions = await misskeyApiGet('notes/reactions', {
noteId: props.note.id,
type: props.reaction,
limit: 10,

View file

@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onMounted, nextTick, shallowRef, ref } from 'vue';
import { Chart } from 'chart.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { defaultStore } from '@/store.js';
import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
import { alpha } from '@/scripts/color.js';
@ -43,7 +43,7 @@ async function renderChart() {
const maxDays = wide ? 10 : narrow ? 5 : 7;
let raw = await os.api('retention', { });
let raw = await misskeyApi('retention', { });
raw = raw.slice(0, maxDays + 1);

View file

@ -16,7 +16,7 @@ import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
import { chartVLine } from '@/scripts/chart-vline.js';
import { alpha } from '@/scripts/color.js';
import { initChart } from '@/scripts/init-chart.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
initChart();
@ -40,7 +40,7 @@ const getDate = (ymd: string) => {
};
onMounted(async () => {
let raw = await os.api('retention', { });
let raw = await misskeyApi('retention', { });
const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';

View file

@ -77,7 +77,14 @@ const emit = defineEmits<{
(ev: 'end'): void;
}>();
const particles = [];
const particles: {
size: number;
xA: number;
yA: number;
xB: number;
yB: number;
color: string;
}[] = [];
const origin = 64;
const colors = ['#FF1493', '#00FFFF', '#FFE202'];
const zIndex = os.claimZIndex('high');

View file

@ -59,6 +59,7 @@ import MkInput from '@/components/MkInput.vue';
import MkInfo from '@/components/MkInfo.vue';
import { host as configHost } from '@/config.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { login } from '@/account.js';
import { i18n } from '@/i18n.js';
@ -95,7 +96,7 @@ const props = defineProps({
});
function onUsernameChange(): void {
os.api('users/show', {
misskeyApi('users/show', {
username: username.value,
}).then(userResponse => {
user.value = userResponse;
@ -120,7 +121,7 @@ async function queryKey(): Promise<void> {
credentialRequest.value = null;
queryingKey.value = false;
signing.value = true;
return os.api('signin', {
return misskeyApi('signin', {
username: username.value,
password: password.value,
credential: credential.toJSON(),
@ -142,7 +143,7 @@ function onSubmit(): void {
signing.value = true;
if (!totpLogin.value && user.value && user.value.twoFactorEnabled) {
if (webAuthnSupported() && user.value.securityKeys) {
os.api('signin', {
misskeyApi('signin', {
username: username.value,
password: password.value,
}).then(res => {
@ -159,7 +160,7 @@ function onSubmit(): void {
signing.value = false;
}
} else {
os.api('signin', {
misskeyApi('signin', {
username: username.value,
password: password.value,
token: user.value?.twoFactorEnabled ? token.value : undefined,

View file

@ -63,6 +63,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
</MkInput>
<MkCaptcha v-if="instance.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" :class="$style.captcha" provider="hcaptcha" :sitekey="instance.hcaptchaSiteKey"/>
<MkCaptcha v-if="instance.enableMcaptcha" ref="mcaptcha" v-model="mCaptchaResponse" :class="$style.captcha" provider="mcaptcha" :sitekey="instance.mcaptchaSiteKey" :instanceUrl="instance.mcaptchaInstanceUrl"/>
<MkCaptcha v-if="instance.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" :class="$style.captcha" provider="recaptcha" :sitekey="instance.recaptchaSiteKey"/>
<MkCaptcha v-if="instance.enableTurnstile" ref="turnstile" v-model="turnstileResponse" :class="$style.captcha" provider="turnstile" :sitekey="instance.turnstileSiteKey"/>
<MkButton type="submit" :disabled="shouldDisableSubmitting" large gradate rounded data-cy-signup-submit style="margin: 0 auto;">
@ -84,6 +85,7 @@ import MkInput from './MkInput.vue';
import MkCaptcha, { type Captcha } from '@/components/MkCaptcha.vue';
import * as config from '@/config.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { login } from '@/account.js';
import { instance } from '@/instance.js';
import { i18n } from '@/i18n.js';
@ -116,6 +118,7 @@ const passwordStrength = ref<'' | 'low' | 'medium' | 'high'>('');
const passwordRetypeState = ref<null | 'match' | 'not-match'>(null);
const submitting = ref<boolean>(false);
const hCaptchaResponse = ref<string | null>(null);
const mCaptchaResponse = ref<string | null>(null);
const reCaptchaResponse = ref<string | null>(null);
const turnstileResponse = ref<string | null>(null);
const usernameAbortController = ref<null | AbortController>(null);
@ -124,6 +127,7 @@ const emailAbortController = ref<null | AbortController>(null);
const shouldDisableSubmitting = computed((): boolean => {
return submitting.value ||
instance.enableHcaptcha && !hCaptchaResponse.value ||
instance.enableMcaptcha && !mCaptchaResponse.value ||
instance.enableRecaptcha && !reCaptchaResponse.value ||
instance.enableTurnstile && !turnstileResponse.value ||
instance.emailRequiredForSignup && emailState.value !== 'ok' ||
@ -180,7 +184,7 @@ function onChangeUsername(): void {
usernameState.value = 'wait';
usernameAbortController.value = new AbortController();
os.api('username/available', {
misskeyApi('username/available', {
username: username.value,
}, undefined, usernameAbortController.value.signal).then(result => {
usernameState.value = result.available ? 'ok' : 'unavailable';
@ -203,7 +207,7 @@ function onChangeEmail(): void {
emailState.value = 'wait';
emailAbortController.value = new AbortController();
os.api('email-address/available', {
misskeyApi('email-address/available', {
emailAddress: email.value,
}, undefined, emailAbortController.value.signal).then(result => {
emailState.value = result.available ? 'ok' :
@ -245,12 +249,13 @@ async function onSubmit(): Promise<void> {
submitting.value = true;
try {
await os.api('signup', {
await misskeyApi('signup', {
username: username.value,
password: password.value,
emailAddress: email.value,
invitationCode: invitationCode.value,
'hcaptcha-response': hCaptchaResponse.value,
'm-captcha-response': mCaptchaResponse.value,
'g-recaptcha-response': reCaptchaResponse.value,
'turnstile-response': turnstileResponse.value,
});
@ -262,7 +267,7 @@ async function onSubmit(): Promise<void> {
});
emit('signupEmailPending');
} else {
const res = await os.api('signin', {
const res = await misskeyApi('signin', {
username: username.value,
password: password.value,
});

View file

@ -11,13 +11,13 @@ SPDX-License-Identifier: AGPL-3.0-only
:pagination="paginationQuery"
:noGap="!defaultStore.state.showGapBetweenNotesInTimeline"
@queue="emit('queue', $event)"
@status="prComponent.setDisabled($event)"
@status="prComponent?.setDisabled($event)"
/>
</MkPullToRefresh>
</template>
<script lang="ts" setup>
import { computed, watch, onUnmounted, provide, ref } from 'vue';
import { computed, watch, onUnmounted, provide, ref, shallowRef } from 'vue';
import { Connection } from 'misskey-js/built/streaming.js';
import MkNotes from '@/components/MkNotes.vue';
import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
@ -62,12 +62,14 @@ type TimelineQueryType = {
roleId?: string
}
const prComponent = ref<InstanceType<typeof MkPullToRefresh>>();
const tlComponent = ref<InstanceType<typeof MkNotes>>();
const prComponent = shallowRef<InstanceType<typeof MkPullToRefresh>>();
const tlComponent = shallowRef<InstanceType<typeof MkNotes>>();
let tlNotesCount = 0;
const prepend = note => {
function prepend(note) {
if (tlComponent.value == null) return;
tlNotesCount++;
if (instance.notesPerOneAd > 0 && tlNotesCount % instance.notesPerOneAd === 0) {
@ -81,7 +83,7 @@ const prepend = note => {
if (props.sound) {
sound.play($i && (note.userId === $i.id) ? 'noteMy' : 'note');
}
};
}
let connection: Connection;
let connection2: Connection;
@ -130,6 +132,7 @@ function connectChannel() {
connection.on('mention', onNote);
} else if (props.src === 'list') {
connection = stream.useChannel('userList', {
withRenotes: props.withRenotes,
withFiles: props.onlyFiles ? true : undefined,
listId: props.list,
});
@ -196,6 +199,7 @@ function updatePaginationQuery() {
} else if (props.src === 'list') {
endpoint = 'notes/user-list-timeline';
query = {
withRenotes: props.withRenotes,
withFiles: props.onlyFiles ? true : undefined,
listId: props.list,
};
@ -234,8 +238,9 @@ function refreshEndpointAndChannel() {
updatePaginationQuery();
}
// withRenotes
// IDTL
watch(() => [props.list, props.antenna, props.channel, props.role], refreshEndpointAndChannel);
watch(() => [props.list, props.antenna, props.channel, props.role, props.withRenotes], refreshEndpointAndChannel);
//
refreshEndpointAndChannel();
@ -246,6 +251,8 @@ onUnmounted(() => {
function reloadTimeline() {
return new Promise<void>((res) => {
if (tlComponent.value == null) return;
tlNotesCount = 0;
tlComponent.value.pagingComponent?.reload().then(() => {

View file

@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:withOkButton="true"
:okButtonDisabled="false"
:canClose="false"
@close="dialog.close()"
@close="dialog?.close()"
@closed="$emit('closed')"
@ok="ok()"
>
@ -87,7 +87,7 @@ function ok(): void {
name: name.value,
permissions: Object.keys(permissions.value).filter(p => permissions.value[p]),
});
dialog.value.close();
dialog.value?.close();
}
function disableAll(): void {

View file

@ -13,8 +13,10 @@ SPDX-License-Identifier: AGPL-3.0-only
>
<div v-show="showing" ref="el" :class="$style.root" class="_acrylic _shadow" :style="{ zIndex, maxWidth: maxWidth + 'px' }">
<slot>
<Mfm v-if="asMfm" :text="text"/>
<span v-else>{{ text }}</span>
<template v-if="text">
<Mfm v-if="asMfm" :text="text"/>
<span v-else>{{ text }}</span>
</template>
</slot>
</div>
</Transition>
@ -53,6 +55,7 @@ const el = shallowRef<HTMLElement>();
const zIndex = os.claimZIndex('high');
function setPosition() {
if (!el.value || !props.targetElement) return;
const data = calcPopupPosition(el.value, {
anchorElement: props.targetElement,
direction: props.direction,

View file

@ -4,12 +4,12 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<MkModal ref="modal" :zPriority="'middle'" @click="$refs.modal.close()" @closed="$emit('closed')">
<MkModal ref="modal" :zPriority="'middle'" @click="modal?.close()" @closed="$emit('closed')">
<div :class="$style.root">
<div :class="$style.title"><MkSparkle>{{ i18n.ts.misskeyUpdated }}</MkSparkle></div>
<div :class="$style.version">{{ version }}🚀</div>
<MkButton full @click="whatIsNew">{{ i18n.ts.whatIsNew }}</MkButton>
<MkButton :class="$style.gotIt" primary full @click="$refs.modal.close()">{{ i18n.ts.gotIt }}</MkButton>
<MkButton :class="$style.gotIt" primary full @click="modal?.close()">{{ i18n.ts.gotIt }}</MkButton>
</div>
</MkModal>
</template>
@ -25,10 +25,10 @@ import { confetti } from '@/scripts/confetti.js';
const modal = shallowRef<InstanceType<typeof MkModal>>();
const whatIsNew = () => {
modal.value.close();
function whatIsNew() {
modal.value?.close();
window.open(`https://misskey-hub.net/docs/releases/#_${version.replace(/\./g, '')}`, '_blank');
};
}
onMounted(() => {
confetti({

View file

@ -56,6 +56,7 @@ import MkModalWindow from '@/components/MkModalWindow.vue';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { i18n } from '@/i18n.js';
import MkTextarea from '@/components/MkTextarea.vue';
import MkSwitch from '@/components/MkSwitch.vue';
@ -121,7 +122,7 @@ async function del() {
});
if (canceled) return;
os.api('admin/announcements/delete', {
misskeyApi('admin/announcements/delete', {
id: props.announcement.id,
}).then(() => {
emit('done', {

View file

@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import * as Misskey from 'misskey-js';
import { onMounted, ref } from 'vue';
import MkMiniChart from '@/components/MkMiniChart.vue';
import * as os from '@/os.js';
import { misskeyApiGet } from '@/scripts/misskey-api.js';
import { acct } from '@/filters/user.js';
const props = withDefaults(defineProps<{
@ -32,7 +32,7 @@ const chartValues = ref<number[] | null>(null);
onMounted(() => {
if (props.withChart) {
os.apiGet('charts/user/notes', { userId: props.user.id, limit: 16 + 1, span: 'day' }).then(res => {
misskeyApiGet('charts/user/notes', { userId: props.user.id, limit: 16 + 1, span: 'day' }).then(res => {
//
res.inc.splice(0, 1);
chartValues.value = res.inc;

View file

@ -60,6 +60,7 @@ import * as Misskey from 'misskey-js';
import MkFollowButton from '@/components/MkFollowButton.vue';
import { userPage } from '@/filters/user.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { getUserMenu } from '@/scripts/get-user-menu.js';
import number from '@/filters/number.js';
import { i18n } from '@/i18n.js';
@ -97,7 +98,7 @@ onMounted(() => {
Misskey.acct.parse(props.q.substring(1)) :
{ userId: props.q };
os.api('users/show', query).then(res => {
misskeyApi('users/show', query).then(res => {
if (!props.showing) return;
user.value = res;
});

View file

@ -62,7 +62,7 @@ import * as Misskey from 'misskey-js';
import MkInput from '@/components/MkInput.vue';
import FormSplit from '@/components/form/split.vue';
import MkModalWindow from '@/components/MkModalWindow.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';
import { $i } from '@/account.js';
@ -90,7 +90,7 @@ const search = () => {
users.value = [];
return;
}
os.api('users/search-by-username-and-host', {
misskeyApi('users/search-by-username-and-host', {
username: username.value,
host: host.value,
limit: 10,
@ -118,7 +118,7 @@ const cancel = () => {
};
onMounted(() => {
os.api('users/show', {
misskeyApi('users/show', {
userIds: defaultStore.state.recentlyUsedUsers,
}).then(users => {
if (props.includeSelf && users.find(x => $i ? x.id === $i.id : true) == null) {

View file

@ -49,7 +49,7 @@ import { i18n } from '@/i18n.js';
import MkSwitch from '@/components/MkSwitch.vue';
import MkInfo from '@/components/MkInfo.vue';
import MkFolder from '@/components/MkFolder.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
const isLocked = ref(false);
const hideOnlineStatus = ref(false);
@ -57,7 +57,7 @@ const noCrawle = ref(false);
const preventAiLearning = ref(true);
watch([isLocked, hideOnlineStatus, noCrawle, preventAiLearning], () => {
os.api('i/update', {
misskeyApi('i/update', {
isLocked: !!isLocked.value,
hideOnlineStatus: !!hideOnlineStatus.value,
noCrawle: !!noCrawle.value,

View file

@ -29,7 +29,7 @@ import * as Misskey from 'misskey-js';
import { ref } from 'vue';
import MkButton from '@/components/MkButton.vue';
import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
const props = defineProps<{
user: Misskey.entities.UserDetailed;
@ -39,7 +39,7 @@ const isFollowing = ref(false);
async function follow() {
isFollowing.value = true;
os.api('following/create', {
misskeyApi('following/create', {
userId: props.user.id,
});
}

View file

@ -17,7 +17,7 @@ import { onMounted, shallowRef, ref } from 'vue';
import { Chart } from 'chart.js';
import gradient from 'chartjs-plugin-gradient';
import tinycolor from 'tinycolor2';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { defaultStore } from '@/store.js';
import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
import { chartVLine } from '@/scripts/chart-vline.js';
@ -53,7 +53,7 @@ async function renderChart() {
}));
};
const raw = await os.api('charts/active-users', { limit: chartLimit, span: 'day' });
const raw = await misskeyApi('charts/active-users', { limit: chartLimit, span: 'day' });
const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';

View file

@ -60,6 +60,7 @@ import MkTimeline from '@/components/MkTimeline.vue';
import MkInfo from '@/components/MkInfo.vue';
import { instanceName } from '@/config.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
import MkNumber from '@/components/MkNumber.vue';
@ -68,11 +69,11 @@ import XActiveUsersChart from '@/components/MkVisitorDashboard.ActiveUsersChart.
const meta = ref<Misskey.entities.MetaResponse | null>(null);
const stats = ref<Misskey.entities.StatsResponse | null>(null);
os.api('meta', { detail: true }).then(_meta => {
misskeyApi('meta', { detail: true }).then(_meta => {
meta.value = _meta;
});
os.api('stats', {}).then((res) => {
misskeyApi('stats', {}).then((res) => {
stats.value = res;
});

View file

@ -143,6 +143,7 @@ function top() {
}
function maximize() {
if (rootEl.value == null) return;
maximized.value = true;
unResizedTop = rootEl.value.style.top;
unResizedLeft = rootEl.value.style.left;
@ -155,6 +156,7 @@ function maximize() {
}
function unMaximize() {
if (rootEl.value == null) return;
maximized.value = false;
rootEl.value.style.top = unResizedTop;
rootEl.value.style.left = unResizedLeft;
@ -163,6 +165,7 @@ function unMaximize() {
}
function minimize() {
if (rootEl.value == null) return;
minimized.value = true;
unResizedWidth = rootEl.value.style.width;
unResizedHeight = rootEl.value.style.height;
@ -171,8 +174,8 @@ function minimize() {
}
function unMinimize() {
if (rootEl.value == null) return;
const main = rootEl.value;
if (main == null) return;
minimized.value = false;
rootEl.value.style.width = unResizedWidth;

View file

@ -15,7 +15,7 @@ import * as os from '@/os.js';
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
import { url } from '@/config.js';
import { i18n } from '@/i18n.js';
import { useRouter } from '@/router.js';
import { useRouter } from '@/global/router/supplier.js';
const props = withDefaults(defineProps<{
to: string;

View file

@ -5,15 +5,14 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<img v-if="!useOsNativeEmojis" :class="$style.root" :src="url" :alt="props.emoji" decoding="async" @pointerenter="computeTitle" @click="onClick"/>
<span v-else-if="useOsNativeEmojis" :alt="props.emoji" @pointerenter="computeTitle" @click="onClick">{{ props.emoji }}</span>
<span v-else>{{ emoji }}</span>
<span v-else :alt="props.emoji" @pointerenter="computeTitle" @click="onClick">{{ colorizedNativeEmoji }}</span>
</template>
<script lang="ts" setup>
import { computed, inject } from 'vue';
import { char2twemojiFilePath, char2fluentEmojiFilePath } from '@/scripts/emoji-base.js';
import { defaultStore } from '@/store.js';
import { getEmojiName } from '@/scripts/emojilist.js';
import { colorizeEmoji, getEmojiName } from '@/scripts/emojilist.js';
import * as os from '@/os.js';
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
import * as sound from '@/scripts/sound.js';
@ -30,9 +29,8 @@ const react = inject<((name: string) => void) | null>('react', null);
const char2path = defaultStore.state.emojiStyle === 'twemoji' ? char2twemojiFilePath : char2fluentEmojiFilePath;
const useOsNativeEmojis = computed(() => defaultStore.state.emojiStyle === 'native');
const url = computed(() => {
return char2path(props.emoji);
});
const url = computed(() => char2path(props.emoji));
const colorizedNativeEmoji = computed(() => colorizeEmoji(props.emoji));
// Searching from an array with 2000 items for every emoji felt like too energy-consuming, so I decided to do it lazily on pointerenter
function computeTitle(event: PointerEvent): void {

View file

@ -16,12 +16,12 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { inject, onBeforeUnmount, provide, shallowRef, ref } from 'vue';
import { Resolved, Router } from '@/nirax.js';
import { inject, onBeforeUnmount, provide, ref, shallowRef } from 'vue';
import { IRouter, Resolved } from '@/nirax.js';
import { defaultStore } from '@/store.js';
const props = defineProps<{
router?: Router;
router?: IRouter;
}>();
const router = props.router ?? inject('router');

View file

@ -16,7 +16,7 @@ import * as Misskey from 'misskey-js';
import { NoteBlock } from './block.type.js';
import MkNote from '@/components/MkNote.vue';
import MkNoteDetailed from '@/components/MkNoteDetailed.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
const props = defineProps<{
block: NoteBlock,
@ -26,7 +26,7 @@ const props = defineProps<{
const note = ref<Misskey.entities.Note | null>(null);
onMounted(() => {
os.api('notes/show', { noteId: props.block.note })
misskeyApi('notes/show', { noteId: props.block.note })
.then(result => {
note.value = result;
});

View file

@ -5,7 +5,7 @@
import { shallowRef, computed, markRaw, watch } from 'vue';
import * as Misskey from 'misskey-js';
import { api, apiGet } from '@/os.js';
import { misskeyApi, misskeyApiGet } from '@/scripts/misskey-api.js';
import { useStream } from '@/stream.js';
import { get, set } from '@/scripts/idb-proxy.js';
@ -52,11 +52,11 @@ export async function fetchCustomEmojis(force = false) {
let res;
if (force) {
res = await api('emojis', {});
res = await misskeyApi('emojis', {});
} else {
const lastFetchedAt = await get('lastEmojisFetchedAt');
if (lastFetchedAt && (now - lastFetchedAt) < 1000 * 60 * 60) return;
res = await apiGet('emojis', {});
res = await misskeyApiGet('emojis', {});
}
customEmojis.value = res.emojis;

View file

@ -5,10 +5,10 @@
export default (v, digits = 0) => {
if (v == null) return '?';
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB', 'RB', 'QB'];
if (v === 0) return '0';
const isMinus = v < 0;
if (isMinus) v = -v;
const i = Math.floor(Math.log(v) / Math.log(1024));
return (isMinus ? '-' : '') + (v / Math.pow(1024, i)).toFixed(digits).replace(/\.0+$/, '') + sizes[i];
return (isMinus ? '-' : '') + (v / Math.pow(1024, i)).toFixed(digits).replace(/(\.[1-9]*)0+$/, '$1').replace(/\.$/, '') + (sizes[i] ?? `e+${ i * 3 }B`);
};

View file

@ -0,0 +1,9 @@
export default (v, fractionDigits = 0) => {
if (v == null) return 'N/A';
if (v === 0) return '0';
const sizes = ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y', 'R', 'Q'];
const isMinus = v < 0;
if (isMinus) v = -v;
const i = Math.floor(Math.log(v) / Math.log(1000));
return (isMinus ? '-' : '') + (v / Math.pow(1000, i)).toFixed(fractionDigits).replace(/(\.[1-9]*)0+$/, '$1').replace(/\.$/, '') + (sizes[i] ?? `e+${ i * 3 }`);
};

View file

@ -0,0 +1,571 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { App, AsyncComponentLoader, defineAsyncComponent, provide } from 'vue';
import { IRouter, Router } from '@/nirax.js';
import { $i, iAmModerator } from '@/account.js';
import MkLoading from '@/pages/_loading_.vue';
import MkError from '@/pages/_error_.vue';
import { setMainRouter } from '@/global/router/main.js';
const page = (loader: AsyncComponentLoader<any>) => defineAsyncComponent({
loader: loader,
loadingComponent: MkLoading,
errorComponent: MkError,
});
const routes = [{
path: '/@:initUser/pages/:initPageName/view-source',
component: page(() => import('@/pages/page-editor/page-editor.vue')),
}, {
path: '/@:username/pages/:pageName',
component: page(() => import('@/pages/page.vue')),
}, {
path: '/@:acct/following',
component: page(() => import('@/pages/user/following.vue')),
}, {
path: '/@:acct/followers',
component: page(() => import('@/pages/user/followers.vue')),
}, {
name: 'user',
path: '/@:acct/:page?',
component: page(() => import('@/pages/user/index.vue')),
}, {
name: 'note',
path: '/notes/:noteId',
component: page(() => import('@/pages/note.vue')),
}, {
name: 'list',
path: '/list/:listId',
component: page(() => import('@/pages/list.vue')),
}, {
path: '/clips/:clipId',
component: page(() => import('@/pages/clip.vue')),
}, {
path: '/instance-info/:host',
component: page(() => import('@/pages/instance-info.vue')),
}, {
name: 'settings',
path: '/settings',
component: page(() => import('@/pages/settings/index.vue')),
loginRequired: true,
children: [{
path: '/profile',
name: 'profile',
component: page(() => import('@/pages/settings/profile.vue')),
}, {
path: '/avatar-decoration',
name: 'avatarDecoration',
component: page(() => import('@/pages/settings/avatar-decoration.vue')),
}, {
path: '/roles',
name: 'roles',
component: page(() => import('@/pages/settings/roles.vue')),
}, {
path: '/privacy',
name: 'privacy',
component: page(() => import('@/pages/settings/privacy.vue')),
}, {
path: '/emoji-picker',
name: 'emojiPicker',
component: page(() => import('@/pages/settings/emoji-picker.vue')),
}, {
path: '/drive',
name: 'drive',
component: page(() => import('@/pages/settings/drive.vue')),
}, {
path: '/drive/cleaner',
name: 'drive',
component: page(() => import('@/pages/settings/drive-cleaner.vue')),
}, {
path: '/notifications',
name: 'notifications',
component: page(() => import('@/pages/settings/notifications.vue')),
}, {
path: '/email',
name: 'email',
component: page(() => import('@/pages/settings/email.vue')),
}, {
path: '/security',
name: 'security',
component: page(() => import('@/pages/settings/security.vue')),
}, {
path: '/general',
name: 'general',
component: page(() => import('@/pages/settings/general.vue')),
}, {
path: '/theme/install',
name: 'theme',
component: page(() => import('@/pages/settings/theme.install.vue')),
}, {
path: '/theme/manage',
name: 'theme',
component: page(() => import('@/pages/settings/theme.manage.vue')),
}, {
path: '/theme',
name: 'theme',
component: page(() => import('@/pages/settings/theme.vue')),
}, {
path: '/navbar',
name: 'navbar',
component: page(() => import('@/pages/settings/navbar.vue')),
}, {
path: '/statusbar',
name: 'statusbar',
component: page(() => import('@/pages/settings/statusbar.vue')),
}, {
path: '/sounds',
name: 'sounds',
component: page(() => import('@/pages/settings/sounds.vue')),
}, {
path: '/plugin/install',
name: 'plugin',
component: page(() => import('@/pages/settings/plugin.install.vue')),
}, {
path: '/plugin',
name: 'plugin',
component: page(() => import('@/pages/settings/plugin.vue')),
}, {
path: '/import-export',
name: 'import-export',
component: page(() => import('@/pages/settings/import-export.vue')),
}, {
path: '/mute-block',
name: 'mute-block',
component: page(() => import('@/pages/settings/mute-block.vue')),
}, {
path: '/api',
name: 'api',
component: page(() => import('@/pages/settings/api.vue')),
}, {
path: '/apps',
name: 'api',
component: page(() => import('@/pages/settings/apps.vue')),
}, {
path: '/webhook/edit/:webhookId',
name: 'webhook',
component: page(() => import('@/pages/settings/webhook.edit.vue')),
}, {
path: '/webhook/new',
name: 'webhook',
component: page(() => import('@/pages/settings/webhook.new.vue')),
}, {
path: '/webhook',
name: 'webhook',
component: page(() => import('@/pages/settings/webhook.vue')),
}, {
path: '/deck',
name: 'deck',
component: page(() => import('@/pages/settings/deck.vue')),
}, {
path: '/preferences-backups',
name: 'preferences-backups',
component: page(() => import('@/pages/settings/preferences-backups.vue')),
}, {
path: '/migration',
name: 'migration',
component: page(() => import('@/pages/settings/migration.vue')),
}, {
path: '/custom-css',
name: 'general',
component: page(() => import('@/pages/settings/custom-css.vue')),
}, {
path: '/accounts',
name: 'profile',
component: page(() => import('@/pages/settings/accounts.vue')),
}, {
path: '/other',
name: 'other',
component: page(() => import('@/pages/settings/other.vue')),
}, {
path: '/',
component: page(() => import('@/pages/_empty_.vue')),
}],
}, {
path: '/reset-password/:token?',
component: page(() => import('@/pages/reset-password.vue')),
}, {
path: '/signup-complete/:code',
component: page(() => import('@/pages/signup-complete.vue')),
}, {
path: '/announcements',
component: page(() => import('@/pages/announcements.vue')),
}, {
path: '/about',
component: page(() => import('@/pages/about.vue')),
hash: 'initialTab',
}, {
path: '/about-misskey',
component: page(() => import('@/pages/about-misskey.vue')),
}, {
path: '/invite',
name: 'invite',
component: page(() => import('@/pages/invite.vue')),
}, {
path: '/ads',
component: page(() => import('@/pages/ads.vue')),
}, {
path: '/theme-editor',
component: page(() => import('@/pages/theme-editor.vue')),
loginRequired: true,
}, {
path: '/roles/:role',
component: page(() => import('@/pages/role.vue')),
}, {
path: '/user-tags/:tag',
component: page(() => import('@/pages/user-tag.vue')),
}, {
path: '/explore',
component: page(() => import('@/pages/explore.vue')),
hash: 'initialTab',
}, {
path: '/search',
component: page(() => import('@/pages/search.vue')),
query: {
q: 'query',
channel: 'channel',
type: 'type',
origin: 'origin',
},
}, {
path: '/authorize-follow',
component: page(() => import('@/pages/follow.vue')),
loginRequired: true,
}, {
path: '/share',
component: page(() => import('@/pages/share.vue')),
loginRequired: true,
}, {
path: '/api-console',
component: page(() => import('@/pages/api-console.vue')),
loginRequired: true,
}, {
path: '/scratchpad',
component: page(() => import('@/pages/scratchpad.vue')),
}, {
path: '/auth/:token',
component: page(() => import('@/pages/auth.vue')),
}, {
path: '/miauth/:session',
component: page(() => import('@/pages/miauth.vue')),
query: {
callback: 'callback',
name: 'name',
icon: 'icon',
permission: 'permission',
},
}, {
path: '/oauth/authorize',
component: page(() => import('@/pages/oauth.vue')),
}, {
path: '/tags/:tag',
component: page(() => import('@/pages/tag.vue')),
}, {
path: '/pages/new',
component: page(() => import('@/pages/page-editor/page-editor.vue')),
loginRequired: true,
}, {
path: '/pages/edit/:initPageId',
component: page(() => import('@/pages/page-editor/page-editor.vue')),
loginRequired: true,
}, {
path: '/pages',
component: page(() => import('@/pages/pages.vue')),
}, {
path: '/play/:id/edit',
component: page(() => import('@/pages/flash/flash-edit.vue')),
loginRequired: true,
}, {
path: '/play/new',
component: page(() => import('@/pages/flash/flash-edit.vue')),
loginRequired: true,
}, {
path: '/play/:id',
component: page(() => import('@/pages/flash/flash.vue')),
}, {
path: '/play',
component: page(() => import('@/pages/flash/flash-index.vue')),
}, {
path: '/gallery/:postId/edit',
component: page(() => import('@/pages/gallery/edit.vue')),
loginRequired: true,
}, {
path: '/gallery/new',
component: page(() => import('@/pages/gallery/edit.vue')),
loginRequired: true,
}, {
path: '/gallery/:postId',
component: page(() => import('@/pages/gallery/post.vue')),
}, {
path: '/gallery',
component: page(() => import('@/pages/gallery/index.vue')),
}, {
path: '/channels/:channelId/edit',
component: page(() => import('@/pages/channel-editor.vue')),
loginRequired: true,
}, {
path: '/channels/new',
component: page(() => import('@/pages/channel-editor.vue')),
loginRequired: true,
}, {
path: '/channels/:channelId',
component: page(() => import('@/pages/channel.vue')),
}, {
path: '/channels',
component: page(() => import('@/pages/channels.vue')),
}, {
path: '/custom-emojis-manager',
component: page(() => import('@/pages/custom-emojis-manager.vue')),
}, {
path: '/avatar-decorations',
name: 'avatarDecorations',
component: page(() => import('@/pages/avatar-decorations.vue')),
}, {
path: '/registry/keys/:domain/:path(*)?',
component: page(() => import('@/pages/registry.keys.vue')),
}, {
path: '/registry/value/:domain/:path(*)?',
component: page(() => import('@/pages/registry.value.vue')),
}, {
path: '/registry',
component: page(() => import('@/pages/registry.vue')),
}, {
path: '/install-extentions',
component: page(() => import('@/pages/install-extentions.vue')),
loginRequired: true,
}, {
path: '/admin/user/:userId',
component: iAmModerator ? page(() => import('@/pages/admin-user.vue')) : page(() => import('@/pages/not-found.vue')),
}, {
path: '/admin/file/:fileId',
component: iAmModerator ? page(() => import('@/pages/admin-file.vue')) : page(() => import('@/pages/not-found.vue')),
}, {
path: '/admin',
component: iAmModerator ? page(() => import('@/pages/admin/index.vue')) : page(() => import('@/pages/not-found.vue')),
children: [{
path: '/overview',
name: 'overview',
component: page(() => import('@/pages/admin/overview.vue')),
}, {
path: '/users',
name: 'users',
component: page(() => import('@/pages/admin/users.vue')),
}, {
path: '/emojis',
name: 'emojis',
component: page(() => import('@/pages/custom-emojis-manager.vue')),
}, {
path: '/avatar-decorations',
name: 'avatarDecorations',
component: page(() => import('@/pages/avatar-decorations.vue')),
}, {
path: '/queue',
name: 'queue',
component: page(() => import('@/pages/admin/queue.vue')),
}, {
path: '/files',
name: 'files',
component: page(() => import('@/pages/admin/files.vue')),
}, {
path: '/federation',
name: 'federation',
component: page(() => import('@/pages/admin/federation.vue')),
}, {
path: '/announcements',
name: 'announcements',
component: page(() => import('@/pages/admin/announcements.vue')),
}, {
path: '/ads',
name: 'ads',
component: page(() => import('@/pages/admin/ads.vue')),
}, {
path: '/roles/:id/edit',
name: 'roles',
component: page(() => import('@/pages/admin/roles.edit.vue')),
}, {
path: '/roles/new',
name: 'roles',
component: page(() => import('@/pages/admin/roles.edit.vue')),
}, {
path: '/roles/:id',
name: 'roles',
component: page(() => import('@/pages/admin/roles.role.vue')),
}, {
path: '/roles',
name: 'roles',
component: page(() => import('@/pages/admin/roles.vue')),
}, {
path: '/database',
name: 'database',
component: page(() => import('@/pages/admin/database.vue')),
}, {
path: '/abuses',
name: 'abuses',
component: page(() => import('@/pages/admin/abuses.vue')),
}, {
path: '/modlog',
name: 'modlog',
component: page(() => import('@/pages/admin/modlog.vue')),
}, {
path: '/settings',
name: 'settings',
component: page(() => import('@/pages/admin/settings.vue')),
}, {
path: '/branding',
name: 'branding',
component: page(() => import('@/pages/admin/branding.vue')),
}, {
path: '/moderation',
name: 'moderation',
component: page(() => import('@/pages/admin/moderation.vue')),
}, {
path: '/email-settings',
name: 'email-settings',
component: page(() => import('@/pages/admin/email-settings.vue')),
}, {
path: '/object-storage',
name: 'object-storage',
component: page(() => import('@/pages/admin/object-storage.vue')),
}, {
path: '/security',
name: 'security',
component: page(() => import('@/pages/admin/security.vue')),
}, {
path: '/relays',
name: 'relays',
component: page(() => import('@/pages/admin/relays.vue')),
}, {
path: '/instance-block',
name: 'instance-block',
component: page(() => import('@/pages/admin/instance-block.vue')),
}, {
path: '/proxy-account',
name: 'proxy-account',
component: page(() => import('@/pages/admin/proxy-account.vue')),
}, {
path: '/external-services',
name: 'external-services',
component: page(() => import('@/pages/admin/external-services.vue')),
}, {
path: '/other-settings',
name: 'other-settings',
component: page(() => import('@/pages/admin/other-settings.vue')),
}, {
path: '/server-rules',
name: 'server-rules',
component: page(() => import('@/pages/admin/server-rules.vue')),
}, {
path: '/invites',
name: 'invites',
component: page(() => import('@/pages/admin/invites.vue')),
}, {
path: '/',
component: page(() => import('@/pages/_empty_.vue')),
}],
}, {
path: '/my/notifications',
component: page(() => import('@/pages/notifications.vue')),
loginRequired: true,
}, {
path: '/my/favorites',
component: page(() => import('@/pages/favorites.vue')),
loginRequired: true,
}, {
path: '/my/achievements',
component: page(() => import('@/pages/achievements.vue')),
loginRequired: true,
}, {
path: '/my/drive/folder/:folder',
component: page(() => import('@/pages/drive.vue')),
loginRequired: true,
}, {
path: '/my/drive',
component: page(() => import('@/pages/drive.vue')),
loginRequired: true,
}, {
path: '/my/drive/file/:fileId',
component: page(() => import('@/pages/drive.file.vue')),
loginRequired: true,
}, {
path: '/my/follow-requests',
component: page(() => import('@/pages/follow-requests.vue')),
loginRequired: true,
}, {
path: '/my/lists/:listId',
component: page(() => import('@/pages/my-lists/list.vue')),
loginRequired: true,
}, {
path: '/my/lists',
component: page(() => import('@/pages/my-lists/index.vue')),
loginRequired: true,
}, {
path: '/my/clips',
component: page(() => import('@/pages/my-clips/index.vue')),
loginRequired: true,
}, {
path: '/my/antennas/create',
component: page(() => import('@/pages/my-antennas/create.vue')),
loginRequired: true,
}, {
path: '/my/antennas/:antennaId',
component: page(() => import('@/pages/my-antennas/edit.vue')),
loginRequired: true,
}, {
path: '/my/antennas',
component: page(() => import('@/pages/my-antennas/index.vue')),
loginRequired: true,
}, {
path: '/timeline/list/:listId',
component: page(() => import('@/pages/user-list-timeline.vue')),
loginRequired: true,
}, {
path: '/timeline/antenna/:antennaId',
component: page(() => import('@/pages/antenna-timeline.vue')),
loginRequired: true,
}, {
path: '/clicker',
component: page(() => import('@/pages/clicker.vue')),
loginRequired: true,
}, {
path: '/bubble-game',
component: page(() => import('@/pages/drop-and-fusion.vue')),
loginRequired: true,
}, {
path: '/timeline',
component: page(() => import('@/pages/timeline.vue')),
}, {
name: 'index',
path: '/',
component: $i ? page(() => import('@/pages/timeline.vue')) : page(() => import('@/pages/welcome.vue')),
globalCacheKey: 'index',
}, {
path: '/:(*)',
component: page(() => import('@/pages/not-found.vue')),
}];
function createRouterImpl(path: string): IRouter {
return new Router(routes, path, !!$i, page(() => import('@/pages/not-found.vue')));
}
/**
* {@link Router}{@link mainRouter}
* {@link Router}{@link provide}`routerFactory`
*/
export function setupRouter(app: App) {
app.provide('routerFactory', createRouterImpl);
const mainRouter = createRouterImpl(location.pathname + location.search + location.hash);
window.history.replaceState({ key: mainRouter.getCurrentKey() }, '', location.href);
window.addEventListener('popstate', (event) => {
mainRouter.replace(location.pathname + location.search + location.hash, event.state?.key);
});
mainRouter.addListener('push', ctx => {
window.history.pushState({ key: ctx.key }, '', ctx.path);
});
setMainRouter(mainRouter);
}

View file

@ -0,0 +1,163 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { ShallowRef } from 'vue';
import { EventEmitter } from 'eventemitter3';
import { IRouter, Resolved, RouteDef, RouterEvent } from '@/nirax.js';
function getMainRouter(): IRouter {
const router = mainRouterHolder;
if (!router) {
throw new Error('mainRouter is not found.');
}
return router;
}
/**
*
* {@link setupRouter}
*/
export function setMainRouter(router: IRouter) {
if (mainRouterHolder) {
throw new Error('mainRouter is already exists.');
}
mainRouterHolder = router;
}
/**
* {@link mainRouter}
* {@link mainRouter}undefinedになる期間がある
* undefined込みにしたくないのでこのクラスを緩衝材として使用する
*/
class MainRouterProxy implements IRouter {
private supplier: () => IRouter;
constructor(supplier: () => IRouter) {
this.supplier = supplier;
}
get current(): Resolved {
return this.supplier().current;
}
get currentRef(): ShallowRef<Resolved> {
return this.supplier().currentRef;
}
get currentRoute(): ShallowRef<RouteDef> {
return this.supplier().currentRoute;
}
get navHook(): ((path: string, flag?: any) => boolean) | null {
return this.supplier().navHook;
}
set navHook(value) {
this.supplier().navHook = value;
}
getCurrentKey(): string {
return this.supplier().getCurrentKey();
}
getCurrentPath(): any {
return this.supplier().getCurrentPath();
}
push(path: string, flag?: any): void {
this.supplier().push(path, flag);
}
replace(path: string, key?: string | null): void {
this.supplier().replace(path, key);
}
resolve(path: string): Resolved | null {
return this.supplier().resolve(path);
}
eventNames(): Array<EventEmitter.EventNames<RouterEvent>> {
return this.supplier().eventNames();
}
listeners<T extends EventEmitter.EventNames<RouterEvent>>(
event: T,
): Array<EventEmitter.EventListener<RouterEvent, T>> {
return this.supplier().listeners(event);
}
listenerCount(
event: EventEmitter.EventNames<RouterEvent>,
): number {
return this.supplier().listenerCount(event);
}
emit<T extends EventEmitter.EventNames<RouterEvent>>(
event: T,
...args: EventEmitter.EventArgs<RouterEvent, T>
): boolean {
return this.supplier().emit(event, ...args);
}
on<T extends EventEmitter.EventNames<RouterEvent>>(
event: T,
fn: EventEmitter.EventListener<RouterEvent, T>,
context?: any,
): this {
this.supplier().on(event, fn, context);
return this;
}
addListener<T extends EventEmitter.EventNames<RouterEvent>>(
event: T,
fn: EventEmitter.EventListener<RouterEvent, T>,
context?: any,
): this {
this.supplier().addListener(event, fn, context);
return this;
}
once<T extends EventEmitter.EventNames<RouterEvent>>(
event: T,
fn: EventEmitter.EventListener<RouterEvent, T>,
context?: any,
): this {
this.supplier().once(event, fn, context);
return this;
}
removeListener<T extends EventEmitter.EventNames<RouterEvent>>(
event: T,
fn?: EventEmitter.EventListener<RouterEvent, T>,
context?: any,
once?: boolean,
): this {
this.supplier().removeListener(event, fn, context, once);
return this;
}
off<T extends EventEmitter.EventNames<RouterEvent>>(
event: T,
fn?: EventEmitter.EventListener<RouterEvent, T>,
context?: any,
once?: boolean,
): this {
this.supplier().off(event, fn, context, once);
return this;
}
removeAllListeners(
event?: EventEmitter.EventNames<RouterEvent>,
): this {
this.supplier().removeAllListeners(event);
return this;
}
}
let mainRouterHolder: IRouter | null = null;
export const mainRouter: IRouter = new MainRouterProxy(getMainRouter);

View file

@ -0,0 +1,30 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { inject } from 'vue';
import { IRouter, Router } from '@/nirax.js';
import { mainRouter } from '@/global/router/main.js';
/**
* {@link Router}
* {@link setupRouter}{@link provide}{@link IRouter}
*/
export function useRouter(): IRouter {
return inject<Router | null>('router', null) ?? mainRouter;
}
/**
* {@link Router}
* {@link setupRouter}
*/
export function useRouterFactory(): (path: string) => IRouter {
const factory = inject<(path: string) => IRouter>('routerFactory');
if (!factory) {
console.error('routerFactory is not defined.');
throw new Error('routerFactory is not defined.');
}
return factory;
}

View file

@ -16,13 +16,13 @@
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self';
content="default-src 'self' https://newassets.hcaptcha.com/ https://challenges.cloudflare.com/ http://localhost:7493/;
worker-src 'self';
script-src 'self' 'unsafe-eval';
script-src 'self' 'unsafe-eval' https://*.hcaptcha.com https://challenges.cloudflare.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: www.google.com xn--931a.moe localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000;
img-src 'self' data: blob: www.google.com xn--931a.moe localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000;
media-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000;
connect-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000;"
connect-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000 https://newassets.hcaptcha.com;"
/>
<meta property="og:site_name" content="[DEV BUILD] Misskey" />
<meta name="viewport" content="width=device-width, initial-scale=1">

View file

@ -5,7 +5,7 @@
import { computed, reactive } from 'vue';
import * as Misskey from 'misskey-js';
import { api } from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { miLocalStorage } from '@/local-storage.js';
import { DEFAULT_INFO_IMAGE_URL, DEFAULT_NOT_FOUND_IMAGE_URL, DEFAULT_SERVER_ERROR_IMAGE_URL } from '@/const.js';
@ -26,7 +26,7 @@ export const infoImageUrl = computed(() => instance.infoImageUrl ?? DEFAULT_INFO
export const notFoundImageUrl = computed(() => instance.notFoundImageUrl ?? DEFAULT_NOT_FOUND_IMAGE_URL);
export async function fetchInstance() {
const meta = await api('meta', {
const meta = await misskeyApi('meta', {
detail: false,
});

View file

@ -5,11 +5,11 @@
// NIRAX --- A lightweight router
import { EventEmitter } from 'eventemitter3';
import { Component, onMounted, shallowRef, ShallowRef } from 'vue';
import { EventEmitter } from 'eventemitter3';
import { safeURIDecode } from '@/scripts/safe-uri-decode.js';
type RouteDef = {
export type RouteDef = {
path: string;
component: Component;
query?: Record<string, string>;
@ -27,6 +27,27 @@ type ParsedPath = (string | {
optional?: boolean;
})[];
export type RouterEvent = {
change: (ctx: {
beforePath: string;
path: string;
resolved: Resolved;
key: string;
}) => void;
replace: (ctx: {
path: string;
key: string;
}) => void;
push: (ctx: {
beforePath: string;
path: string;
route: RouteDef | null;
props: Map<string, string> | null;
key: string;
}) => void;
same: () => void;
}
export type Resolved = { route: RouteDef; props: Map<string, string | boolean>; child?: Resolved; };
function parsePath(path: string): ParsedPath {
@ -54,26 +75,85 @@ function parsePath(path: string): ParsedPath {
return res;
}
export class Router extends EventEmitter<{
change: (ctx: {
beforePath: string;
path: string;
resolved: Resolved;
key: string;
}) => void;
replace: (ctx: {
path: string;
key: string;
}) => void;
push: (ctx: {
beforePath: string;
path: string;
route: RouteDef | null;
props: Map<string, string> | null;
key: string;
}) => void;
same: () => void;
}> {
export interface IRouter extends EventEmitter<RouterEvent> {
current: Resolved;
currentRef: ShallowRef<Resolved>;
currentRoute: ShallowRef<RouteDef>;
navHook: ((path: string, flag?: any) => boolean) | null;
resolve(path: string): Resolved | null;
getCurrentPath(): any;
getCurrentKey(): string;
push(path: string, flag?: any): void;
replace(path: string, key?: string | null): void;
/** @see EventEmitter */
eventNames(): Array<EventEmitter.EventNames<RouterEvent>>;
/** @see EventEmitter */
listeners<T extends EventEmitter.EventNames<RouterEvent>>(
event: T
): Array<EventEmitter.EventListener<RouterEvent, T>>;
/** @see EventEmitter */
listenerCount(
event: EventEmitter.EventNames<RouterEvent>
): number;
/** @see EventEmitter */
emit<T extends EventEmitter.EventNames<RouterEvent>>(
event: T,
...args: EventEmitter.EventArgs<RouterEvent, T>
): boolean;
/** @see EventEmitter */
on<T extends EventEmitter.EventNames<RouterEvent>>(
event: T,
fn: EventEmitter.EventListener<RouterEvent, T>,
context?: any
): this;
/** @see EventEmitter */
addListener<T extends EventEmitter.EventNames<RouterEvent>>(
event: T,
fn: EventEmitter.EventListener<RouterEvent, T>,
context?: any
): this;
/** @see EventEmitter */
once<T extends EventEmitter.EventNames<RouterEvent>>(
event: T,
fn: EventEmitter.EventListener<RouterEvent, T>,
context?: any
): this;
/** @see EventEmitter */
removeListener<T extends EventEmitter.EventNames<RouterEvent>>(
event: T,
fn?: EventEmitter.EventListener<RouterEvent, T>,
context?: any,
once?: boolean | undefined
): this;
/** @see EventEmitter */
off<T extends EventEmitter.EventNames<RouterEvent>>(
event: T,
fn?: EventEmitter.EventListener<RouterEvent, T>,
context?: any,
once?: boolean | undefined
): this;
/** @see EventEmitter */
removeAllListeners(
event?: EventEmitter.EventNames<RouterEvent>
): this;
}
export class Router extends EventEmitter<RouterEvent> implements IRouter {
private routes: RouteDef[];
public current: Resolved;
public currentRef: ShallowRef<Resolved> = shallowRef();
@ -277,7 +357,7 @@ export class Router extends EventEmitter<{
}
}
export function useScrollPositionManager(getScrollContainer: () => HTMLElement, router: Router) {
export function useScrollPositionManager(getScrollContainer: () => HTMLElement, router: IRouter) {
const scrollPosStore = new Map<string, number>();
onMounted(() => {

View file

@ -5,12 +5,11 @@
// TODO: なんでもかんでもos.tsに突っ込むのやめたいのでよしなに分割する
import { pendingApiRequestsCount, api, apiGet } from '@/scripts/api.js';
export { pendingApiRequestsCount, api, apiGet };
import { Component, markRaw, Ref, ref, defineAsyncComponent } from 'vue';
import { EventEmitter } from 'eventemitter3';
import insertTextAtCursor from 'insert-text-at-cursor';
import * as Misskey from 'misskey-js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { i18n } from '@/i18n.js';
import MkPostFormDialog from '@/components/MkPostFormDialog.vue';
import MkWaitingDialog from '@/components/MkWaitingDialog.vue';
@ -33,7 +32,7 @@ export const apiWithDialog = ((
data: Record<string, any> = {},
token?: string | null | undefined,
) => {
const promise = api(endpoint, data, token);
const promise = misskeyApi(endpoint, data, token);
promiseDialog(promise, null, async (err) => {
let title = null;
let text = err.message + '\n' + (err as any).id;
@ -83,7 +82,7 @@ export const apiWithDialog = ((
});
return promise;
}) as typeof api;
}) as typeof misskeyApi;
export function promiseDialog<T extends Promise<any>>(
promise: T,
@ -621,7 +620,7 @@ export function checkExistence(fileData: ArrayBuffer): Promise<any> {
const data = new FormData();
data.append('md5', getMD5(fileData));
os.api('drive/files/find-by-hash', {
api('drive/files/find-by-hash', {
md5: getMD5(fileData)
}).then(resp => {
resolve(resp.length > 0 ? resp[0] : null);

View file

@ -29,7 +29,7 @@ import { ref, computed } from 'vue';
import * as Misskey from 'misskey-js';
import MkButton from '@/components/MkButton.vue';
import { version } from '@/config.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { unisonReload } from '@/scripts/unison-reload.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
@ -46,7 +46,7 @@ const loaded = ref(false);
const serverIsDead = ref(false);
const meta = ref<Misskey.entities.MetaResponse | null>(null);
os.api('meta', {
misskeyApi('meta', {
detail: false,
}).then(res => {
loaded.value = true;

View file

@ -114,7 +114,7 @@ import FormSplit from '@/components/form/split.vue';
import MkFolder from '@/components/MkFolder.vue';
import MkKeyValue from '@/components/MkKeyValue.vue';
import MkInstanceStats from '@/components/MkInstanceStats.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import number from '@/filters/number.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
@ -136,7 +136,7 @@ watch(tab, () => {
}
});
const initStats = () => os.api('stats', {
const initStats = () => misskeyApi('stats', {
}).then((res) => {
stats.value = res;
});

Some files were not shown because too many files have changed in this diff Show more