summaryrefslogtreecommitdiff
path: root/packages/frontend/src/scripts
diff options
context:
space:
mode:
authorかっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>2024-06-01 11:27:03 +0900
committerGitHub <noreply@github.com>2024-06-01 11:27:03 +0900
commitfce66b85b603caac79e1bfa87b5f4621b1ba9d4e (patch)
treed22952ee3f8e30057977a99a33823f4d52990fbc /packages/frontend/src/scripts
parentMerge pull request #13493 from misskey-dev/develop (diff)
parentfix(backend): use insertOne insteadof insert/findOneOrFail combination (#13908) (diff)
downloadsharkey-fce66b85b603caac79e1bfa87b5f4621b1ba9d4e.tar.gz
sharkey-fce66b85b603caac79e1bfa87b5f4621b1ba9d4e.tar.bz2
sharkey-fce66b85b603caac79e1bfa87b5f4621b1ba9d4e.zip
Merge pull request #13917 from misskey-dev/develop
Release 2024.5.0 (master)
Diffstat (limited to 'packages/frontend/src/scripts')
-rw-r--r--packages/frontend/src/scripts/admin-lookup.ts (renamed from packages/frontend/src/scripts/lookup-user.ts)23
-rw-r--r--packages/frontend/src/scripts/aiscript/ui.ts62
-rw-r--r--packages/frontend/src/scripts/check-reaction-permissions.ts5
-rw-r--r--packages/frontend/src/scripts/clear-cache.ts5
-rw-r--r--packages/frontend/src/scripts/code-highlighter.ts23
-rw-r--r--packages/frontend/src/scripts/collapsed.ts19
-rw-r--r--packages/frontend/src/scripts/form.ts30
-rw-r--r--packages/frontend/src/scripts/get-note-menu.ts79
-rw-r--r--packages/frontend/src/scripts/get-user-menu.ts2
-rw-r--r--packages/frontend/src/scripts/idb-proxy.ts10
-rw-r--r--packages/frontend/src/scripts/keycode.ts1
-rw-r--r--packages/frontend/src/scripts/media-has-audio.ts5
-rw-r--r--packages/frontend/src/scripts/popup-position.ts38
-rw-r--r--packages/frontend/src/scripts/snowfall-effect.ts4
-rw-r--r--packages/frontend/src/scripts/theme.ts4
-rw-r--r--packages/frontend/src/scripts/upload.ts10
-rw-r--r--packages/frontend/src/scripts/use-chart-tooltip.ts6
-rw-r--r--packages/frontend/src/scripts/use-note-capture.ts2
18 files changed, 233 insertions, 95 deletions
diff --git a/packages/frontend/src/scripts/lookup-user.ts b/packages/frontend/src/scripts/admin-lookup.ts
index efc9132e75..1b57b853c9 100644
--- a/packages/frontend/src/scripts/lookup-user.ts
+++ b/packages/frontend/src/scripts/admin-lookup.ts
@@ -63,3 +63,26 @@ export async function lookupUserByEmail() {
}
}
}
+
+export async function lookupFile() {
+ const { canceled, result: q } = await os.inputText({
+ title: i18n.ts.fileIdOrUrl,
+ minLength: 1,
+ });
+ if (canceled) return;
+
+ const show = (file) => {
+ os.pageWindow(`/admin/file/${file.id}`);
+ };
+
+ misskeyApi('admin/drive/show-file', q.startsWith('http://') || q.startsWith('https://') ? { url: q.trim() } : { fileId: q.trim() }).then(file => {
+ show(file);
+ }).catch(err => {
+ if (err.code === 'NO_SUCH_FILE') {
+ os.alert({
+ type: 'error',
+ text: i18n.ts.notFound,
+ });
+ }
+ });
+}
diff --git a/packages/frontend/src/scripts/aiscript/ui.ts b/packages/frontend/src/scripts/aiscript/ui.ts
index f2493264d3..fa3fcac2e7 100644
--- a/packages/frontend/src/scripts/aiscript/ui.ts
+++ b/packages/frontend/src/scripts/aiscript/ui.ts
@@ -6,6 +6,7 @@
import { utils, values } from '@syuilo/aiscript';
import { v4 as uuid } from 'uuid';
import { ref, Ref } from 'vue';
+import * as Misskey from 'misskey-js';
export type AsUiComponentBase = {
id: string;
@@ -115,23 +116,24 @@ export type AsUiFolder = AsUiComponentBase & {
opened?: boolean;
};
+type PostFormPropsForAsUi = {
+ text: string;
+ cw?: string;
+ visibility?: (typeof Misskey.noteVisibilities)[number];
+ localOnly?: boolean;
+};
+
export type AsUiPostFormButton = AsUiComponentBase & {
type: 'postFormButton';
text?: string;
primary?: boolean;
rounded?: boolean;
- form?: {
- text: string;
- cw?: string;
- };
+ form?: PostFormPropsForAsUi;
};
export type AsUiPostForm = AsUiComponentBase & {
type: 'postForm';
- form?: {
- text: string;
- cw?: string;
- };
+ form?: PostFormPropsForAsUi;
};
export type AsUiComponent = AsUiRoot | AsUiContainer | AsUiText | AsUiMfm | AsUiButton | AsUiButtons | AsUiSwitch | AsUiTextarea | AsUiTextInput | AsUiNumberInput | AsUiSelect | AsUiFolder | AsUiPostFormButton | AsUiPostForm;
@@ -447,6 +449,24 @@ function getFolderOptions(def: values.Value | undefined): Omit<AsUiFolder, 'id'
};
}
+function getPostFormProps(form: values.VObj): PostFormPropsForAsUi {
+ const text = form.value.get('text');
+ utils.assertString(text);
+ const cw = form.value.get('cw');
+ if (cw) utils.assertString(cw);
+ const visibility = form.value.get('visibility');
+ if (visibility) utils.assertString(visibility);
+ const localOnly = form.value.get('localOnly');
+ if (localOnly) utils.assertBoolean(localOnly);
+
+ return {
+ text: text.value,
+ cw: cw?.value,
+ visibility: (visibility?.value && (Misskey.noteVisibilities as readonly string[]).includes(visibility.value)) ? visibility.value as typeof Misskey.noteVisibilities[number] : undefined,
+ localOnly: localOnly?.value,
+ };
+}
+
function getPostFormButtonOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise<values.Value>): Omit<AsUiPostFormButton, 'id' | 'type'> {
utils.assertObject(def);
@@ -459,22 +479,11 @@ function getPostFormButtonOptions(def: values.Value | undefined, call: (fn: valu
const form = def.value.get('form');
if (form) utils.assertObject(form);
- 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,
- };
- };
-
return {
text: text?.value,
primary: primary?.value,
rounded: rounded?.value,
- form: form ? getForm() : {
+ form: form ? getPostFormProps(form) : {
text: '',
},
};
@@ -486,19 +495,8 @@ function getPostFormOptions(def: values.Value | undefined, call: (fn: values.VFn
const form = def.value.get('form');
if (form) utils.assertObject(form);
- 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,
- };
- };
-
return {
- form: form ? getForm() : {
+ form: form ? getPostFormProps(form) : {
text: '',
},
};
diff --git a/packages/frontend/src/scripts/check-reaction-permissions.ts b/packages/frontend/src/scripts/check-reaction-permissions.ts
index e7b473dd75..8fc857f84f 100644
--- a/packages/frontend/src/scripts/check-reaction-permissions.ts
+++ b/packages/frontend/src/scripts/check-reaction-permissions.ts
@@ -1,3 +1,8 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
import * as Misskey from 'misskey-js';
import { UnicodeEmojiDef } from './emojilist.js';
diff --git a/packages/frontend/src/scripts/clear-cache.ts b/packages/frontend/src/scripts/clear-cache.ts
index b20109ec72..71d1232710 100644
--- a/packages/frontend/src/scripts/clear-cache.ts
+++ b/packages/frontend/src/scripts/clear-cache.ts
@@ -1,3 +1,8 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
import { unisonReload } from '@/scripts/unison-reload.js';
import * as os from '@/os.js';
import { miLocalStorage } from '@/local-storage.js';
diff --git a/packages/frontend/src/scripts/code-highlighter.ts b/packages/frontend/src/scripts/code-highlighter.ts
index 2733897bab..e94027d302 100644
--- a/packages/frontend/src/scripts/code-highlighter.ts
+++ b/packages/frontend/src/scripts/code-highlighter.ts
@@ -1,15 +1,21 @@
-import { bundledThemesInfo } from 'shiki';
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
import { getHighlighterCore, loadWasm } from 'shiki/core';
import darkPlus from 'shiki/themes/dark-plus.mjs';
+import { bundledThemesInfo } from 'shiki/themes';
+import { bundledLanguagesInfo } from 'shiki/langs';
import { unique } from './array.js';
import { deepClone } from './clone.js';
import { deepMerge } from './merge.js';
-import type { Highlighter, LanguageRegistration, ThemeRegistration, ThemeRegistrationRaw } from 'shiki';
+import type { HighlighterCore, LanguageRegistration, ThemeRegistration, ThemeRegistrationRaw } from 'shiki/core';
import { ColdDeviceStorage } from '@/store.js';
import lightTheme from '@/themes/_light.json5';
import darkTheme from '@/themes/_dark.json5';
-let _highlighter: Highlighter | null = null;
+let _highlighter: HighlighterCore | null = null;
export async function getTheme(mode: 'light' | 'dark', getName: true): Promise<string>;
export async function getTheme(mode: 'light' | 'dark', getName?: false): Promise<ThemeRegistration | ThemeRegistrationRaw>;
@@ -46,16 +52,14 @@ export async function getTheme(mode: 'light' | 'dark', getName = false): Promise
return darkPlus;
}
-export async function getHighlighter(): Promise<Highlighter> {
+export async function getHighlighter(): Promise<HighlighterCore> {
if (!_highlighter) {
return await initHighlighter();
}
return _highlighter;
}
-export async function initHighlighter() {
- const aiScriptGrammar = await import('aiscript-vscode/aiscript/syntaxes/aiscript.tmLanguage.json');
-
+async function initHighlighter() {
await loadWasm(import('shiki/onig.wasm?init'));
// テーマの重複を消す
@@ -64,11 +68,12 @@ export async function initHighlighter() {
...(await Promise.all([getTheme('light'), getTheme('dark')])),
]);
+ const jsLangInfo = bundledLanguagesInfo.find(t => t.id === 'javascript');
const highlighter = await getHighlighterCore({
themes,
langs: [
- import('shiki/langs/javascript.mjs'),
- aiScriptGrammar.default as unknown as LanguageRegistration,
+ ...(jsLangInfo ? [async () => await jsLangInfo.import()] : []),
+ async () => (await import('aiscript-vscode/aiscript/syntaxes/aiscript.tmLanguage.json')).default as unknown as LanguageRegistration,
],
});
diff --git a/packages/frontend/src/scripts/collapsed.ts b/packages/frontend/src/scripts/collapsed.ts
index 237bd37c7a..4ec88a3c65 100644
--- a/packages/frontend/src/scripts/collapsed.ts
+++ b/packages/frontend/src/scripts/collapsed.ts
@@ -6,15 +6,16 @@
import * as Misskey from 'misskey-js';
export function shouldCollapsed(note: Misskey.entities.Note, urls: string[]): boolean {
- const collapsed = note.cw == null && note.text != null && (
- (note.text.includes('$[x2')) ||
- (note.text.includes('$[x3')) ||
- (note.text.includes('$[x4')) ||
- (note.text.includes('$[scale')) ||
- (note.text.split('\n').length > 9) ||
- (note.text.length > 500) ||
- (note.files.length >= 5) ||
- (urls.length >= 4)
+ const collapsed = note.cw == null && (
+ note.text != null && (
+ (note.text.includes('$[x2')) ||
+ (note.text.includes('$[x3')) ||
+ (note.text.includes('$[x4')) ||
+ (note.text.includes('$[scale')) ||
+ (note.text.split('\n').length > 9) ||
+ (note.text.length > 500) ||
+ (urls.length >= 4)
+ ) || note.files.length >= 5
);
return collapsed;
diff --git a/packages/frontend/src/scripts/form.ts b/packages/frontend/src/scripts/form.ts
index b0db404f28..242a504c3b 100644
--- a/packages/frontend/src/scripts/form.ts
+++ b/packages/frontend/src/scripts/form.ts
@@ -3,18 +3,22 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
+import * as Misskey from 'misskey-js';
+
type EnumItem = string | {
label: string;
value: string;
};
+type Hidden = boolean | ((v: any) => boolean);
+
export type FormItem = {
label?: string;
type: 'string';
default: string | null;
description?: string;
required?: boolean;
- hidden?: boolean;
+ hidden?: Hidden;
multiline?: boolean;
treatAsMfm?: boolean;
} | {
@@ -23,27 +27,27 @@ export type FormItem = {
default: number | null;
description?: string;
required?: boolean;
- hidden?: boolean;
+ hidden?: Hidden;
step?: number;
} | {
label?: string;
type: 'boolean';
default: boolean | null;
description?: string;
- hidden?: boolean;
+ hidden?: Hidden;
} | {
label?: string;
type: 'enum';
default: string | null;
required?: boolean;
- hidden?: boolean;
+ hidden?: Hidden;
enum: EnumItem[];
} | {
label?: string;
type: 'radio';
default: unknown | null;
required?: boolean;
- hidden?: boolean;
+ hidden?: Hidden;
options: {
label: string;
value: unknown;
@@ -58,20 +62,27 @@ export type FormItem = {
min: number;
max: number;
textConverter?: (value: number) => string;
+ hidden?: Hidden;
} | {
label?: string;
type: 'object';
default: Record<string, unknown> | null;
- hidden: boolean;
+ hidden: Hidden;
} | {
label?: string;
type: 'array';
default: unknown[] | null;
- hidden: boolean;
+ hidden: Hidden;
} | {
type: 'button';
content?: string;
+ hidden?: Hidden;
action: (ev: MouseEvent, v: any) => void;
+} | {
+ type: 'drive-file';
+ defaultFileId?: string | null;
+ hidden?: Hidden;
+ validate?: (v: Misskey.entities.DriveFile) => Promise<boolean>;
};
export type Form = Record<string, FormItem>;
@@ -84,8 +95,9 @@ type GetItemType<Item extends FormItem> =
Item['type'] extends 'range' ? number :
Item['type'] extends 'enum' ? string :
Item['type'] extends 'array' ? unknown[] :
- Item['type'] extends 'object' ? Record<string, unknown>
- : never;
+ Item['type'] extends 'object' ? Record<string, unknown> :
+ Item['type'] extends 'drive-file' ? Misskey.entities.DriveFile | undefined :
+ never;
export type GetFormResultType<F extends Form> = {
[P in keyof F]: GetItemType<F[P]>;
diff --git a/packages/frontend/src/scripts/get-note-menu.ts b/packages/frontend/src/scripts/get-note-menu.ts
index b273bd36f3..71ad299f50 100644
--- a/packages/frontend/src/scripts/get-note-menu.ts
+++ b/packages/frontend/src/scripts/get-note-menu.ts
@@ -16,7 +16,7 @@ import { url } from '@/config.js';
import { defaultStore, noteActions } from '@/store.js';
import { miLocalStorage } from '@/local-storage.js';
import { getUserMenu } from '@/scripts/get-user-menu.js';
-import { clipsCache } from '@/cache.js';
+import { clipsCache, favoritedChannelsCache } from '@/cache.js';
import { MenuItem } from '@/types/menu.js';
import MkRippleEffect from '@/components/MkRippleEffect.vue';
import { isSupportShare } from '@/scripts/navigator.js';
@@ -26,6 +26,14 @@ export async function getNoteClipMenu(props: {
isDeleted: Ref<boolean>;
currentClip?: Misskey.entities.Clip;
}) {
+ function getClipName(clip: Misskey.entities.Clip) {
+ if ($i && clip.userId === $i.id && clip.notesCount != null) {
+ return `${clip.name} (${clip.notesCount}/${$i.policies.noteEachClipsLimit})`;
+ } else {
+ return clip.name;
+ }
+ }
+
const isRenote = (
props.note.renote != null &&
props.note.text == null &&
@@ -37,7 +45,7 @@ export async function getNoteClipMenu(props: {
const clips = await clipsCache.fetch();
const menu: MenuItem[] = [...clips.map(clip => ({
- text: clip.name,
+ text: getClipName(clip),
action: () => {
claimAchievement('noteClipped1');
os.promiseDialog(
@@ -50,7 +58,18 @@ export async function getNoteClipMenu(props: {
text: i18n.tsx.confirmToUnclipAlreadyClippedNote({ name: clip.name }),
});
if (!confirm.canceled) {
- os.apiWithDialog('clips/remove-note', { clipId: clip.id, noteId: appearNote.id });
+ os.apiWithDialog('clips/remove-note', { clipId: clip.id, noteId: appearNote.id }).then(() => {
+ clipsCache.set(clips.map(c => {
+ if (c.id === clip.id) {
+ return {
+ ...c,
+ notesCount: Math.max(0, ((c.notesCount ?? 0) - 1)),
+ };
+ } else {
+ return c;
+ }
+ }));
+ });
if (props.currentClip?.id === clip.id) props.isDeleted.value = true;
}
} else {
@@ -60,7 +79,18 @@ export async function getNoteClipMenu(props: {
});
}
},
- );
+ ).then(() => {
+ clipsCache.set(clips.map(c => {
+ if (c.id === clip.id) {
+ return {
+ ...c,
+ notesCount: (c.notesCount ?? 0) + 1,
+ };
+ } else {
+ return c;
+ }
+ }));
+ });
},
})), { type: 'divider' }, {
icon: 'ti ti-plus',
@@ -462,10 +492,9 @@ export function getNoteMenu(props: {
};
}
-type Visibility = 'public' | 'home' | 'followers' | 'specified';
+type Visibility = (typeof Misskey.noteVisibilities)[number];
-// defaultStore.state.visibilityがstringなためstringも受け付けている
-function smallerVisibility(a: Visibility | string, b: Visibility | string): Visibility {
+function smallerVisibility(a: Visibility, b: Visibility): Visibility {
if (a === 'specified' || b === 'specified') return 'specified';
if (a === 'followers' || b === 'followers') return 'followers';
if (a === 'home' || b === 'home') return 'home';
@@ -489,6 +518,7 @@ export function getRenoteMenu(props: {
const channelRenoteItems: MenuItem[] = [];
const normalRenoteItems: MenuItem[] = [];
+ const normalExternalChannelRenoteItems: MenuItem[] = [];
if (appearNote.channel) {
channelRenoteItems.push(...[{
@@ -567,12 +597,47 @@ export function getRenoteMenu(props: {
});
},
}]);
+
+ normalExternalChannelRenoteItems.push({
+ type: 'parent',
+ icon: 'ti ti-repeat',
+ text: appearNote.channel ? i18n.ts.renoteToOtherChannel : i18n.ts.renoteToChannel,
+ children: async () => {
+ const channels = await favoritedChannelsCache.fetch();
+ return channels.filter((channel) => {
+ if (!appearNote.channelId) return true;
+ return channel.id !== appearNote.channelId;
+ }).map((channel) => ({
+ text: channel.name,
+ action: () => {
+ const el = props.renoteButton.value;
+ if (el) {
+ const rect = el.getBoundingClientRect();
+ const x = rect.left + (el.offsetWidth / 2);
+ const y = rect.top + (el.offsetHeight / 2);
+ os.popup(MkRippleEffect, { x, y }, {}, 'end');
+ }
+
+ if (!props.mock) {
+ misskeyApi('notes/create', {
+ renoteId: appearNote.id,
+ channelId: channel.id,
+ }).then(() => {
+ os.toast(i18n.tsx.renotedToX({ name: channel.name }));
+ });
+ }
+ },
+ }));
+ },
+ });
}
const renoteItems = [
...normalRenoteItems,
...(channelRenoteItems.length > 0 && normalRenoteItems.length > 0) ? [{ type: 'divider' }] as MenuItem[] : [],
...channelRenoteItems,
+ ...(normalExternalChannelRenoteItems.length > 0 && (normalRenoteItems.length > 0 || channelRenoteItems.length > 0)) ? [{ type: 'divider' }] as MenuItem[] : [],
+ ...normalExternalChannelRenoteItems,
];
return {
diff --git a/packages/frontend/src/scripts/get-user-menu.ts b/packages/frontend/src/scripts/get-user-menu.ts
index c14f75f382..3e031d232f 100644
--- a/packages/frontend/src/scripts/get-user-menu.ts
+++ b/packages/frontend/src/scripts/get-user-menu.ts
@@ -272,7 +272,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter
text: r.name,
action: async () => {
const { canceled, result: period } = await os.select({
- title: i18n.ts.period,
+ title: i18n.ts.period + ': ' + r.name,
items: [{
value: 'indefinitely', text: i18n.ts.indefinitely,
}, {
diff --git a/packages/frontend/src/scripts/idb-proxy.ts b/packages/frontend/src/scripts/idb-proxy.ts
index 1ca0990ba9..6b511f2a5f 100644
--- a/packages/frontend/src/scripts/idb-proxy.ts
+++ b/packages/frontend/src/scripts/idb-proxy.ts
@@ -15,6 +15,16 @@ const fallbackName = (key: string) => `idbfallback::${key}`;
let idbAvailable = typeof window !== 'undefined' ? !!(window.indexedDB && window.indexedDB.open) : true;
+// iframe.contentWindow.indexedDB.deleteDatabase() がchromeのバグで使用できないため、indexedDBを無効化している。
+// バグが治って再度有効化するのであれば、cypressのコマンド内のコメントアウトを外すこと
+// see https://github.com/misskey-dev/misskey/issues/13605#issuecomment-2053652123
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-expect-error
+if (window.Cypress) {
+ idbAvailable = false;
+ console.log('Cypress detected. It will use localStorage.');
+}
+
if (idbAvailable) {
await iset('idb-test', 'test')
.catch(err => {
diff --git a/packages/frontend/src/scripts/keycode.ts b/packages/frontend/src/scripts/keycode.ts
index bc1f485f5e..7ffceafada 100644
--- a/packages/frontend/src/scripts/keycode.ts
+++ b/packages/frontend/src/scripts/keycode.ts
@@ -15,6 +15,7 @@ export default (input: string): string[] => {
export const aliases = {
'esc': 'Escape',
'enter': ['Enter', 'NumpadEnter'],
+ 'space': [' ', 'Spacebar'],
'up': 'ArrowUp',
'down': 'ArrowDown',
'left': 'ArrowLeft',
diff --git a/packages/frontend/src/scripts/media-has-audio.ts b/packages/frontend/src/scripts/media-has-audio.ts
index 3421a38a76..4bf3ee5d97 100644
--- a/packages/frontend/src/scripts/media-has-audio.ts
+++ b/packages/frontend/src/scripts/media-has-audio.ts
@@ -1,3 +1,8 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
export default async function hasAudio(media: HTMLMediaElement) {
const cloned = media.cloneNode() as HTMLMediaElement;
cloned.muted = (cloned as typeof cloned & Partial<HTMLVideoElement>).playsInline = true;
diff --git a/packages/frontend/src/scripts/popup-position.ts b/packages/frontend/src/scripts/popup-position.ts
index 8c9e3c02c3..3dad41a8b3 100644
--- a/packages/frontend/src/scripts/popup-position.ts
+++ b/packages/frontend/src/scripts/popup-position.ts
@@ -26,8 +26,8 @@ export function calcPopupPosition(el: HTMLElement, props: {
let top: number;
if (props.anchorElement) {
- left = rect.left + window.pageXOffset + (props.anchorElement.offsetWidth / 2);
- top = (rect.top + window.pageYOffset - contentHeight) - props.innerMargin;
+ left = rect.left + window.scrollX + (props.anchorElement.offsetWidth / 2);
+ top = (rect.top + window.scrollY - contentHeight) - props.innerMargin;
} else {
left = props.x;
top = (props.y - contentHeight) - props.innerMargin;
@@ -35,8 +35,8 @@ export function calcPopupPosition(el: HTMLElement, props: {
left -= (el.offsetWidth / 2);
- if (left + contentWidth - window.pageXOffset > window.innerWidth) {
- left = window.innerWidth - contentWidth + window.pageXOffset - 1;
+ if (left + contentWidth - window.scrollX > window.innerWidth) {
+ left = window.innerWidth - contentWidth + window.scrollX - 1;
}
return [left, top];
@@ -47,8 +47,8 @@ export function calcPopupPosition(el: HTMLElement, props: {
let top: number;
if (props.anchorElement) {
- left = rect.left + window.pageXOffset + (props.anchorElement.offsetWidth / 2);
- top = (rect.top + window.pageYOffset + props.anchorElement.offsetHeight) + props.innerMargin;
+ left = rect.left + window.scrollX + (props.anchorElement.offsetWidth / 2);
+ top = (rect.top + window.scrollY + props.anchorElement.offsetHeight) + props.innerMargin;
} else {
left = props.x;
top = (props.y) + props.innerMargin;
@@ -56,8 +56,8 @@ export function calcPopupPosition(el: HTMLElement, props: {
left -= (el.offsetWidth / 2);
- if (left + contentWidth - window.pageXOffset > window.innerWidth) {
- left = window.innerWidth - contentWidth + window.pageXOffset - 1;
+ if (left + contentWidth - window.scrollX > window.innerWidth) {
+ left = window.innerWidth - contentWidth + window.scrollX - 1;
}
return [left, top];
@@ -68,8 +68,8 @@ export function calcPopupPosition(el: HTMLElement, props: {
let top: number;
if (props.anchorElement) {
- left = (rect.left + window.pageXOffset - contentWidth) - props.innerMargin;
- top = rect.top + window.pageYOffset + (props.anchorElement.offsetHeight / 2);
+ left = (rect.left + window.scrollX - contentWidth) - props.innerMargin;
+ top = rect.top + window.scrollY + (props.anchorElement.offsetHeight / 2);
} else {
left = (props.x - contentWidth) - props.innerMargin;
top = props.y;
@@ -77,8 +77,8 @@ export function calcPopupPosition(el: HTMLElement, props: {
top -= (el.offsetHeight / 2);
- if (top + contentHeight - window.pageYOffset > window.innerHeight) {
- top = window.innerHeight - contentHeight + window.pageYOffset - 1;
+ if (top + contentHeight - window.scrollY > window.innerHeight) {
+ top = window.innerHeight - contentHeight + window.scrollY - 1;
}
return [left, top];
@@ -89,15 +89,15 @@ export function calcPopupPosition(el: HTMLElement, props: {
let top: number;
if (props.anchorElement) {
- left = (rect.left + props.anchorElement.offsetWidth + window.pageXOffset) + props.innerMargin;
+ left = (rect.left + props.anchorElement.offsetWidth + window.scrollX) + props.innerMargin;
if (props.align === 'top') {
- top = rect.top + window.pageYOffset;
+ top = rect.top + window.scrollY;
if (props.alignOffset != null) top += props.alignOffset;
} else if (props.align === 'bottom') {
// TODO
} else { // center
- top = rect.top + window.pageYOffset + (props.anchorElement.offsetHeight / 2);
+ top = rect.top + window.scrollY + (props.anchorElement.offsetHeight / 2);
top -= (el.offsetHeight / 2);
}
} else {
@@ -106,8 +106,8 @@ export function calcPopupPosition(el: HTMLElement, props: {
top -= (el.offsetHeight / 2);
}
- if (top + contentHeight - window.pageYOffset > window.innerHeight) {
- top = window.innerHeight - contentHeight + window.pageYOffset - 1;
+ if (top + contentHeight - window.scrollY > window.innerHeight) {
+ top = window.innerHeight - contentHeight + window.scrollY - 1;
}
return [left, top];
@@ -123,7 +123,7 @@ export function calcPopupPosition(el: HTMLElement, props: {
const [left, top] = calcPosWhenTop();
// ツールチップを上に向かって表示するスペースがなければ下に向かって出す
- if (top - window.pageYOffset < 0) {
+ if (top - window.scrollY < 0) {
const [left, top] = calcPosWhenBottom();
return { left, top, transformOrigin: 'center top' };
}
@@ -141,7 +141,7 @@ export function calcPopupPosition(el: HTMLElement, props: {
const [left, top] = calcPosWhenLeft();
// ツールチップを左に向かって表示するスペースがなければ右に向かって出す
- if (left - window.pageXOffset < 0) {
+ if (left - window.scrollX < 0) {
const [left, top] = calcPosWhenRight();
return { left, top, transformOrigin: 'left center' };
}
diff --git a/packages/frontend/src/scripts/snowfall-effect.ts b/packages/frontend/src/scripts/snowfall-effect.ts
index 11fcaa0716..d88bdb6660 100644
--- a/packages/frontend/src/scripts/snowfall-effect.ts
+++ b/packages/frontend/src/scripts/snowfall-effect.ts
@@ -155,7 +155,9 @@ export class SnowfallEffect {
max: 0.125,
easing: 0.0005,
};
-
+ /**
+ * @throws {Error} - Thrown when it fails to get WebGL context for the canvas
+ */
constructor(options: {
sakura?: boolean;
}) {
diff --git a/packages/frontend/src/scripts/theme.ts b/packages/frontend/src/scripts/theme.ts
index 5f7e88bd9f..c7f8b3d596 100644
--- a/packages/frontend/src/scripts/theme.ts
+++ b/packages/frontend/src/scripts/theme.ts
@@ -6,7 +6,7 @@
import { ref } from 'vue';
import tinycolor from 'tinycolor2';
import { deepClone } from './clone.js';
-import type { BuiltinTheme } from 'shiki';
+import type { BundledTheme } from 'shiki/themes';
import { globalEvents } from '@/events.js';
import lightTheme from '@/themes/_light.json5';
import darkTheme from '@/themes/_dark.json5';
@@ -20,7 +20,7 @@ export type Theme = {
base?: 'dark' | 'light';
props: Record<string, string>;
codeHighlighter?: {
- base: BuiltinTheme;
+ base: BundledTheme;
overrides?: Record<string, any>;
} | {
base: '_none_';
diff --git a/packages/frontend/src/scripts/upload.ts b/packages/frontend/src/scripts/upload.ts
index 6c46b2bc1b..3e947183c9 100644
--- a/packages/frontend/src/scripts/upload.ts
+++ b/packages/frontend/src/scripts/upload.ts
@@ -5,6 +5,7 @@
import { reactive, ref } from 'vue';
import * as Misskey from 'misskey-js';
+import { v4 as uuid } from 'uuid';
import { readAndCompressImage } from '@misskey-dev/browser-image-resizer';
import { getCompressionConfig } from './upload/compress-config.js';
import { defaultStore } from '@/store.js';
@@ -39,13 +40,16 @@ export function uploadFile(
if (folder && typeof folder === 'object') folder = folder.id;
return new Promise((resolve, reject) => {
- const id = Math.random().toString();
+ const id = uuid();
const reader = new FileReader();
reader.onload = async (): Promise<void> => {
+ const filename = name ?? file.name ?? 'untitled';
+ const extension = filename.split('.').length > 1 ? '.' + filename.split('.').pop() : '';
+
const ctx = reactive<Uploading>({
- id: id,
- name: name ?? file.name ?? 'untitled',
+ id,
+ name: defaultStore.state.keepOriginalFilename ? filename : id + extension,
progressMax: undefined,
progressValue: undefined,
img: window.URL.createObjectURL(file),
diff --git a/packages/frontend/src/scripts/use-chart-tooltip.ts b/packages/frontend/src/scripts/use-chart-tooltip.ts
index 7e4bf5c9c6..bed221a622 100644
--- a/packages/frontend/src/scripts/use-chart-tooltip.ts
+++ b/packages/frontend/src/scripts/use-chart-tooltip.ts
@@ -53,11 +53,11 @@ export function useChartTooltip(opts: { position: 'top' | 'middle' } = { positio
const rect = context.chart.canvas.getBoundingClientRect();
tooltipShowing.value = true;
- tooltipX.value = rect.left + window.pageXOffset + context.tooltip.caretX;
+ tooltipX.value = rect.left + window.scrollX + context.tooltip.caretX;
if (opts.position === 'top') {
- tooltipY.value = rect.top + window.pageYOffset;
+ tooltipY.value = rect.top + window.scrollY;
} else if (opts.position === 'middle') {
- tooltipY.value = rect.top + window.pageYOffset + context.tooltip.caretY;
+ tooltipY.value = rect.top + window.scrollY + context.tooltip.caretY;
}
}
diff --git a/packages/frontend/src/scripts/use-note-capture.ts b/packages/frontend/src/scripts/use-note-capture.ts
index 524ac5d3fe..542d8ab52b 100644
--- a/packages/frontend/src/scripts/use-note-capture.ts
+++ b/packages/frontend/src/scripts/use-note-capture.ts
@@ -35,6 +35,7 @@ export function useNoteCapture(props: {
const currentCount = (note.value.reactions || {})[reaction] || 0;
note.value.reactions[reaction] = currentCount + 1;
+ note.value.reactionCount += 1;
if ($i && (body.userId === $i.id)) {
note.value.myReaction = reaction;
@@ -49,6 +50,7 @@ export function useNoteCapture(props: {
const currentCount = (note.value.reactions || {})[reaction] || 0;
note.value.reactions[reaction] = Math.max(0, currentCount - 1);
+ note.value.reactionCount = Math.max(0, note.value.reactionCount - 1);
if (note.value.reactions[reaction] === 0) delete note.value.reactions[reaction];
if ($i && (body.userId === $i.id)) {