Merge remote-tracking branch 'mi-dev/master' into report

This commit is contained in:
mattyatea 2023-12-28 04:16:03 +09:00
commit cb1586658e
No known key found for this signature in database
GPG key ID: 068E54E2C33BEF9A
567 changed files with 10281 additions and 3996 deletions

View file

@ -47,6 +47,7 @@ export type AsUiMfm = AsUiComponentBase & {
bold?: boolean;
color?: string;
font?: 'serif' | 'sans-serif' | 'monospace';
onClickEv?: (evId: string) => void
};
export type AsUiButton = AsUiComponentBase & {
@ -230,6 +231,8 @@ function getMfmOptions(def: values.Value | undefined): Omit<AsUiMfm, 'id' | 'typ
if (color) utils.assertString(color);
const font = def.value.get('font');
if (font) utils.assertString(font);
const onClickEv = def.value.get('onClickEv');
if (onClickEv) utils.assertFunction(onClickEv);
return {
text: text?.value,
@ -237,6 +240,9 @@ function getMfmOptions(def: values.Value | undefined): Omit<AsUiMfm, 'id' | 'typ
bold: bold?.value,
color: color?.value,
font: font?.value,
onClickEv: (evId: string) => {
if (onClickEv) call(onClickEv, values.STR(evId));
},
};
}

View file

@ -8,6 +8,8 @@ import getCaretCoordinates from 'textarea-caret';
import { toASCII } from 'punycode/';
import { popup } from '@/os.js';
export type SuggestionType = 'user' | 'hashtag' | 'emoji' | 'mfmTag';
export class Autocomplete {
private suggestion: {
x: Ref<number>;
@ -19,6 +21,7 @@ export class Autocomplete {
private currentType: string;
private textRef: Ref<string>;
private opening: boolean;
private onlyType: SuggestionType[];
private get text(): string {
// Use raw .value to get the latest value
@ -35,7 +38,7 @@ export class Autocomplete {
/**
*
*/
constructor(textarea: HTMLInputElement | HTMLTextAreaElement, textRef: Ref<string>) {
constructor(textarea: HTMLInputElement | HTMLTextAreaElement, textRef: Ref<string>, onlyType?: SuggestionType[]) {
//#region BIND
this.onInput = this.onInput.bind(this);
this.complete = this.complete.bind(this);
@ -46,6 +49,7 @@ export class Autocomplete {
this.textarea = textarea;
this.textRef = textRef;
this.opening = false;
this.onlyType = onlyType ?? ['user', 'hashtag', 'emoji', 'mfmTag'];
this.attach();
}
@ -95,7 +99,7 @@ export class Autocomplete {
let opened = false;
if (isMention) {
if (isMention && this.onlyType.includes('user')) {
const username = text.substring(mentionIndex + 1);
if (username !== '' && username.match(/^[a-zA-Z0-9_]+$/)) {
this.open('user', username);
@ -106,7 +110,7 @@ export class Autocomplete {
}
}
if (isHashtag && !opened) {
if (isHashtag && !opened && this.onlyType.includes('hashtag')) {
const hashtag = text.substring(hashtagIndex + 1);
if (!hashtag.includes(' ')) {
this.open('hashtag', hashtag);
@ -114,7 +118,7 @@ export class Autocomplete {
}
}
if (isEmoji && !opened) {
if (isEmoji && !opened && this.onlyType.includes('emoji')) {
const emoji = text.substring(emojiIndex + 1);
if (!emoji.includes(' ')) {
this.open('emoji', emoji);
@ -122,7 +126,7 @@ export class Autocomplete {
}
}
if (isMfmTag && !opened) {
if (isMfmTag && !opened && this.onlyType.includes('mfmTag')) {
const mfmTag = text.substring(mfmTagIndex + 1);
if (!mfmTag.includes(' ')) {
this.open('mfmTag', mfmTag.replace('[', ''));

View file

@ -6,6 +6,7 @@ import { fetchCustomEmojis } from '@/custom-emojis.js';
export async function clearCache() {
os.waiting();
miLocalStorage.removeItem('locale');
miLocalStorage.removeItem('localeVersion');
miLocalStorage.removeItem('theme');
miLocalStorage.removeItem('emojis');
miLocalStorage.removeItem('lastEmojisFetchedAt');

View file

@ -5,6 +5,7 @@
import { defineAsyncComponent, Ref, ref } from 'vue';
import { popup } from '@/os.js';
import { defaultStore } from '@/store.js';
/**
*
@ -23,8 +24,10 @@ class EmojiPicker {
}
public async init() {
const emojisRef = defaultStore.reactiveState.pinnedEmojis;
await popup(defineAsyncComponent(() => import('@/components/MkEmojiPickerDialog.vue')), {
src: this.src,
pinnedEmojis: emojisRef,
asReactionPicker: false,
manualShowing: this.manualShowing,
choseAndClose: false,
@ -44,8 +47,8 @@ class EmojiPicker {
public show(
src: HTMLElement,
onChosen: EmojiPicker['onChosen'],
onClosed: EmojiPicker['onClosed'],
onChosen?: EmojiPicker['onChosen'],
onClosed?: EmojiPicker['onClosed'],
) {
this.src.value = src;
this.manualShowing.value = true;

View file

@ -82,7 +82,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),
@ -101,7 +101,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({
@ -118,7 +118,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,
@ -126,7 +126,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: () => {

View file

@ -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,
@ -122,7 +122,7 @@ export function getCopyNoteLinkMenu(note: misskey.entities.Note, text: string):
export function getNoteMenu(props: {
note: Misskey.entities.Note;
menuButton: Ref<HTMLElement>;
translation: Ref<any>;
translation: Ref<Misskey.entities.NotesTranslateResponse | null>;
translating: Ref<boolean>;
isDeleted: Ref<boolean>;
currentClip?: Misskey.entities.Clip;
@ -264,7 +264,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,
@ -291,7 +291,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,
@ -338,7 +338,7 @@ export function getNoteMenu(props: {
},
/*
...($i.isModerator || $i.isAdmin ? [
null,
{ type: 'divider' },
{
icon: 'ti ti-speakerphone',
text: i18n.ts.promote,
@ -347,13 +347,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 ? {
icon: 'ti ti-edit',
text: i18n.ts.deleteAndEdit,
@ -389,7 +389,7 @@ export function getNoteMenu(props: {
}
if (noteActions.length > 0) {
menu = menu.concat([null, ...noteActions.map(action => ({
menu = menu.concat([{ type: "divider" }, ...noteActions.map(action => ({
icon: 'ti ti-plug',
text: action.title,
action: () => {
@ -399,7 +399,7 @@ export function getNoteMenu(props: {
}
if (defaultStore.state.devMode) {
menu = menu.concat([null, {
menu = menu.concat([{ type: "divider" }, {
icon: 'ti ti-id',
text: i18n.ts.copyNoteId,
action: () => {
@ -528,10 +528,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,
];

View file

@ -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: () => {

View file

@ -6,11 +6,19 @@
import * as Misskey from 'misskey-js';
import { $i } from '@/account.js';
export function isFfVisibleForMe(user: Misskey.entities.UserDetailed): boolean {
export function isFollowingVisibleForMe(user: Misskey.entities.UserDetailed): boolean {
if ($i && $i.id === user.id) return true;
if (user.ffVisibility === 'private') return false;
if (user.ffVisibility === 'followers' && !user.isFollowing) return false;
if (user.followingVisibility === 'private') return false;
if (user.followingVisibility === 'followers' && !user.isFollowing) return false;
return true;
}
export function isFollowersVisibleForMe(user: Misskey.entities.UserDetailed): boolean {
if ($i && $i.id === user.id) return true;
if (user.followersVisibility === 'private') return false;
if (user.followersVisibility === 'followers' && !user.isFollowing) return false;
return true;
}

View file

@ -0,0 +1,61 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Ref, nextTick } from 'vue';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { MFM_TAGS } from '@/const.js';
/**
* MFMの装飾のリストを表示する
*/
export function mfmFunctionPicker(src: any, textArea: HTMLInputElement | HTMLTextAreaElement, textRef: Ref<string>) {
return new Promise((res, rej) => {
os.popupMenu([{
text: i18n.ts.addMfmFunction,
type: 'label',
}, ...getFunctionList(textArea, textRef)], src);
});
}
function getFunctionList(textArea: HTMLInputElement | HTMLTextAreaElement, textRef: Ref<string>) : object[] {
const ret: object[] = [];
MFM_TAGS.forEach(tag => {
ret.push({
text: tag,
icon: 'ti ti-icons',
action: () => add(textArea, textRef, tag),
});
});
return ret;
}
function add(textArea: HTMLInputElement | HTMLTextAreaElement, textRef: Ref<string>, type: string) {
const caretStart: number = textArea.selectionStart as number;
const caretEnd: number = textArea.selectionEnd as number;
MFM_TAGS.forEach(tag => {
if (type === tag) {
if (caretStart === caretEnd) {
// 単純にFunctionを追加
const trimmedText = `${textRef.value.substring(0, caretStart)}$[${type} ]${textRef.value.substring(caretEnd)}`;
textRef.value = trimmedText;
} else {
// 選択範囲を囲むようにFunctionを追加
const trimmedText = `${textRef.value.substring(0, caretStart)}$[${type} ${textRef.value.substring(caretStart, caretEnd)}]${textRef.value.substring(caretEnd)}`;
textRef.value = trimmedText;
}
}
});
const nextCaretStart: number = caretStart + 3 + type.length;
const nextCaretEnd: number = caretEnd + 3 + type.length;
// キャレットを戻す
nextTick(() => {
textArea.focus();
textArea.setSelectionRange(nextCaretStart, nextCaretEnd);
});
}

View file

@ -5,6 +5,7 @@
import { defineAsyncComponent, Ref, ref } from 'vue';
import { popup } from '@/os.js';
import { defaultStore } from '@/store.js';
class ReactionPicker {
private src: Ref<HTMLElement | null> = ref(null);
@ -17,25 +18,27 @@ class ReactionPicker {
}
public async init() {
const reactionsRef = defaultStore.reactiveState.reactions;
await popup(defineAsyncComponent(() => import('@/components/MkEmojiPickerDialog.vue')), {
src: this.src,
pinnedEmojis: reactionsRef,
asReactionPicker: true,
manualShowing: this.manualShowing,
}, {
done: reaction => {
this.onChosen!(reaction);
if (this.onChosen) this.onChosen(reaction);
},
close: () => {
this.manualShowing.value = false;
},
closed: () => {
this.src.value = null;
this.onClosed!();
if (this.onClosed) this.onClosed();
},
});
}
public show(src: HTMLElement, onChosen: ReactionPicker['onChosen'], onClosed: ReactionPicker['onClosed']) {
public show(src: HTMLElement, onChosen?: ReactionPicker['onChosen'], onClosed?: ReactionPicker['onClosed']) {
this.src.value = src;
this.manualShowing.value = true;
this.onChosen = onChosen;

File diff suppressed because one or more lines are too long

View file

@ -4,7 +4,7 @@
*/
import isAnimated from 'is-file-animated';
import { isWebpSupported } from './isWebpSupported';
import { isWebpSupported } from './isWebpSupported.js';
import type { BrowserImageResizerConfig } from 'browser-image-resizer';
const compressTypeMap = {

View file

@ -11,8 +11,12 @@ export function useChartTooltip(opts: { position: 'top' | 'middle' } = { positio
const tooltipShowing = ref(false);
const tooltipX = ref(0);
const tooltipY = ref(0);
const tooltipTitle = ref(null);
const tooltipSeries = ref(null);
const tooltipTitle = ref<string | null>(null);
const tooltipSeries = ref<{
backgroundColor: string;
borderColor: string;
text: string;
}[] | null>(null);
let disposeTooltipComponent;
os.popup(MkChartTooltip, {