summaryrefslogtreecommitdiff
path: root/packages/client/src/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/client/src/scripts')
-rw-r--r--packages/client/src/scripts/array.ts2
-rw-r--r--packages/client/src/scripts/autocomplete.ts2
-rw-r--r--packages/client/src/scripts/check-word-mute.ts8
-rw-r--r--packages/client/src/scripts/gen-search-query.ts1
-rw-r--r--packages/client/src/scripts/get-note-menu.ts270
-rw-r--r--packages/client/src/scripts/get-user-menu.ts58
-rw-r--r--packages/client/src/scripts/hotkey.ts8
-rw-r--r--packages/client/src/scripts/hpml/evaluator.ts1
-rw-r--r--packages/client/src/scripts/hpml/index.ts14
-rw-r--r--packages/client/src/scripts/hpml/lib.ts81
-rw-r--r--packages/client/src/scripts/i18n.ts4
-rw-r--r--packages/client/src/scripts/navigate.ts34
-rw-r--r--packages/client/src/scripts/page-metadata.ts41
-rw-r--r--packages/client/src/scripts/please-login.ts2
-rw-r--r--packages/client/src/scripts/scroll.ts13
-rw-r--r--packages/client/src/scripts/search.ts16
-rw-r--r--packages/client/src/scripts/select-file.ts23
-rw-r--r--packages/client/src/scripts/twemoji-base.ts11
-rw-r--r--packages/client/src/scripts/upload.ts44
-rw-r--r--packages/client/src/scripts/url.ts2
-rw-r--r--packages/client/src/scripts/use-chart-tooltip.ts50
-rw-r--r--packages/client/src/scripts/use-interval.ts24
-rw-r--r--packages/client/src/scripts/use-leave-guard.ts3
-rw-r--r--packages/client/src/scripts/use-tooltip.ts5
24 files changed, 438 insertions, 279 deletions
diff --git a/packages/client/src/scripts/array.ts b/packages/client/src/scripts/array.ts
index 29d027de14..26c6195d66 100644
--- a/packages/client/src/scripts/array.ts
+++ b/packages/client/src/scripts/array.ts
@@ -98,7 +98,7 @@ export function groupOn<T, S>(f: (x: T) => S, xs: T[]): T[][] {
export function groupByX<T>(collections: T[], keySelector: (x: T) => string) {
return collections.reduce((obj: Record<string, T[]>, item: T) => {
const key = keySelector(item);
- if (!obj.hasOwnProperty(key)) {
+ if (typeof obj[key] === 'undefined') {
obj[key] = [];
}
diff --git a/packages/client/src/scripts/autocomplete.ts b/packages/client/src/scripts/autocomplete.ts
index 8d9bdee8f5..3ef6224175 100644
--- a/packages/client/src/scripts/autocomplete.ts
+++ b/packages/client/src/scripts/autocomplete.ts
@@ -8,7 +8,7 @@ export class Autocomplete {
x: Ref<number>;
y: Ref<number>;
q: Ref<string | null>;
- close: Function;
+ close: () => void;
} | null;
private textarea: HTMLInputElement | HTMLTextAreaElement;
private currentType: string;
diff --git a/packages/client/src/scripts/check-word-mute.ts b/packages/client/src/scripts/check-word-mute.ts
index fa74c09939..35d40a6e08 100644
--- a/packages/client/src/scripts/check-word-mute.ts
+++ b/packages/client/src/scripts/check-word-mute.ts
@@ -3,7 +3,9 @@ export function checkWordMute(note: Record<string, any>, me: Record<string, any>
if (me && (note.userId === me.id)) return false;
if (mutedWords.length > 0) {
- if (note.text == null) return false;
+ const text = ((note.cw ?? '') + '\n' + (note.text ?? '')).trim();
+
+ if (text === '') return false;
const matched = mutedWords.some(filter => {
if (Array.isArray(filter)) {
@@ -11,7 +13,7 @@ export function checkWordMute(note: Record<string, any>, me: Record<string, any>
const filteredFilter = filter.filter(keyword => keyword !== '');
if (filteredFilter.length === 0) return false;
- return filteredFilter.every(keyword => note.text!.includes(keyword));
+ return filteredFilter.every(keyword => text.includes(keyword));
} else {
// represents RegExp
const regexp = filter.match(/^\/(.+)\/(.*)$/);
@@ -20,7 +22,7 @@ export function checkWordMute(note: Record<string, any>, me: Record<string, any>
if (!regexp) return false;
try {
- return new RegExp(regexp[1], regexp[2]).test(note.text!);
+ return new RegExp(regexp[1], regexp[2]).test(text);
} catch (err) {
// This should never happen due to input sanitisation.
return false;
diff --git a/packages/client/src/scripts/gen-search-query.ts b/packages/client/src/scripts/gen-search-query.ts
index 57a06c280c..b413cbbab1 100644
--- a/packages/client/src/scripts/gen-search-query.ts
+++ b/packages/client/src/scripts/gen-search-query.ts
@@ -21,7 +21,6 @@ export async function genSearchQuery(v: any, q: string) {
}
}
}
-
}
return {
query: q.split(' ').filter(x => !x.startsWith('/') && !x.startsWith('@')).join(' '),
diff --git a/packages/client/src/scripts/get-note-menu.ts b/packages/client/src/scripts/get-note-menu.ts
index 78749ad6bb..632143f514 100644
--- a/packages/client/src/scripts/get-note-menu.ts
+++ b/packages/client/src/scripts/get-note-menu.ts
@@ -1,5 +1,6 @@
-import { defineAsyncComponent, Ref } from 'vue';
+import { defineAsyncComponent, Ref, inject } from 'vue';
import * as misskey from 'misskey-js';
+import { pleaseLogin } from './please-login';
import { $i } from '@/account';
import { i18n } from '@/i18n';
import { instance } from '@/instance';
@@ -7,13 +8,14 @@ import * as os from '@/os';
import copyToClipboard from '@/scripts/copy-to-clipboard';
import { url } from '@/config';
import { noteActions } from '@/store';
-import { pleaseLogin } from './please-login';
export function getNoteMenu(props: {
note: misskey.entities.Note;
menuButton: Ref<HTMLElement>;
translation: Ref<any>;
translating: Ref<boolean>;
+ isDeleted: Ref<boolean>;
+ currentClipPage?: Ref<misskey.entities.Clip>;
}) {
const isRenote = (
props.note.renote != null &&
@@ -32,7 +34,7 @@ export function getNoteMenu(props: {
if (canceled) return;
os.api('notes/delete', {
- noteId: appearNote.id
+ noteId: appearNote.id,
});
});
}
@@ -45,7 +47,7 @@ export function getNoteMenu(props: {
if (canceled) return;
os.api('notes/delete', {
- noteId: appearNote.id
+ noteId: appearNote.id,
});
os.post({ initialNote: appearNote, renote: appearNote.renote, reply: appearNote.reply, channel: appearNote.channel });
@@ -54,19 +56,19 @@ export function getNoteMenu(props: {
function toggleFavorite(favorite: boolean): void {
os.apiWithDialog(favorite ? 'notes/favorites/create' : 'notes/favorites/delete', {
- noteId: appearNote.id
+ noteId: appearNote.id,
});
}
function toggleWatch(watch: boolean): void {
os.apiWithDialog(watch ? 'notes/watching/create' : 'notes/watching/delete', {
- noteId: appearNote.id
+ noteId: appearNote.id,
});
}
function toggleThreadMute(mute: boolean): void {
os.apiWithDialog(mute ? 'notes/thread-muting/create' : 'notes/thread-muting/delete', {
- noteId: appearNote.id
+ noteId: appearNote.id,
});
}
@@ -82,12 +84,12 @@ export function getNoteMenu(props: {
function togglePin(pin: boolean): void {
os.apiWithDialog(pin ? 'i/pin' : 'i/unpin', {
- noteId: appearNote.id
+ noteId: appearNote.id,
}, undefined, null, res => {
if (res.id === '72dab508-c64d-498f-8740-a8eec1ba385a') {
os.alert({
type: 'error',
- text: i18n.ts.pinLimitExceeded
+ text: i18n.ts.pinLimitExceeded,
});
}
});
@@ -102,35 +104,60 @@ export function getNoteMenu(props: {
const { canceled, result } = await os.form(i18n.ts.createNewClip, {
name: {
type: 'string',
- label: i18n.ts.name
+ label: i18n.ts.name,
},
description: {
type: 'string',
required: false,
multiline: true,
- label: i18n.ts.description
+ label: i18n.ts.description,
},
isPublic: {
type: 'boolean',
label: i18n.ts.public,
- default: false
- }
+ default: false,
+ },
});
if (canceled) return;
const clip = await os.apiWithDialog('clips/create', result);
os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: appearNote.id });
- }
+ },
}, null, ...clips.map(clip => ({
text: clip.name,
action: () => {
- os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: appearNote.id });
- }
+ os.promiseDialog(
+ os.api('clips/add-note', { clipId: clip.id, noteId: appearNote.id }),
+ null,
+ async (err) => {
+ if (err.id === '734806c4-542c-463a-9311-15c512803965') {
+ const confirm = await os.confirm({
+ type: 'warning',
+ text: i18n.t('confirmToUnclipAlreadyClippedNote', { name: clip.name }),
+ });
+ if (!confirm.canceled) {
+ os.apiWithDialog('clips/remove-note', { clipId: clip.id, noteId: appearNote.id });
+ if (props.currentClipPage?.value.id === clip.id) props.isDeleted.value = true;
+ }
+ } else {
+ os.alert({
+ type: 'error',
+ text: err.message + '\n' + err.id,
+ });
+ }
+ },
+ );
+ },
}))], props.menuButton.value, {
}).then(focus);
}
+ async function unclip(): Promise<void> {
+ os.apiWithDialog('clips/remove-note', { clipId: props.currentClipPage.value.id, noteId: appearNote.id });
+ props.isDeleted.value = true;
+ }
+
async function promote(): Promise<void> {
const { canceled, result: days } = await os.inputNumber({
title: i18n.ts.numberOfDays,
@@ -166,77 +193,86 @@ export function getNoteMenu(props: {
let menu;
if ($i) {
const statePromise = os.api('notes/state', {
- noteId: appearNote.id
+ noteId: appearNote.id,
});
- menu = [{
- icon: 'fas fa-copy',
- text: i18n.ts.copyContent,
- action: copyContent
- }, {
- icon: 'fas fa-link',
- text: i18n.ts.copyLink,
- action: copyLink
- }, (appearNote.url || appearNote.uri) ? {
- icon: 'fas fa-external-link-square-alt',
- text: i18n.ts.showOnRemote,
- action: () => {
- window.open(appearNote.url || appearNote.uri, '_blank');
- }
- } : undefined,
- {
- icon: 'fas fa-share-alt',
- text: i18n.ts.share,
- action: share
- },
- instance.translatorAvailable ? {
- icon: 'fas fa-language',
- text: i18n.ts.translate,
- action: translate
- } : undefined,
- null,
- statePromise.then(state => state.isFavorited ? {
- icon: 'fas fa-star',
- text: i18n.ts.unfavorite,
- action: () => toggleFavorite(false)
- } : {
- icon: 'fas fa-star',
- text: i18n.ts.favorite,
- action: () => toggleFavorite(true)
- }),
- {
- icon: 'fas fa-paperclip',
- text: i18n.ts.clip,
- action: () => clip()
- },
- (appearNote.userId !== $i.id) ? statePromise.then(state => state.isWatching ? {
- icon: 'fas fa-eye-slash',
- text: i18n.ts.unwatch,
- action: () => toggleWatch(false)
- } : {
- icon: 'fas fa-eye',
- text: i18n.ts.watch,
- action: () => toggleWatch(true)
- }) : undefined,
- statePromise.then(state => state.isMutedThread ? {
- icon: 'fas fa-comment-slash',
- text: i18n.ts.unmuteThread,
- action: () => toggleThreadMute(false)
- } : {
- icon: 'fas fa-comment-slash',
- text: i18n.ts.muteThread,
- action: () => toggleThreadMute(true)
- }),
- appearNote.userId === $i.id ? ($i.pinnedNoteIds || []).includes(appearNote.id) ? {
- icon: 'fas fa-thumbtack',
- text: i18n.ts.unpin,
- action: () => togglePin(false)
- } : {
- icon: 'fas fa-thumbtack',
- text: i18n.ts.pin,
- action: () => togglePin(true)
- } : undefined,
- /*
+ menu = [
+ ...(
+ props.currentClipPage?.value.userId === $i.id ? [{
+ icon: 'fas fa-circle-minus',
+ text: i18n.ts.unclip,
+ danger: true,
+ action: unclip,
+ }, null] : []
+ ),
+ {
+ icon: 'fas fa-copy',
+ text: i18n.ts.copyContent,
+ action: copyContent,
+ }, {
+ icon: 'fas fa-link',
+ text: i18n.ts.copyLink,
+ action: copyLink,
+ }, (appearNote.url || appearNote.uri) ? {
+ icon: 'fas fa-external-link-square-alt',
+ text: i18n.ts.showOnRemote,
+ action: () => {
+ window.open(appearNote.url || appearNote.uri, '_blank');
+ },
+ } : undefined,
+ {
+ icon: 'fas fa-share-alt',
+ text: i18n.ts.share,
+ action: share,
+ },
+ instance.translatorAvailable ? {
+ icon: 'fas fa-language',
+ text: i18n.ts.translate,
+ action: translate,
+ } : undefined,
+ null,
+ statePromise.then(state => state.isFavorited ? {
+ icon: 'fas fa-star',
+ text: i18n.ts.unfavorite,
+ action: () => toggleFavorite(false),
+ } : {
+ icon: 'fas fa-star',
+ text: i18n.ts.favorite,
+ action: () => toggleFavorite(true),
+ }),
+ {
+ icon: 'fas fa-paperclip',
+ text: i18n.ts.clip,
+ action: () => clip(),
+ },
+ (appearNote.userId !== $i.id) ? statePromise.then(state => state.isWatching ? {
+ icon: 'fas fa-eye-slash',
+ text: i18n.ts.unwatch,
+ action: () => toggleWatch(false),
+ } : {
+ icon: 'fas fa-eye',
+ text: i18n.ts.watch,
+ action: () => toggleWatch(true),
+ }) : undefined,
+ statePromise.then(state => state.isMutedThread ? {
+ icon: 'fas fa-comment-slash',
+ text: i18n.ts.unmuteThread,
+ action: () => toggleThreadMute(false),
+ } : {
+ icon: 'fas fa-comment-slash',
+ text: i18n.ts.muteThread,
+ action: () => toggleThreadMute(true),
+ }),
+ appearNote.userId === $i.id ? ($i.pinnedNoteIds || []).includes(appearNote.id) ? {
+ icon: 'fas fa-thumbtack',
+ text: i18n.ts.unpin,
+ action: () => togglePin(false),
+ } : {
+ icon: 'fas fa-thumbtack',
+ text: i18n.ts.pin,
+ action: () => togglePin(true),
+ } : undefined,
+ /*
...($i.isModerator || $i.isAdmin ? [
null,
{
@@ -246,52 +282,52 @@ export function getNoteMenu(props: {
}]
: []
),*/
- ...(appearNote.userId !== $i.id ? [
- null,
- {
- icon: 'fas fa-exclamation-circle',
- text: i18n.ts.reportAbuse,
- action: () => {
- const u = appearNote.url || appearNote.uri || `${url}/notes/${appearNote.id}`;
- os.popup(defineAsyncComponent(() => import('@/components/abuse-report-window.vue')), {
- user: appearNote.user,
- initialComment: `Note: ${u}\n-----\n`
- }, {}, 'closed');
- }
- }]
+ ...(appearNote.userId !== $i.id ? [
+ null,
+ {
+ icon: 'fas fa-exclamation-circle',
+ text: i18n.ts.reportAbuse,
+ action: () => {
+ const u = appearNote.url || appearNote.uri || `${url}/notes/${appearNote.id}`;
+ os.popup(defineAsyncComponent(() => import('@/components/abuse-report-window.vue')), {
+ user: appearNote.user,
+ initialComment: `Note: ${u}\n-----\n`,
+ }, {}, 'closed');
+ },
+ }]
: []
- ),
- ...(appearNote.userId === $i.id || $i.isModerator || $i.isAdmin ? [
- null,
- appearNote.userId === $i.id ? {
- icon: 'fas fa-edit',
- text: i18n.ts.deleteAndEdit,
- action: delEdit
- } : undefined,
- {
- icon: 'fas fa-trash-alt',
- text: i18n.ts.delete,
- danger: true,
- action: del
- }]
+ ),
+ ...(appearNote.userId === $i.id || $i.isModerator || $i.isAdmin ? [
+ null,
+ appearNote.userId === $i.id ? {
+ icon: 'fas fa-edit',
+ text: i18n.ts.deleteAndEdit,
+ action: delEdit,
+ } : undefined,
+ {
+ icon: 'fas fa-trash-alt',
+ text: i18n.ts.delete,
+ danger: true,
+ action: del,
+ }]
: []
- )]
+ )]
.filter(x => x !== undefined);
} else {
menu = [{
icon: 'fas fa-copy',
text: i18n.ts.copyContent,
- action: copyContent
+ action: copyContent,
}, {
icon: 'fas fa-link',
text: i18n.ts.copyLink,
- action: copyLink
+ action: copyLink,
}, (appearNote.url || appearNote.uri) ? {
icon: 'fas fa-external-link-square-alt',
text: i18n.ts.showOnRemote,
action: () => {
window.open(appearNote.url || appearNote.uri, '_blank');
- }
+ },
} : undefined]
.filter(x => x !== undefined);
}
@@ -302,7 +338,7 @@ export function getNoteMenu(props: {
text: action.title,
action: () => {
action.handler(appearNote);
- }
+ },
}))]);
}
diff --git a/packages/client/src/scripts/get-user-menu.ts b/packages/client/src/scripts/get-user-menu.ts
index 091338efd6..25bcd90e9f 100644
--- a/packages/client/src/scripts/get-user-menu.ts
+++ b/packages/client/src/scripts/get-user-menu.ts
@@ -1,12 +1,12 @@
+import * as Acct from 'misskey-js/built/acct';
+import { defineAsyncComponent } from 'vue';
import { i18n } from '@/i18n';
import copyToClipboard from '@/scripts/copy-to-clipboard';
import { host } from '@/config';
-import * as Acct from 'misskey-js/built/acct';
import * as os from '@/os';
import { userActions } from '@/store';
-import { router } from '@/router';
import { $i, iAmModerator } from '@/account';
-import { defineAsyncComponent } from 'vue';
+import { mainRouter } from '@/router';
export function getUserMenu(user) {
const meId = $i ? $i.id : null;
@@ -17,20 +17,20 @@ export function getUserMenu(user) {
if (lists.length === 0) {
os.alert({
type: 'error',
- text: i18n.ts.youHaveNoLists
+ text: i18n.ts.youHaveNoLists,
});
return;
}
const { canceled, result: listId } = await os.select({
title: t,
items: lists.map(list => ({
- value: list.id, text: list.name
- }))
+ value: list.id, text: list.name,
+ })),
});
if (canceled) return;
os.apiWithDialog('users/lists/push', {
listId: listId,
- userId: user.id
+ userId: user.id,
});
}
@@ -39,20 +39,20 @@ export function getUserMenu(user) {
if (groups.length === 0) {
os.alert({
type: 'error',
- text: i18n.ts.youHaveNoGroups
+ text: i18n.ts.youHaveNoGroups,
});
return;
}
const { canceled, result: groupId } = await os.select({
title: i18n.ts.group,
items: groups.map(group => ({
- value: group.id, text: group.name
- }))
+ value: group.id, text: group.name,
+ })),
});
if (canceled) return;
os.apiWithDialog('users/groups/invite', {
groupId: groupId,
- userId: user.id
+ userId: user.id,
});
}
@@ -101,7 +101,7 @@ export function getUserMenu(user) {
if (!await getConfirmed(user.isBlocking ? i18n.ts.unblockConfirm : i18n.ts.blockConfirm)) return;
os.apiWithDialog(user.isBlocking ? 'blocking/delete' : 'blocking/create', {
- userId: user.id
+ userId: user.id,
}).then(() => {
user.isBlocking = !user.isBlocking;
});
@@ -111,7 +111,7 @@ export function getUserMenu(user) {
if (!await getConfirmed(i18n.t(user.isSilenced ? 'unsilenceConfirm' : 'silenceConfirm'))) return;
os.apiWithDialog(user.isSilenced ? 'admin/unsilence-user' : 'admin/silence-user', {
- userId: user.id
+ userId: user.id,
}).then(() => {
user.isSilenced = !user.isSilenced;
});
@@ -121,7 +121,7 @@ export function getUserMenu(user) {
if (!await getConfirmed(i18n.t(user.isSuspended ? 'unsuspendConfirm' : 'suspendConfirm'))) return;
os.apiWithDialog(user.isSuspended ? 'admin/unsuspend-user' : 'admin/suspend-user', {
- userId: user.id
+ userId: user.id,
}).then(() => {
user.isSuspended = !user.isSuspended;
});
@@ -145,7 +145,7 @@ export function getUserMenu(user) {
async function invalidateFollow() {
os.apiWithDialog('following/invalidate', {
- userId: user.id
+ userId: user.id,
}).then(() => {
user.isFollowed = !user.isFollowed;
});
@@ -156,19 +156,19 @@ export function getUserMenu(user) {
text: i18n.ts.copyUsername,
action: () => {
copyToClipboard(`@${user.username}@${user.host || host}`);
- }
+ },
}, {
icon: 'fas fa-info-circle',
text: i18n.ts.info,
action: () => {
os.pageWindow(`/user-info/${user.id}`);
- }
+ },
}, {
icon: 'fas fa-envelope',
text: i18n.ts.sendMessage,
action: () => {
os.post({ specified: user });
- }
+ },
}, meId !== user.id ? {
type: 'link',
icon: 'fas fa-comments',
@@ -177,47 +177,47 @@ export function getUserMenu(user) {
} : undefined, null, {
icon: 'fas fa-list-ul',
text: i18n.ts.addToList,
- action: pushList
+ action: pushList,
}, meId !== user.id ? {
icon: 'fas fa-users',
text: i18n.ts.inviteToGroup,
- action: inviteGroup
+ action: inviteGroup,
} : undefined] as any;
if ($i && meId !== user.id) {
menu = menu.concat([null, {
icon: user.isMuted ? 'fas fa-eye' : 'fas fa-eye-slash',
text: user.isMuted ? i18n.ts.unmute : i18n.ts.mute,
- action: toggleMute
+ action: toggleMute,
}, {
icon: 'fas fa-ban',
text: user.isBlocking ? i18n.ts.unblock : i18n.ts.block,
- action: toggleBlock
+ action: toggleBlock,
}]);
if (user.isFollowed) {
menu = menu.concat([{
icon: 'fas fa-unlink',
text: i18n.ts.breakFollow,
- action: invalidateFollow
+ action: invalidateFollow,
}]);
}
menu = menu.concat([null, {
icon: 'fas fa-exclamation-circle',
text: i18n.ts.reportAbuse,
- action: reportAbuse
+ action: reportAbuse,
}]);
if (iAmModerator) {
menu = menu.concat([null, {
icon: 'fas fa-microphone-slash',
text: user.isSilenced ? i18n.ts.unsilence : i18n.ts.silence,
- action: toggleSilence
+ action: toggleSilence,
}, {
icon: 'fas fa-snowflake',
text: user.isSuspended ? i18n.ts.unsuspend : i18n.ts.suspend,
- action: toggleSuspend
+ action: toggleSuspend,
}]);
}
}
@@ -227,8 +227,8 @@ export function getUserMenu(user) {
icon: 'fas fa-pencil-alt',
text: i18n.ts.editProfile,
action: () => {
- router.push('/settings/profile');
- }
+ mainRouter.push('/settings/profile');
+ },
}]);
}
@@ -238,7 +238,7 @@ export function getUserMenu(user) {
text: action.title,
action: () => {
action.handler(user);
- }
+ },
}))]);
}
diff --git a/packages/client/src/scripts/hotkey.ts b/packages/client/src/scripts/hotkey.ts
index fd9c74f6c8..bd8c3b6cab 100644
--- a/packages/client/src/scripts/hotkey.ts
+++ b/packages/client/src/scripts/hotkey.ts
@@ -1,6 +1,8 @@
import keyCode from './keycode';
-type Keymap = Record<string, Function>;
+type Callback = (ev: KeyboardEvent) => void;
+
+type Keymap = Record<string, Callback>;
type Pattern = {
which: string[];
@@ -11,14 +13,14 @@ type Pattern = {
type Action = {
patterns: Pattern[];
- callback: Function;
+ callback: Callback;
allowRepeat: boolean;
};
const parseKeymap = (keymap: Keymap) => Object.entries(keymap).map(([patterns, callback]): Action => {
const result = {
patterns: [],
- callback: callback,
+ callback,
allowRepeat: true
} as Action;
diff --git a/packages/client/src/scripts/hpml/evaluator.ts b/packages/client/src/scripts/hpml/evaluator.ts
index 8106687b61..10023edffb 100644
--- a/packages/client/src/scripts/hpml/evaluator.ts
+++ b/packages/client/src/scripts/hpml/evaluator.ts
@@ -159,7 +159,6 @@ export class Hpml {
@autobind
private evaluate(expr: Expr, scope: HpmlScope): any {
-
if (isLiteralValue(expr)) {
if (expr.type === null) {
return null;
diff --git a/packages/client/src/scripts/hpml/index.ts b/packages/client/src/scripts/hpml/index.ts
index ac81eac2d9..7cf88d5961 100644
--- a/packages/client/src/scripts/hpml/index.ts
+++ b/packages/client/src/scripts/hpml/index.ts
@@ -14,13 +14,13 @@ export type Fn = {
export type Type = 'string' | 'number' | 'boolean' | 'stringArray' | null;
export const literalDefs: Record<string, { out: any; category: string; icon: any; }> = {
- text: { out: 'string', category: 'value', icon: 'fas fa-quote-right', },
- multiLineText: { out: 'string', category: 'value', icon: 'fas fa-align-left', },
- textList: { out: 'stringArray', category: 'value', icon: 'fas fa-list', },
- number: { out: 'number', category: 'value', icon: 'fas fa-sort-numeric-up', },
- ref: { out: null, category: 'value', icon: 'fas fa-magic', },
- aiScriptVar: { out: null, category: 'value', icon: 'fas fa-magic', },
- fn: { out: 'function', category: 'value', icon: 'fas fa-square-root-alt', },
+ text: { out: 'string', category: 'value', icon: 'fas fa-quote-right', },
+ multiLineText: { out: 'string', category: 'value', icon: 'fas fa-align-left', },
+ textList: { out: 'stringArray', category: 'value', icon: 'fas fa-list', },
+ number: { out: 'number', category: 'value', icon: 'fas fa-sort-numeric-up', },
+ ref: { out: null, category: 'value', icon: 'fas fa-magic', },
+ aiScriptVar: { out: null, category: 'value', icon: 'fas fa-magic', },
+ fn: { out: 'function', category: 'value', icon: 'fas fa-square-root-alt', },
};
export const blockDefs = [
diff --git a/packages/client/src/scripts/hpml/lib.ts b/packages/client/src/scripts/hpml/lib.ts
index 01a44ffcdf..cab467a920 100644
--- a/packages/client/src/scripts/hpml/lib.ts
+++ b/packages/client/src/scripts/hpml/lib.ts
@@ -125,55 +125,56 @@ export function initAiLib(hpml: Hpml) {
}
});
*/
- })
+ }),
};
}
export const funcDefs: Record<string, { in: any[]; out: any; category: string; icon: any; }> = {
- if: { in: ['boolean', 0, 0], out: 0, category: 'flow', icon: 'fas fa-share-alt', },
- for: { in: ['number', 'function'], out: null, category: 'flow', icon: 'fas fa-recycle', },
- not: { in: ['boolean'], out: 'boolean', category: 'logical', icon: 'fas fa-flag', },
- or: { in: ['boolean', 'boolean'], out: 'boolean', category: 'logical', icon: 'fas fa-flag', },
- and: { in: ['boolean', 'boolean'], out: 'boolean', category: 'logical', icon: 'fas fa-flag', },
- add: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'fas fa-plus', },
- subtract: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'fas fa-minus', },
- multiply: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'fas fa-times', },
- divide: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'fas fa-divide', },
- mod: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'fas fa-divide', },
- round: { in: ['number'], out: 'number', category: 'operation', icon: 'fas fa-calculator', },
- eq: { in: [0, 0], out: 'boolean', category: 'comparison', icon: 'fas fa-equals', },
- notEq: { in: [0, 0], out: 'boolean', category: 'comparison', icon: 'fas fa-not-equal', },
- gt: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'fas fa-greater-than', },
- lt: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'fas fa-less-than', },
- gtEq: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'fas fa-greater-than-equal', },
- ltEq: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'fas fa-less-than-equal', },
- strLen: { in: ['string'], out: 'number', category: 'text', icon: 'fas fa-quote-right', },
- strPick: { in: ['string', 'number'], out: 'string', category: 'text', icon: 'fas fa-quote-right', },
- strReplace: { in: ['string', 'string', 'string'], out: 'string', category: 'text', icon: 'fas fa-quote-right', },
- strReverse: { in: ['string'], out: 'string', category: 'text', icon: 'fas fa-quote-right', },
- join: { in: ['stringArray', 'string'], out: 'string', category: 'text', icon: 'fas fa-quote-right', },
- stringToNumber: { in: ['string'], out: 'number', category: 'convert', icon: 'fas fa-exchange-alt', },
- numberToString: { in: ['number'], out: 'string', category: 'convert', icon: 'fas fa-exchange-alt', },
- splitStrByLine: { in: ['string'], out: 'stringArray', category: 'convert', icon: 'fas fa-exchange-alt', },
- pick: { in: [null, 'number'], out: null, category: 'list', icon: 'fas fa-indent', },
- listLen: { in: [null], out: 'number', category: 'list', icon: 'fas fa-indent', },
- rannum: { in: ['number', 'number'], out: 'number', category: 'random', icon: 'fas fa-dice', },
- dailyRannum: { in: ['number', 'number'], out: 'number', category: 'random', icon: 'fas fa-dice', },
- seedRannum: { in: [null, 'number', 'number'], out: 'number', category: 'random', icon: 'fas fa-dice', },
- random: { in: ['number'], out: 'boolean', category: 'random', icon: 'fas fa-dice', },
- dailyRandom: { in: ['number'], out: 'boolean', category: 'random', icon: 'fas fa-dice', },
- seedRandom: { in: [null, 'number'], out: 'boolean', category: 'random', icon: 'fas fa-dice', },
- randomPick: { in: [0], out: 0, category: 'random', icon: 'fas fa-dice', },
- dailyRandomPick: { in: [0], out: 0, category: 'random', icon: 'fas fa-dice', },
- seedRandomPick: { in: [null, 0], out: 0, category: 'random', icon: 'fas fa-dice', },
- DRPWPM: { in: ['stringArray'], out: 'string', category: 'random', icon: 'fas fa-dice', }, // dailyRandomPickWithProbabilityMapping
+ if: { in: ['boolean', 0, 0], out: 0, category: 'flow', icon: 'fas fa-share-alt' },
+ for: { in: ['number', 'function'], out: null, category: 'flow', icon: 'fas fa-recycle' },
+ not: { in: ['boolean'], out: 'boolean', category: 'logical', icon: 'fas fa-flag' },
+ or: { in: ['boolean', 'boolean'], out: 'boolean', category: 'logical', icon: 'fas fa-flag' },
+ and: { in: ['boolean', 'boolean'], out: 'boolean', category: 'logical', icon: 'fas fa-flag' },
+ add: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'fas fa-plus' },
+ subtract: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'fas fa-minus' },
+ multiply: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'fas fa-times' },
+ divide: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'fas fa-divide' },
+ mod: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'fas fa-divide' },
+ round: { in: ['number'], out: 'number', category: 'operation', icon: 'fas fa-calculator' },
+ eq: { in: [0, 0], out: 'boolean', category: 'comparison', icon: 'fas fa-equals' },
+ notEq: { in: [0, 0], out: 'boolean', category: 'comparison', icon: 'fas fa-not-equal' },
+ gt: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'fas fa-greater-than' },
+ lt: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'fas fa-less-than' },
+ gtEq: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'fas fa-greater-than-equal' },
+ ltEq: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'fas fa-less-than-equal' },
+ strLen: { in: ['string'], out: 'number', category: 'text', icon: 'fas fa-quote-right' },
+ strPick: { in: ['string', 'number'], out: 'string', category: 'text', icon: 'fas fa-quote-right' },
+ strReplace: { in: ['string', 'string', 'string'], out: 'string', category: 'text', icon: 'fas fa-quote-right' },
+ strReverse: { in: ['string'], out: 'string', category: 'text', icon: 'fas fa-quote-right' },
+ join: { in: ['stringArray', 'string'], out: 'string', category: 'text', icon: 'fas fa-quote-right' },
+ stringToNumber: { in: ['string'], out: 'number', category: 'convert', icon: 'fas fa-exchange-alt' },
+ numberToString: { in: ['number'], out: 'string', category: 'convert', icon: 'fas fa-exchange-alt' },
+ splitStrByLine: { in: ['string'], out: 'stringArray', category: 'convert', icon: 'fas fa-exchange-alt' },
+ pick: { in: [null, 'number'], out: null, category: 'list', icon: 'fas fa-indent' },
+ listLen: { in: [null], out: 'number', category: 'list', icon: 'fas fa-indent' },
+ rannum: { in: ['number', 'number'], out: 'number', category: 'random', icon: 'fas fa-dice' },
+ dailyRannum: { in: ['number', 'number'], out: 'number', category: 'random', icon: 'fas fa-dice' },
+ seedRannum: { in: [null, 'number', 'number'], out: 'number', category: 'random', icon: 'fas fa-dice' },
+ random: { in: ['number'], out: 'boolean', category: 'random', icon: 'fas fa-dice' },
+ dailyRandom: { in: ['number'], out: 'boolean', category: 'random', icon: 'fas fa-dice' },
+ seedRandom: { in: [null, 'number'], out: 'boolean', category: 'random', icon: 'fas fa-dice' },
+ randomPick: { in: [0], out: 0, category: 'random', icon: 'fas fa-dice' },
+ dailyRandomPick: { in: [0], out: 0, category: 'random', icon: 'fas fa-dice' },
+ seedRandomPick: { in: [null, 0], out: 0, category: 'random', icon: 'fas fa-dice' },
+ DRPWPM: { in: ['stringArray'], out: 'string', category: 'random', icon: 'fas fa-dice' }, // dailyRandomPickWithProbabilityMapping
};
export function initHpmlLib(expr: Expr, scope: HpmlScope, randomSeed: string, visitor?: any) {
-
const date = new Date();
const day = `${visitor ? visitor.id : ''} ${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`;
+ // SHOULD be fine to ignore since it's intended + function shape isn't defined
+ // eslint-disable-next-line @typescript-eslint/ban-types
const funcs: Record<string, Function> = {
not: (a: boolean) => !a,
or: (a: boolean, b: boolean) => a || b,
@@ -189,7 +190,7 @@ export function initHpmlLib(expr: Expr, scope: HpmlScope, randomSeed: string, vi
const result: any[] = [];
for (let i = 0; i < times; i++) {
result.push(fn.exec({
- [fn.slots[0]]: i + 1
+ [fn.slots[0]]: i + 1,
}));
}
return result;
diff --git a/packages/client/src/scripts/i18n.ts b/packages/client/src/scripts/i18n.ts
index 3fe88e5514..54184386da 100644
--- a/packages/client/src/scripts/i18n.ts
+++ b/packages/client/src/scripts/i18n.ts
@@ -11,13 +11,13 @@ export class I18n<T extends Record<string, any>> {
// string にしているのは、ドット区切りでのパス指定を許可するため
// なるべくこのメソッド使うよりもlocale直接参照の方がvueのキャッシュ効いてパフォーマンスが良いかも
- public t(key: string, args?: Record<string, string>): string {
+ public t(key: string, args?: Record<string, string | number>): string {
try {
let str = key.split('.').reduce((o, i) => o[i], this.ts) as unknown as string;
if (args) {
for (const [k, v] of Object.entries(args)) {
- str = str.replace(`{${k}}`, v);
+ str = str.replace(`{${k}}`, v.toString());
}
}
return str;
diff --git a/packages/client/src/scripts/navigate.ts b/packages/client/src/scripts/navigate.ts
deleted file mode 100644
index 08b891ec5b..0000000000
--- a/packages/client/src/scripts/navigate.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { inject } from 'vue';
-import { router } from '@/router';
-import { defaultStore } from '@/store';
-
-export type Navigate = (path: string, record?: boolean) => void;
-
-export class MisskeyNavigator {
- public readonly navHook: Navigate | null = null;
- public readonly sideViewHook: Navigate | null = null;
-
- // It should be constructed during vue creating in order for inject function to work
- constructor() {
- this.navHook = inject<Navigate | null>('navHook', null);
- this.sideViewHook = inject<Navigate | null>('sideViewHook', null);
- }
-
- // Use this method instead of router.push()
- public push(path: string, record = true) {
- if (this.navHook) {
- this.navHook(path, record);
- } else {
- if (defaultStore.state.defaultSideView && this.sideViewHook && path !== '/') {
- return this.sideViewHook(path, record);
- }
-
- if (router.currentRoute.value.path === path) {
- window.scroll({ top: 0, behavior: 'smooth' });
- } else {
- if (record) router.push(path);
- else router.replace(path);
- }
- }
- }
-}
diff --git a/packages/client/src/scripts/page-metadata.ts b/packages/client/src/scripts/page-metadata.ts
new file mode 100644
index 0000000000..0db8369f9d
--- /dev/null
+++ b/packages/client/src/scripts/page-metadata.ts
@@ -0,0 +1,41 @@
+import * as misskey from 'misskey-js';
+import { ComputedRef, inject, isRef, onActivated, onMounted, provide, ref, Ref } from 'vue';
+
+export const setPageMetadata = Symbol('setPageMetadata');
+export const pageMetadataProvider = Symbol('pageMetadataProvider');
+
+export type PageMetadata = {
+ title: string;
+ subtitle?: string;
+ icon?: string | null;
+ avatar?: misskey.entities.User | null;
+ userName?: misskey.entities.User | null;
+ bg?: string;
+};
+
+export function definePageMetadata(metadata: PageMetadata | null | Ref<PageMetadata | null> | ComputedRef<PageMetadata | null>): void {
+ const _metadata = isRef(metadata) ? metadata : ref(metadata);
+
+ provide(pageMetadataProvider, _metadata);
+
+ const set = inject(setPageMetadata) as any;
+ if (set) {
+ set(_metadata);
+
+ onMounted(() => {
+ set(_metadata);
+ });
+
+ onActivated(() => {
+ set(_metadata);
+ });
+ }
+}
+
+export function provideMetadataReceiver(callback: (info: ComputedRef<PageMetadata>) => void): void {
+ provide(setPageMetadata, callback);
+}
+
+export function injectPageMetadata(): PageMetadata | undefined {
+ return inject(pageMetadataProvider);
+}
diff --git a/packages/client/src/scripts/please-login.ts b/packages/client/src/scripts/please-login.ts
index e21a6d2ed3..1f38061841 100644
--- a/packages/client/src/scripts/please-login.ts
+++ b/packages/client/src/scripts/please-login.ts
@@ -17,5 +17,5 @@ export function pleaseLogin(path?: string) {
},
}, 'closed');
- throw new Error('signin required');
+ if (!path) throw new Error('signin required');
}
diff --git a/packages/client/src/scripts/scroll.ts b/packages/client/src/scripts/scroll.ts
index 621fe88105..f5bc6bf9ce 100644
--- a/packages/client/src/scripts/scroll.ts
+++ b/packages/client/src/scripts/scroll.ts
@@ -1,9 +1,9 @@
type ScrollBehavior = 'auto' | 'smooth' | 'instant';
-export function getScrollContainer(el: Element | null): Element | null {
- if (el == null || el.tagName === 'BODY') return null;
- const overflow = window.getComputedStyle(el).getPropertyValue('overflow');
- if (overflow.endsWith('auto')) { // xとyを個別に指定している場合、hidden auto みたいな値になる
+export function getScrollContainer(el: HTMLElement | null): HTMLElement | null {
+ if (el == null || el.tagName === 'HTML') return null;
+ const overflow = window.getComputedStyle(el).getPropertyValue('overflow-y');
+ if (overflow === 'scroll' || overflow === 'auto') {
return el;
} else {
return getScrollContainer(el.parentElement);
@@ -22,6 +22,11 @@ export function isTopVisible(el: Element | null): boolean {
return scrollTop <= topPosition;
}
+export function isBottomVisible(el: HTMLElement, tolerance = 1, container = getScrollContainer(el)) {
+ if (container) return el.scrollHeight <= container.clientHeight + Math.abs(container.scrollTop) + tolerance;
+ return el.scrollHeight <= window.innerHeight + window.scrollY + tolerance;
+}
+
export function onScrollTop(el: Element, cb) {
const container = getScrollContainer(el) || window;
const onScroll = ev => {
diff --git a/packages/client/src/scripts/search.ts b/packages/client/src/scripts/search.ts
index 0aedee9c98..64914d3d65 100644
--- a/packages/client/src/scripts/search.ts
+++ b/packages/client/src/scripts/search.ts
@@ -1,6 +1,6 @@
import * as os from '@/os';
import { i18n } from '@/i18n';
-import { router } from '@/router';
+import { mainRouter } from '@/router';
export async function search() {
const { canceled, result: query } = await os.inputText({
@@ -11,12 +11,12 @@ export async function search() {
const q = query.trim();
if (q.startsWith('@') && !q.includes(' ')) {
- router.push(`/${q}`);
+ mainRouter.push(`/${q}`);
return;
}
if (q.startsWith('#')) {
- router.push(`/tags/${encodeURIComponent(q.substr(1))}`);
+ mainRouter.push(`/tags/${encodeURIComponent(q.substr(1))}`);
return;
}
@@ -36,14 +36,14 @@ export async function search() {
//v.$root.$emit('warp', date);
os.alert({
icon: 'fas fa-history',
- iconOnly: true, autoClose: true
+ iconOnly: true, autoClose: true,
});
return;
}
if (q.startsWith('https://')) {
const promise = os.api('ap/show', {
- uri: q
+ uri: q,
});
os.promiseDialog(promise, null, null, i18n.ts.fetchingAsApObject);
@@ -51,13 +51,13 @@ export async function search() {
const res = await promise;
if (res.type === 'User') {
- router.push(`/@${res.object.username}@${res.object.host}`);
+ mainRouter.push(`/@${res.object.username}@${res.object.host}`);
} else if (res.type === 'Note') {
- router.push(`/notes/${res.object.id}`);
+ mainRouter.push(`/notes/${res.object.id}`);
}
return;
}
- router.push(`/search?q=${encodeURIComponent(q)}`);
+ mainRouter.push(`/search?q=${encodeURIComponent(q)}`);
}
diff --git a/packages/client/src/scripts/select-file.ts b/packages/client/src/scripts/select-file.ts
index 461d613b42..17e31d96f1 100644
--- a/packages/client/src/scripts/select-file.ts
+++ b/packages/client/src/scripts/select-file.ts
@@ -1,9 +1,9 @@
import { ref } from 'vue';
+import { DriveFile } from 'misskey-js/built/entities';
import * as os from '@/os';
import { stream } from '@/stream';
import { i18n } from '@/i18n';
import { defaultStore } from '@/store';
-import { DriveFile } from 'misskey-js/built/entities';
import { uploadFile } from '@/scripts/upload';
function select(src: any, label: string | null, multiple: boolean): Promise<DriveFile | DriveFile[]> {
@@ -20,10 +20,7 @@ function select(src: any, label: string | null, multiple: boolean): Promise<Driv
Promise.all(promises).then(driveFiles => {
res(multiple ? driveFiles : driveFiles[0]);
}).catch(err => {
- os.alert({
- type: 'error',
- text: err
- });
+ // アップロードのエラーは uploadFile 内でハンドリングされているためアラートダイアログを出したりはしてはいけない
});
// 一応廃棄
@@ -47,7 +44,7 @@ function select(src: any, label: string | null, multiple: boolean): Promise<Driv
os.inputText({
title: i18n.ts.uploadFromUrl,
type: 'url',
- placeholder: i18n.ts.uploadFromUrlDescription
+ placeholder: i18n.ts.uploadFromUrlDescription,
}).then(({ canceled, result: url }) => {
if (canceled) return;
@@ -64,35 +61,35 @@ function select(src: any, label: string | null, multiple: boolean): Promise<Driv
os.api('drive/files/upload-from-url', {
url: url,
folderId: defaultStore.state.uploadFolder,
- marker
+ marker,
});
os.alert({
title: i18n.ts.uploadFromUrlRequested,
- text: i18n.ts.uploadFromUrlMayTakeTime
+ text: i18n.ts.uploadFromUrlMayTakeTime,
});
});
};
os.popupMenu([label ? {
text: label,
- type: 'label'
+ type: 'label',
} : undefined, {
type: 'switch',
text: i18n.ts.keepOriginalUploading,
- ref: keepOriginal
+ ref: keepOriginal,
}, {
text: i18n.ts.upload,
icon: 'fas fa-upload',
- action: chooseFileFromPc
+ action: chooseFileFromPc,
}, {
text: i18n.ts.fromDrive,
icon: 'fas fa-cloud',
- action: chooseFileFromDrive
+ action: chooseFileFromDrive,
}, {
text: i18n.ts.fromUrl,
icon: 'fas fa-link',
- action: chooseFileFromUrl
+ action: chooseFileFromUrl,
}], src);
});
}
diff --git a/packages/client/src/scripts/twemoji-base.ts b/packages/client/src/scripts/twemoji-base.ts
index cd50311b15..638aae3284 100644
--- a/packages/client/src/scripts/twemoji-base.ts
+++ b/packages/client/src/scripts/twemoji-base.ts
@@ -1 +1,12 @@
export const twemojiSvgBase = '/twemoji';
+
+export function char2fileName(char: string): string {
+ let codes = Array.from(char).map(x => x.codePointAt(0)?.toString(16));
+ if (!codes.includes('200d')) codes = codes.filter(x => x !== 'fe0f');
+ codes = codes.filter(x => x && x.length);
+ return codes.join('-');
+}
+
+export function char2filePath(char: string): string {
+ return `${twemojiSvgBase}/${char2fileName(char)}.svg`;
+}
diff --git a/packages/client/src/scripts/upload.ts b/packages/client/src/scripts/upload.ts
index 2f7b30b58d..51f1c1b86f 100644
--- a/packages/client/src/scripts/upload.ts
+++ b/packages/client/src/scripts/upload.ts
@@ -1,10 +1,11 @@
import { reactive, ref } from 'vue';
+import * as Misskey from 'misskey-js';
+import { readAndCompressImage } from 'browser-image-resizer';
import { defaultStore } from '@/store';
import { apiUrl } from '@/config';
-import * as Misskey from 'misskey-js';
import { $i } from '@/account';
-import { readAndCompressImage } from 'browser-image-resizer';
import { alert } from '@/os';
+import { i18n } from '@/i18n';
type Uploading = {
id: string;
@@ -31,7 +32,7 @@ export function uploadFile(
file: File,
folder?: any,
name?: string,
- keepOriginal: boolean = defaultStore.state.keepOriginalUploading
+ keepOriginal: boolean = defaultStore.state.keepOriginalUploading,
): Promise<Misskey.entities.DriveFile> {
if (folder && typeof folder === 'object') folder = folder.id;
@@ -45,7 +46,7 @@ export function uploadFile(
name: name || file.name || 'untitled',
progressMax: undefined,
progressValue: undefined,
- img: window.URL.createObjectURL(file)
+ img: window.URL.createObjectURL(file),
});
uploads.value.push(ctx);
@@ -80,14 +81,37 @@ export function uploadFile(
xhr.open('POST', apiUrl + '/drive/files/create', true);
xhr.onload = (ev) => {
if (xhr.status !== 200 || ev.target == null || ev.target.response == null) {
- // TODO: 消すのではなくて再送できるようにしたい
+ // TODO: 消すのではなくて(ネットワーク的なエラーなら)再送できるようにしたい
uploads.value = uploads.value.filter(x => x.id !== id);
- alert({
- type: 'error',
- title: 'Failed to upload',
- text: `${JSON.stringify(ev.target?.response)}, ${JSON.stringify(xhr.response)}`
- });
+ if (ev.target?.response) {
+ const res = JSON.parse(ev.target.response);
+ if (res.error?.id === 'bec5bd69-fba3-43c9-b4fb-2894b66ad5d2') {
+ alert({
+ type: 'error',
+ title: i18n.ts.failedToUpload,
+ text: i18n.ts.cannotUploadBecauseInappropriate,
+ });
+ } else if (res.error?.id === 'd08dbc37-a6a9-463a-8c47-96c32ab5f064') {
+ alert({
+ type: 'error',
+ title: i18n.ts.failedToUpload,
+ text: i18n.ts.cannotUploadBecauseNoFreeSpace,
+ });
+ } else {
+ alert({
+ type: 'error',
+ title: i18n.ts.failedToUpload,
+ text: `${res.error?.message}\n${res.error?.code}\n${res.error?.id}`,
+ });
+ }
+ } else {
+ alert({
+ type: 'error',
+ title: 'Failed to upload',
+ text: `${JSON.stringify(ev.target?.response)}, ${JSON.stringify(xhr.response)}`,
+ });
+ }
reject();
return;
diff --git a/packages/client/src/scripts/url.ts b/packages/client/src/scripts/url.ts
index 542b00e0f0..86735de9f0 100644
--- a/packages/client/src/scripts/url.ts
+++ b/packages/client/src/scripts/url.ts
@@ -1,4 +1,4 @@
-export function query(obj: {}): string {
+export function query(obj: Record<string, any>): string {
const params = Object.entries(obj)
.filter(([, v]) => Array.isArray(v) ? v.length : v !== undefined)
.reduce((a, [k, v]) => (a[k] = v, a), {} as Record<string, any>);
diff --git a/packages/client/src/scripts/use-chart-tooltip.ts b/packages/client/src/scripts/use-chart-tooltip.ts
new file mode 100644
index 0000000000..ab57165694
--- /dev/null
+++ b/packages/client/src/scripts/use-chart-tooltip.ts
@@ -0,0 +1,50 @@
+import { onUnmounted, ref } from 'vue';
+import * as os from '@/os';
+import MkChartTooltip from '@/components/chart-tooltip.vue';
+
+export function useChartTooltip() {
+ const tooltipShowing = ref(false);
+ const tooltipX = ref(0);
+ const tooltipY = ref(0);
+ const tooltipTitle = ref(null);
+ const tooltipSeries = ref(null);
+ let disposeTooltipComponent;
+
+ os.popup(MkChartTooltip, {
+ showing: tooltipShowing,
+ x: tooltipX,
+ y: tooltipY,
+ title: tooltipTitle,
+ series: tooltipSeries,
+ }, {}).then(({ dispose }) => {
+ disposeTooltipComponent = dispose;
+ });
+
+ onUnmounted(() => {
+ if (disposeTooltipComponent) disposeTooltipComponent();
+ });
+
+ function handler(context) {
+ if (context.tooltip.opacity === 0) {
+ tooltipShowing.value = false;
+ return;
+ }
+
+ tooltipTitle.value = context.tooltip.title[0];
+ tooltipSeries.value = context.tooltip.body.map((b, i) => ({
+ backgroundColor: context.tooltip.labelColors[i].backgroundColor,
+ borderColor: context.tooltip.labelColors[i].borderColor,
+ text: b.lines[0],
+ }));
+
+ const rect = context.chart.canvas.getBoundingClientRect();
+
+ tooltipShowing.value = true;
+ tooltipX.value = rect.left + window.pageXOffset + context.tooltip.caretX;
+ tooltipY.value = rect.top + window.pageYOffset + context.tooltip.caretY;
+ }
+
+ return {
+ handler,
+ };
+}
diff --git a/packages/client/src/scripts/use-interval.ts b/packages/client/src/scripts/use-interval.ts
new file mode 100644
index 0000000000..201ba417ef
--- /dev/null
+++ b/packages/client/src/scripts/use-interval.ts
@@ -0,0 +1,24 @@
+import { onMounted, onUnmounted } from 'vue';
+
+export function useInterval(fn: () => void, interval: number, options: {
+ immediate: boolean;
+ afterMounted: boolean;
+}): void {
+ if (Number.isNaN(interval)) return;
+
+ let intervalId: number | null = null;
+
+ if (options.afterMounted) {
+ onMounted(() => {
+ if (options.immediate) fn();
+ intervalId = window.setInterval(fn, interval);
+ });
+ } else {
+ if (options.immediate) fn();
+ intervalId = window.setInterval(fn, interval);
+ }
+
+ onUnmounted(() => {
+ if (intervalId) window.clearInterval(intervalId);
+ });
+}
diff --git a/packages/client/src/scripts/use-leave-guard.ts b/packages/client/src/scripts/use-leave-guard.ts
index 33eea6b522..a93b84d1fe 100644
--- a/packages/client/src/scripts/use-leave-guard.ts
+++ b/packages/client/src/scripts/use-leave-guard.ts
@@ -1,9 +1,9 @@
import { inject, onUnmounted, Ref } from 'vue';
-import { onBeforeRouteLeave } from 'vue-router';
import { i18n } from '@/i18n';
import * as os from '@/os';
export function useLeaveGuard(enabled: Ref<boolean>) {
+ /* TODO
const setLeaveGuard = inject('setLeaveGuard');
if (setLeaveGuard) {
@@ -29,6 +29,7 @@ export function useLeaveGuard(enabled: Ref<boolean>) {
return !canceled;
});
}
+ */
/*
function onBeforeLeave(ev: BeforeUnloadEvent) {
diff --git a/packages/client/src/scripts/use-tooltip.ts b/packages/client/src/scripts/use-tooltip.ts
index bc8f27a038..1f6e0fb6ce 100644
--- a/packages/client/src/scripts/use-tooltip.ts
+++ b/packages/client/src/scripts/use-tooltip.ts
@@ -3,6 +3,7 @@ import { Ref, ref, watch, onUnmounted } from 'vue';
export function useTooltip(
elRef: Ref<HTMLElement | { $el: HTMLElement } | null | undefined>,
onShow: (showing: Ref<boolean>) => void,
+ delay = 300,
): void {
let isHovering = false;
@@ -40,7 +41,7 @@ export function useTooltip(
if (isHovering) return;
if (shouldIgnoreMouseover) return;
isHovering = true;
- timeoutId = window.setTimeout(open, 300);
+ timeoutId = window.setTimeout(open, delay);
};
const onMouseleave = () => {
@@ -54,7 +55,7 @@ export function useTooltip(
shouldIgnoreMouseover = true;
if (isHovering) return;
isHovering = true;
- timeoutId = window.setTimeout(open, 300);
+ timeoutId = window.setTimeout(open, delay);
};
const onTouchend = () => {