Merge remote-tracking branch 'misskey-original/develop' into develop
# Conflicts: # locales/index.d.ts # locales/ja-JP.yml # package.json # packages/backend/src/server/api/endpoints/admin/emoji/copy.ts # packages/backend/src/server/api/endpoints/admin/meta.ts # packages/backend/src/server/api/endpoints/channels/timeline.ts # packages/backend/src/server/api/endpoints/notes/featured.ts # packages/frontend/src/components/MkButton.vue # packages/frontend/src/components/MkClickerGame.vue # packages/frontend/src/components/MkDialog.vue # packages/frontend/src/components/MkDrive.vue # packages/frontend/src/components/MkEmojiEditDialog.vue # packages/frontend/src/components/MkEmojiPicker.section.vue # packages/frontend/src/components/MkEmojiPicker.vue # packages/frontend/src/components/MkFollowButton.vue # packages/frontend/src/components/MkInstanceTicker.vue # packages/frontend/src/components/MkLaunchPad.vue # packages/frontend/src/components/MkMenu.vue # packages/frontend/src/components/MkNote.vue # packages/frontend/src/components/MkNoteSimple.vue # packages/frontend/src/components/MkPostForm.vue # packages/frontend/src/components/MkRadio.vue # packages/frontend/src/components/MkSignupDialog.form.vue # packages/frontend/src/components/MkSwitch.vue # packages/frontend/src/custom-emojis.ts # packages/frontend/src/pages/about.emojis.vue # packages/frontend/src/pages/about.vue # packages/frontend/src/pages/admin/index.vue # packages/frontend/src/pages/admin/other-settings.vue # packages/frontend/src/pages/custom-emojis-manager.vue # packages/frontend/src/pages/settings/general.vue # packages/frontend/src/pages/settings/mute-block.vue # packages/frontend/src/pages/timeline.vue # packages/frontend/src/pages/user/home.vue # packages/frontend/src/pages/user/index.files.vue # packages/frontend/src/scripts/get-note-menu.ts # packages/frontend/src/store.ts # packages/frontend/src/ui/_common_/stream-indicator.vue # packages/frontend/src/ui/classic.sidebar.vue # packages/frontend/src/ui/universal.vue # packages/frontend/src/ui/universal.widgets.vue # packages/frontend/vite.config.ts # packages/misskey-js/etc/misskey-js.api.md # packages/misskey-js/src/api.types.ts # packages/misskey-js/src/entities.ts # packages/misskey-js/src/streaming.types.ts
This commit is contained in:
commit
8c4a08c383
404 changed files with 42042 additions and 11250 deletions
|
|
@ -50,6 +50,7 @@ export function createAiScriptEnv(opts) {
|
|||
return values.ERROR('request_failed', utils.jsToVal(err));
|
||||
});
|
||||
}),
|
||||
/* セキュリティ上の問題があるため無効化
|
||||
'Mk:apiExternal': values.FN_NATIVE(async ([host, ep, param, token]) => {
|
||||
utils.assertString(host);
|
||||
utils.assertString(ep);
|
||||
|
|
@ -60,6 +61,7 @@ export function createAiScriptEnv(opts) {
|
|||
return values.ERROR('request_failed', utils.jsToVal(err));
|
||||
});
|
||||
}),
|
||||
*/
|
||||
'Mk:save': values.FN_NATIVE(([key, value]) => {
|
||||
utils.assertString(key);
|
||||
miLocalStorage.setItem(`aiscript:${opts.storageKey}:${key.value}`, JSON.stringify(utils.valToJs(value)));
|
||||
|
|
|
|||
|
|
@ -121,6 +121,7 @@ export type AsUiPostFormButton = AsUiComponentBase & {
|
|||
rounded?: boolean;
|
||||
form?: {
|
||||
text: string;
|
||||
cw?: string;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -128,6 +129,7 @@ export type AsUiPostForm = AsUiComponentBase & {
|
|||
type: 'postForm';
|
||||
form?: {
|
||||
text: string;
|
||||
cw?: string;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -454,8 +456,11 @@ function getPostFormButtonOptions(def: values.Value | undefined, call: (fn: valu
|
|||
const getForm = () => {
|
||||
const text = form!.value.get('text');
|
||||
utils.assertString(text);
|
||||
const cw = form!.value.get('cw');
|
||||
if (cw) utils.assertString(cw);
|
||||
return {
|
||||
text: text.value,
|
||||
cw: cw?.value,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -478,8 +483,11 @@ function getPostFormOptions(def: values.Value | undefined, call: (fn: values.VFn
|
|||
const getForm = () => {
|
||||
const text = form!.value.get('text');
|
||||
utils.assertString(text);
|
||||
const cw = form!.value.get('cw');
|
||||
if (cw) utils.assertString(cw);
|
||||
return {
|
||||
text: text.value,
|
||||
cw: cw?.value,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@ import { $i } from '@/account.js';
|
|||
export const pendingApiRequestsCount = ref(0);
|
||||
|
||||
// Implements Misskey.api.ApiClient.request
|
||||
export function api<E extends keyof Misskey.Endpoints, P extends Misskey.Endpoints[E]['req']>(endpoint: E, data: P = {} as any, token?: string | null | undefined, signal?: AbortSignal): Promise<Misskey.Endpoints[E]['res']> {
|
||||
export function api<E extends keyof Misskey.Endpoints, P extends Misskey.Endpoints[E]['req']>(
|
||||
endpoint: E,
|
||||
data: P = {} as any,
|
||||
token?: string | null | undefined,
|
||||
signal?: AbortSignal,
|
||||
): Promise<Misskey.api.SwitchCaseResponseType<E, P>> {
|
||||
if (endpoint.includes('://')) throw new Error('invalid endpoint');
|
||||
pendingApiRequestsCount.value++;
|
||||
|
||||
|
|
@ -51,51 +56,11 @@ export function api<E extends keyof Misskey.Endpoints, P extends Misskey.Endpoin
|
|||
return promise;
|
||||
}
|
||||
|
||||
export function apiExternal<E extends keyof Misskey.Endpoints, P extends Misskey.Endpoints[E]['req']>(hostUrl: string, endpoint: E, data: P = {} as any, token?: string | null | undefined, signal?: AbortSignal): Promise<Misskey.Endpoints[E]['res']> {
|
||||
if (!/^https?:\/\//.test(hostUrl)) throw new Error('invalid host name');
|
||||
if (endpoint.includes('://')) throw new Error('invalid endpoint');
|
||||
pendingApiRequestsCount.value++;
|
||||
|
||||
const onFinally = () => {
|
||||
pendingApiRequestsCount.value--;
|
||||
};
|
||||
|
||||
const promise = new Promise<Misskey.Endpoints[E]['res'] | void>((resolve, reject) => {
|
||||
// Append a credential
|
||||
(data as any).i = token;
|
||||
|
||||
const fullUrl = (hostUrl.slice(-1) === '/' ? hostUrl.slice(0, -1) : hostUrl)
|
||||
+ '/api/' + (endpoint.slice(0, 1) === '/' ? endpoint.slice(1) : endpoint);
|
||||
// Send request
|
||||
window.fetch(fullUrl, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
credentials: 'omit',
|
||||
cache: 'no-cache',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
signal,
|
||||
}).then(async (res) => {
|
||||
const body = res.status === 204 ? null : await res.json();
|
||||
|
||||
if (res.status === 200) {
|
||||
resolve(body);
|
||||
} else if (res.status === 204) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(body.error);
|
||||
}
|
||||
}).catch(reject);
|
||||
});
|
||||
|
||||
promise.then(onFinally, onFinally);
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
// Implements Misskey.api.ApiClient.request
|
||||
export function apiGet <E extends keyof Misskey.Endpoints, P extends Misskey.Endpoints[E]['req']>(endpoint: E, data: P = {} as any): Promise<Misskey.Endpoints[E]['res']> {
|
||||
export function apiGet<E extends keyof Misskey.Endpoints, P extends Misskey.Endpoints[E]['req']>(
|
||||
endpoint: E,
|
||||
data: P = {} as any,
|
||||
): Promise<Misskey.api.SwitchCaseResponseType<E, P>> {
|
||||
pendingApiRequestsCount.value++;
|
||||
|
||||
const onFinally = () => {
|
||||
|
|
|
|||
57
packages/frontend/src/scripts/emoji-picker.ts
Normal file
57
packages/frontend/src/scripts/emoji-picker.ts
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { defineAsyncComponent, Ref, ref } from 'vue';
|
||||
import { popup } from '@/os.js';
|
||||
|
||||
/**
|
||||
* 絵文字ピッカーを表示する。
|
||||
* 類似の機能として{@link ReactionPicker}が存在しているが、この機能とは動きが異なる。
|
||||
* 投稿フォームなどで絵文字を選択する時など、絵文字ピックアップ後でもダイアログが消えずに残り、
|
||||
* 一度表示したダイアログを連続で使用できることが望ましいシーンでの利用が想定される。
|
||||
*/
|
||||
class EmojiPicker {
|
||||
private src: Ref<HTMLElement | null> = ref(null);
|
||||
private manualShowing = ref(false);
|
||||
private onChosen?: (emoji: string) => void;
|
||||
private onClosed?: () => void;
|
||||
|
||||
constructor() {
|
||||
// nop
|
||||
}
|
||||
|
||||
public async init() {
|
||||
await popup(defineAsyncComponent(() => import('@/components/MkEmojiPickerDialog.vue')), {
|
||||
src: this.src,
|
||||
asReactionPicker: false,
|
||||
manualShowing: this.manualShowing,
|
||||
choseAndClose: false,
|
||||
}, {
|
||||
done: emoji => {
|
||||
if (this.onChosen) this.onChosen(emoji);
|
||||
},
|
||||
close: () => {
|
||||
this.manualShowing.value = false;
|
||||
},
|
||||
closed: () => {
|
||||
this.src.value = null;
|
||||
if (this.onClosed) this.onClosed();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public show(
|
||||
src: HTMLElement,
|
||||
onChosen: EmojiPicker['onChosen'],
|
||||
onClosed: EmojiPicker['onClosed'],
|
||||
) {
|
||||
this.src.value = src;
|
||||
this.manualShowing.value = true;
|
||||
this.onChosen = onChosen;
|
||||
this.onClosed = onClosed;
|
||||
}
|
||||
}
|
||||
|
||||
export const emojiPicker = new EmojiPicker();
|
||||
|
|
@ -43,3 +43,9 @@ export function getEmojiName(char: string): string | null {
|
|||
return emojilist[idx].name;
|
||||
}
|
||||
}
|
||||
|
||||
export interface CustomEmojiFolderTree {
|
||||
value: string;
|
||||
category: string;
|
||||
children: CustomEmojiFolderTree[];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ export function getDriveFileMenu(file: Misskey.entities.DriveFile, folder?: Miss
|
|||
to: `/my/drive/file/${file.id}`,
|
||||
text: i18n.ts._fileViewer.title,
|
||||
icon: 'ti ti-info-circle',
|
||||
}, null, {
|
||||
}, { type: 'divider' }, {
|
||||
text: i18n.ts.rename,
|
||||
icon: 'ti ti-forms',
|
||||
action: () => rename(file),
|
||||
|
|
@ -128,7 +128,7 @@ export function getDriveFileMenu(file: Misskey.entities.DriveFile, folder?: Miss
|
|||
aspectRatio: NaN,
|
||||
uploadFolder: folder ? folder.id : folder,
|
||||
}),
|
||||
}] : [], null, {
|
||||
}] : [], { type: 'divider' }, {
|
||||
text: i18n.ts.createNoteFromTheFile,
|
||||
icon: 'ti ti-pencil',
|
||||
action: () => os.post({
|
||||
|
|
@ -145,7 +145,7 @@ export function getDriveFileMenu(file: Misskey.entities.DriveFile, folder?: Miss
|
|||
text: i18n.ts.download,
|
||||
icon: 'ti ti-download',
|
||||
download: file.name,
|
||||
}, null, {
|
||||
}, { type: 'divider' }, {
|
||||
text: i18n.ts.delete,
|
||||
icon: 'ti ti-trash',
|
||||
danger: true,
|
||||
|
|
@ -153,7 +153,7 @@ export function getDriveFileMenu(file: Misskey.entities.DriveFile, folder?: Miss
|
|||
}];
|
||||
|
||||
if (defaultStore.state.devMode) {
|
||||
menu = menu.concat([null, {
|
||||
menu = menu.concat([{ type: 'divider' }, {
|
||||
icon: 'ti ti-id',
|
||||
text: i18n.ts.copyFileId,
|
||||
action: () => {
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ export async function getNoteClipMenu(props: {
|
|||
},
|
||||
);
|
||||
},
|
||||
})), null, {
|
||||
})), { type: 'divider' }, {
|
||||
icon: 'ti ti-plus',
|
||||
text: i18n.ts.createNew,
|
||||
action: async () => {
|
||||
|
|
@ -94,7 +94,7 @@ export async function getNoteClipMenu(props: {
|
|||
}];
|
||||
}
|
||||
|
||||
export function getAbuseNoteMenu(note: misskey.entities.Note, text: string): MenuItem {
|
||||
export function getAbuseNoteMenu(note: Misskey.entities.Note, text: string): MenuItem {
|
||||
return {
|
||||
icon: 'ti ti-exclamation-circle',
|
||||
text,
|
||||
|
|
@ -108,7 +108,7 @@ export function getAbuseNoteMenu(note: misskey.entities.Note, text: string): Men
|
|||
};
|
||||
}
|
||||
|
||||
export function getCopyNoteLinkMenu(note: misskey.entities.Note, text: string): MenuItem {
|
||||
export function getCopyNoteLinkMenu(note: Misskey.entities.Note, text: string): MenuItem {
|
||||
return {
|
||||
icon: 'ti ti-link',
|
||||
text,
|
||||
|
|
@ -268,7 +268,7 @@ export function getNoteMenu(props: {
|
|||
text: i18n.ts.unclip,
|
||||
danger: true,
|
||||
action: unclip,
|
||||
}, null] : []
|
||||
}, { type: 'divider' }] : []
|
||||
), {
|
||||
icon: 'ti ti-info-circle',
|
||||
text: i18n.ts.details,
|
||||
|
|
@ -282,7 +282,7 @@ export function getNoteMenu(props: {
|
|||
icon: 'ti ti-external-link',
|
||||
text: i18n.ts.showOnRemote,
|
||||
action: () => {
|
||||
window.open(appearNote.url ?? appearNote.uri, '_blank');
|
||||
window.open(appearNote.url ?? appearNote.uri, '_blank', 'noopener');
|
||||
},
|
||||
} : undefined,
|
||||
...(isSupportShare() ? [{
|
||||
|
|
@ -295,7 +295,7 @@ export function getNoteMenu(props: {
|
|||
text: i18n.ts.translate,
|
||||
action: translate,
|
||||
} : undefined,
|
||||
null,
|
||||
{ type: 'divider' },
|
||||
statePromise.then(state => state.isFavorited ? {
|
||||
icon: 'ti ti-star-off',
|
||||
text: i18n.ts.unfavorite,
|
||||
|
|
@ -342,7 +342,7 @@ export function getNoteMenu(props: {
|
|||
},
|
||||
/*
|
||||
...($i.isModerator || $i.isAdmin ? [
|
||||
null,
|
||||
{ type: 'divider' },
|
||||
{
|
||||
icon: 'ti ti-speakerphone',
|
||||
text: i18n.ts.promote,
|
||||
|
|
@ -351,13 +351,13 @@ export function getNoteMenu(props: {
|
|||
: []
|
||||
),*/
|
||||
...(appearNote.userId !== $i.id ? [
|
||||
null,
|
||||
{ type: 'divider' },
|
||||
appearNote.userId !== $i.id ? getAbuseNoteMenu(appearNote, i18n.ts.reportAbuse) : undefined,
|
||||
]
|
||||
: []
|
||||
),
|
||||
...(appearNote.userId === $i.id || $i.isModerator || $i.isAdmin ? [
|
||||
null,
|
||||
{ type: 'divider' },
|
||||
appearNote.userId === $i.id && $i.policies.canEditNote ? {
|
||||
icon: 'ti ti-edit',
|
||||
text: i18n.ts.edit,
|
||||
|
|
@ -391,7 +391,7 @@ export function getNoteMenu(props: {
|
|||
icon: 'ti ti-external-link',
|
||||
text: i18n.ts.showOnRemote,
|
||||
action: () => {
|
||||
window.open(appearNote.url ?? appearNote.uri, '_blank');
|
||||
window.open(appearNote.url ?? appearNote.uri, '_blank', 'noopener');
|
||||
},
|
||||
} : undefined]
|
||||
.filter(x => x !== undefined);
|
||||
|
|
@ -537,10 +537,9 @@ export function getRenoteMenu(props: {
|
|||
}]);
|
||||
}
|
||||
|
||||
// nullを挟むことで区切り線を出せる
|
||||
const renoteItems = [
|
||||
...normalRenoteItems,
|
||||
...(channelRenoteItems.length > 0 && normalRenoteItems.length > 0) ? [null] : [],
|
||||
...(channelRenoteItems.length > 0 && normalRenoteItems.length > 0) ? [{ type: 'divider' }] : [],
|
||||
...channelRenoteItems,
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
|
|||
userId: user.id,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async function invalidateFollow() {
|
||||
if (!await getConfirmed(i18n.ts.breakFollowConfirm)) return;
|
||||
|
||||
|
|
@ -183,7 +183,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
|
|||
const canonical = user.host === null ? `@${user.username}` : `@${user.username}@${user.host}`;
|
||||
os.post({ specified: user, initialText: `${canonical} ` });
|
||||
},
|
||||
}, null, {
|
||||
}, { type: 'divider' }, {
|
||||
icon: 'ti ti-pencil',
|
||||
text: i18n.ts.editMemo,
|
||||
action: () => {
|
||||
|
|
@ -307,7 +307,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
|
|||
}]);
|
||||
//}
|
||||
|
||||
menu = menu.concat([null, {
|
||||
menu = menu.concat([{ type: 'divider' }, {
|
||||
icon: user.isMuted ? 'ti ti-eye' : 'ti ti-eye-off',
|
||||
text: user.isMuted ? i18n.ts.unmute : i18n.ts.mute,
|
||||
action: toggleMute,
|
||||
|
|
@ -329,7 +329,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
|
|||
}]);
|
||||
}
|
||||
|
||||
menu = menu.concat([null, {
|
||||
menu = menu.concat([{ type: 'divider' }, {
|
||||
icon: 'ti ti-exclamation-circle',
|
||||
text: i18n.ts.reportAbuse,
|
||||
action: reportAbuse,
|
||||
|
|
@ -337,15 +337,15 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
|
|||
}
|
||||
|
||||
if (user.host !== null) {
|
||||
menu = menu.concat([null, {
|
||||
menu = menu.concat([{ type: 'divider' }, {
|
||||
icon: 'ti ti-refresh',
|
||||
text: i18n.ts.updateRemoteUser,
|
||||
action: userInfoUpdate,
|
||||
}]);
|
||||
}
|
||||
|
||||
|
||||
if (defaultStore.state.devMode) {
|
||||
menu = menu.concat([null, {
|
||||
menu = menu.concat([{ type: 'divider' }, {
|
||||
icon: 'ti ti-id',
|
||||
text: i18n.ts.copyUserId,
|
||||
action: () => {
|
||||
|
|
@ -355,7 +355,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
|
|||
}
|
||||
|
||||
if ($i && meId === user.id) {
|
||||
menu = menu.concat([null, {
|
||||
menu = menu.concat([{ type: 'divider' }, {
|
||||
icon: 'ti ti-pencil',
|
||||
text: i18n.ts.editProfile,
|
||||
action: () => {
|
||||
|
|
@ -365,7 +365,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
|
|||
}
|
||||
|
||||
if (userActions.length > 0) {
|
||||
menu = menu.concat([null, ...userActions.map(action => ({
|
||||
menu = menu.concat([{ type: 'divider' }, ...userActions.map(action => ({
|
||||
icon: 'ti ti-plug',
|
||||
text: action.title,
|
||||
action: () => {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export type PageMetadata = {
|
|||
icon?: string | null;
|
||||
avatar?: Misskey.entities.User | null;
|
||||
userName?: Misskey.entities.User | null;
|
||||
needWideArea?: boolean;
|
||||
};
|
||||
|
||||
export function definePageMetadata(metadata: PageMetadata | null | Ref<PageMetadata | null> | ComputedRef<PageMetadata | null>): void {
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ export const getBuiltinThemes = () => Promise.all(
|
|||
'd-cherry',
|
||||
'd-ice',
|
||||
'd-u0',
|
||||
].map(name => import(`../themes/${name}.json5`).then(({ default: _default }): Theme => _default)),
|
||||
].map(name => import(`@/themes/${name}.json5`).then(({ default: _default }): Theme => _default)),
|
||||
);
|
||||
|
||||
export const getBuiltinThemesRef = () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue