Merge tag '2023.12.0-beta.5' into merge-upstream
This commit is contained in:
commit
00c9e43861
150 changed files with 4706 additions and 2076 deletions
|
|
@ -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('[', ''));
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { defineAsyncComponent, Ref, ref } from 'vue';
|
||||
import { defineAsyncComponent, Ref, ref, computed, ComputedRef } 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;
|
||||
|
|
|
|||
|
|
@ -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: () => {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -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,
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -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: () => {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
479
packages/frontend/src/scripts/snowfall-effect.ts
Normal file
479
packages/frontend/src/scripts/snowfall-effect.ts
Normal file
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue