mirror of
https://github.com/misskey-dev/misskey.git
synced 2024-12-23 12:50:39 +01:00
Refactor sw (#10579)
* refactor(sw): remove dead code * refactor(sw): remove dead code * refactor(sw): remove dead code * refactor(sw): remove dead code * refactor(sw): remove dead code * refactor(sw): remove dead code * refactor(sw): 冗長な部分を変更 * refactor(sw): 使われていない煩雑な機能を削除 * refactor(sw): remove dead code * refactor(sw): URL文字列の作成に`URL`を使うように * refactor(sw): 型アサーションの削除とそれに伴い露呈したエラーへの対処 * refactor(sw): `append` -> `set` in `URLSearchParams` * refactor(sw): `any`の削除とそれに伴い露呈したエラーへの対処 * refactor(sw): 型アサーションの削除とそれに伴い露呈したエラーへの対処 対処と言っても`throw`するだけ。いままでもこの状況ではエラーが投げられていたはずなので、この対処により新たな問題が起きることはないはず。 * refactor(sw): i18n loading * refactor(sw): 型推論がうまくできる書き方に変更 `codes`が`(string | undefined)[]`から`string[]`になった * refactor(sw): クエリ文字列の作成に`URLSearchParams`を使うように * refactor(sw): `findClient` * refactor(sw): `openClient`における`any`や`as`の書き換え * refactor(sw): `openPost`における`any`の書き換え * refactor(sw): `let` -> `const` * refactor(sw): `any` -> `unknown` * cleanup(sw): import * cleanup(sw) * cleanup(sw): `?.` * cleanup(sw/.eslintrc.js) * refactor(sw): `@typescript-eslint/explicit-function-return-type` * refactor(sw): `@typescript-eslint/no-unused-vars` * refactor(sw): どうしようもないところに`eslint-disable-next-line`を * refactor(sw): `import/no-default-export` * update operations.ts * throw new Error --------- Co-authored-by: tamaina <tamaina@hotmail.co.jp>
This commit is contained in:
parent
35613fd642
commit
5002effd65
14 changed files with 97 additions and 138 deletions
|
@ -1,22 +1,20 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
root: true,
|
root: true,
|
||||||
env: {
|
env: {
|
||||||
"node": false
|
node: false,
|
||||||
},
|
},
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
"parser": "@typescript-eslint/parser",
|
parser: '@typescript-eslint/parser',
|
||||||
tsconfigRootDir: __dirname,
|
tsconfigRootDir: __dirname,
|
||||||
project: ['./tsconfig.json'],
|
project: ['./tsconfig.json'],
|
||||||
},
|
},
|
||||||
extends: [
|
extends: ['../shared/.eslintrc.js'],
|
||||||
"../shared/.eslintrc.js",
|
|
||||||
],
|
|
||||||
globals: {
|
globals: {
|
||||||
"require": false,
|
require: false,
|
||||||
"_DEV_": false,
|
_DEV_: false,
|
||||||
"_LANGS_": false,
|
_LANGS_: false,
|
||||||
"_VERSION_": false,
|
_VERSION_: false,
|
||||||
"_ENV_": false,
|
_ENV_: false,
|
||||||
"_PERF_PREFIX_": false,
|
_PERF_PREFIX_: false,
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|
1
packages/sw/src/@types/global.d.ts
vendored
1
packages/sw/src/@types/global.d.ts
vendored
|
@ -1,3 +1,4 @@
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
type FIXME = any;
|
type FIXME = any;
|
||||||
|
|
||||||
declare const _LANGS_: string[][];
|
declare const _LANGS_: string[][];
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
import * as misskey from 'misskey-js';
|
|
||||||
import * as Acct from 'misskey-js/built/acct';
|
|
||||||
|
|
||||||
export const acct = (user: misskey.Acct) => {
|
|
||||||
return Acct.toString(user);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const userName = (user: misskey.entities.User) => {
|
|
||||||
return user.name || user.username;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const userPage = (user: misskey.Acct, path?, absolute = false) => {
|
|
||||||
return `${absolute ? origin : ''}/@${acct(user)}${(path ? `/${path}` : '')}`;
|
|
||||||
};
|
|
|
@ -1,22 +1,20 @@
|
||||||
/*
|
/*
|
||||||
* Notification manager for SW
|
* Notification manager for SW
|
||||||
*/
|
*/
|
||||||
import { swLang } from '@/scripts/lang';
|
import type { BadgeNames, PushNotificationDataMap } from '@/types';
|
||||||
import { cli } from '@/scripts/operations';
|
|
||||||
import { BadgeNames, PushNotificationDataMap } from '@/types';
|
|
||||||
import getUserName from '@/scripts/get-user-name';
|
|
||||||
import { I18n } from '@/scripts/i18n';
|
|
||||||
import { getAccountFromId } from '@/scripts/get-account-from-id';
|
|
||||||
import { char2fileName } from '@/scripts/twemoji-base';
|
import { char2fileName } from '@/scripts/twemoji-base';
|
||||||
import * as url from '@/scripts/url';
|
import { cli } from '@/scripts/operations';
|
||||||
|
import { getAccountFromId } from '@/scripts/get-account-from-id';
|
||||||
|
import { swLang } from '@/scripts/lang';
|
||||||
|
import { getUserName } from '@/scripts/get-user-name';
|
||||||
|
|
||||||
const closeNotificationsByTags = async (tags: string[]) => {
|
const closeNotificationsByTags = async (tags: string[]): Promise<void> => {
|
||||||
for (const n of (await Promise.all(tags.map(tag => globalThis.registration.getNotifications({ tag })))).flat()) {
|
for (const n of (await Promise.all(tags.map(tag => globalThis.registration.getNotifications({ tag })))).flat()) {
|
||||||
n.close();
|
n.close();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const iconUrl = (name: BadgeNames) => `/static-assets/tabler-badges/${name}.png`;
|
const iconUrl = (name: BadgeNames): string => `/static-assets/tabler-badges/${name}.png`;
|
||||||
/* How to add a new badge:
|
/* How to add a new badge:
|
||||||
* 1. Find the icon and download png from https://tabler-icons.io/
|
* 1. Find the icon and download png from https://tabler-icons.io/
|
||||||
* 2. vips resize ~/Downloads/icon-name.png vipswork.png 0.4; vips scRGB2BW vipswork.png ~/icon-name.png"[compression=9,strip]"; rm vipswork.png;
|
* 2. vips resize ~/Downloads/icon-name.png vipswork.png 0.4; vips scRGB2BW vipswork.png ~/icon-name.png"[compression=9,strip]"; rm vipswork.png;
|
||||||
|
@ -25,7 +23,7 @@ const iconUrl = (name: BadgeNames) => `/static-assets/tabler-badges/${name}.png`
|
||||||
* 5. Add `badge: iconUrl('icon-name'),`
|
* 5. Add `badge: iconUrl('icon-name'),`
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export async function createNotification<K extends keyof PushNotificationDataMap>(data: PushNotificationDataMap[K]) {
|
export async function createNotification<K extends keyof PushNotificationDataMap>(data: PushNotificationDataMap[K]): Promise<void> {
|
||||||
const n = await composeNotification(data);
|
const n = await composeNotification(data);
|
||||||
|
|
||||||
if (n) {
|
if (n) {
|
||||||
|
@ -37,8 +35,7 @@ export async function createNotification<K extends keyof PushNotificationDataMap
|
||||||
}
|
}
|
||||||
|
|
||||||
async function composeNotification(data: PushNotificationDataMap[keyof PushNotificationDataMap]): Promise<[string, NotificationOptions] | null> {
|
async function composeNotification(data: PushNotificationDataMap[keyof PushNotificationDataMap]): Promise<[string, NotificationOptions] | null> {
|
||||||
if (!swLang.i18n) swLang.fetchLocale();
|
const i18n = await (swLang.i18n ?? swLang.fetchLocale());
|
||||||
const i18n = await swLang.i18n as I18n<any>;
|
|
||||||
const { t } = i18n;
|
const { t } = i18n;
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
/*
|
/*
|
||||||
|
@ -139,16 +136,16 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif
|
||||||
if (reaction.startsWith(':')) {
|
if (reaction.startsWith(':')) {
|
||||||
// カスタム絵文字の場合
|
// カスタム絵文字の場合
|
||||||
const name = reaction.substring(1, reaction.length - 1);
|
const name = reaction.substring(1, reaction.length - 1);
|
||||||
badge = `${origin}/emoji/${name}.webp?${url.query({
|
const badgeUrl = new URL(`/emoji/${name}.webp`, origin);
|
||||||
badge: '1',
|
badgeUrl.searchParams.set('badge', '1');
|
||||||
})}`;
|
badge = badgeUrl.href;
|
||||||
reaction = name.split('@')[0];
|
reaction = name.split('@')[0];
|
||||||
} else {
|
} else {
|
||||||
// Unicode絵文字の場合
|
// Unicode絵文字の場合
|
||||||
badge = `/twemoji-badge/${char2fileName(reaction)}.png`;
|
badge = `/twemoji-badge/${char2fileName(reaction)}.png`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (badge ? await fetch(badge).then(res => res.status !== 200).catch(() => true) : true) {
|
if (await fetch(badge).then(res => res.status !== 200).catch(() => true)) {
|
||||||
badge = iconUrl('plus');
|
badge = iconUrl('plus');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,10 +223,9 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createEmptyNotification() {
|
export async function createEmptyNotification(): Promise<void> {
|
||||||
return new Promise<void>(async res => {
|
return new Promise<void>(async res => {
|
||||||
if (!swLang.i18n) swLang.fetchLocale();
|
const i18n = await (swLang.i18n ?? swLang.fetchLocale());
|
||||||
const i18n = await swLang.i18n as I18n<any>;
|
|
||||||
const { t } = i18n;
|
const { t } = i18n;
|
||||||
|
|
||||||
await globalThis.registration.showNotification(
|
await globalThis.registration.showNotification(
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
import { get } from 'idb-keyval';
|
import { get } from 'idb-keyval';
|
||||||
|
|
||||||
export async function getAccountFromId(id: string) {
|
export async function getAccountFromId(id: string): Promise<{ token: string; id: string } | void> {
|
||||||
const accounts = await get('accounts') as { token: string; id: string; }[];
|
const accounts = await get<{ token: string; id: string }[]>('accounts');
|
||||||
if (!accounts) console.log('Accounts are not recorded');
|
if (!accounts) {
|
||||||
|
console.log('Accounts are not recorded');
|
||||||
|
return;
|
||||||
|
}
|
||||||
return accounts.find(e => e.id === id);
|
return accounts.find(e => e.id === id);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
export default function(user: { name?: string | null, username: string }): string {
|
export function getUserName(user: { name?: string | null; username: string }): string {
|
||||||
return user.name === '' ? user.username : user.name ?? user.username;
|
return user.name === '' ? user.username : user.name ?? user.username;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
export class I18n<T extends Record<string, any>> {
|
export type Locale = { [key: string]: string | Locale };
|
||||||
|
|
||||||
|
export class I18n<T extends Locale = Locale> {
|
||||||
public ts: T;
|
public ts: T;
|
||||||
|
|
||||||
constructor(locale: T) {
|
constructor(locale: T) {
|
||||||
|
@ -13,7 +15,8 @@ export class I18n<T extends Record<string, any>> {
|
||||||
// なるべくこのメソッド使うよりもlocale直接参照の方がvueのキャッシュ効いてパフォーマンスが良いかも
|
// なるべくこのメソッド使うよりもlocale直接参照の方がvueのキャッシュ効いてパフォーマンスが良いかも
|
||||||
public t(key: string, args?: Record<string, string>): string {
|
public t(key: string, args?: Record<string, string>): string {
|
||||||
try {
|
try {
|
||||||
let str = key.split('.').reduce((o, i) => o[i], this.ts) as unknown as string;
|
let str = key.split('.').reduce<Locale | Locale[keyof Locale]>((o, i) => o[i], this.ts);
|
||||||
|
if (typeof str !== 'string') throw new Error();
|
||||||
|
|
||||||
if (args) {
|
if (args) {
|
||||||
for (const [k, v] of Object.entries(args)) {
|
for (const [k, v] of Object.entries(args)) {
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
* Language manager for SW
|
* Language manager for SW
|
||||||
*/
|
*/
|
||||||
import { get, set } from 'idb-keyval';
|
import { get, set } from 'idb-keyval';
|
||||||
import { I18n } from '@/scripts/i18n';
|
import { I18n, type Locale } from '@/scripts/i18n';
|
||||||
|
|
||||||
class SwLang {
|
class SwLang {
|
||||||
public cacheName = `mk-cache-${_VERSION_}`;
|
public cacheName = `mk-cache-${_VERSION_}`;
|
||||||
|
@ -12,19 +12,19 @@ class SwLang {
|
||||||
return prelang;
|
return prelang;
|
||||||
});
|
});
|
||||||
|
|
||||||
public setLang(newLang: string) {
|
public setLang(newLang: string): Promise<I18n<Locale>> {
|
||||||
this.lang = Promise.resolve(newLang);
|
this.lang = Promise.resolve(newLang);
|
||||||
set('lang', newLang);
|
set('lang', newLang);
|
||||||
return this.fetchLocale();
|
return this.fetchLocale();
|
||||||
}
|
}
|
||||||
|
|
||||||
public i18n: Promise<I18n<any>> | null = null;
|
public i18n: Promise<I18n> | null = null;
|
||||||
|
|
||||||
public fetchLocale() {
|
public fetchLocale(): Promise<I18n<Locale>> {
|
||||||
return this.i18n = this._fetch();
|
return (this.i18n = this._fetch());
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _fetch() {
|
private async _fetch(): Promise<I18n<Locale>> {
|
||||||
// Service Workerは何度も起動しそのたびにlocaleを読み込むので、CacheStorageを使う
|
// Service Workerは何度も起動しそのたびにlocaleを読み込むので、CacheStorageを使う
|
||||||
const localeUrl = `/assets/locales/${await this.lang}.${_VERSION_}.json`;
|
const localeUrl = `/assets/locales/${await this.lang}.${_VERSION_}.json`;
|
||||||
let localeRes = await caches.match(localeUrl);
|
let localeRes = await caches.match(localeUrl);
|
||||||
|
@ -32,13 +32,13 @@ class SwLang {
|
||||||
// _DEV_がtrueの場合は常に最新化
|
// _DEV_がtrueの場合は常に最新化
|
||||||
if (!localeRes || _DEV_) {
|
if (!localeRes || _DEV_) {
|
||||||
localeRes = await fetch(localeUrl);
|
localeRes = await fetch(localeUrl);
|
||||||
const clone = localeRes?.clone();
|
const clone = localeRes.clone();
|
||||||
if (!clone?.clone().ok) Error('locale fetching error');
|
if (!clone.clone().ok) throw new Error('locale fetching error');
|
||||||
|
|
||||||
caches.open(this.cacheName).then(cache => cache.put(localeUrl, clone));
|
caches.open(this.cacheName).then(cache => cache.put(localeUrl, clone));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new I18n(await localeRes.json());
|
return new I18n<Locale>(await localeRes.json());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,5 @@
|
||||||
export function getUrlWithLoginId(url: string, loginId: string) {
|
export function getUrlWithLoginId(url: string, loginId: string): string {
|
||||||
const u = new URL(url, origin);
|
const u = new URL(url, origin);
|
||||||
u.searchParams.append('loginId', loginId);
|
u.searchParams.set('loginId', loginId);
|
||||||
return u.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getUrlWithoutLoginId(url: string) {
|
|
||||||
const u = new URL(url);
|
|
||||||
u.searchParams.delete('loginId');
|
|
||||||
return u.toString();
|
return u.toString();
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,17 +3,21 @@
|
||||||
* 各種操作
|
* 各種操作
|
||||||
*/
|
*/
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { SwMessage, SwMessageOrderType } from '@/types';
|
import type { SwMessage, SwMessageOrderType } from '@/types';
|
||||||
import { getAccountFromId } from '@/scripts/get-account-from-id';
|
import { getAccountFromId } from '@/scripts/get-account-from-id';
|
||||||
import { getUrlWithLoginId } from '@/scripts/login-id';
|
import { getUrlWithLoginId } from '@/scripts/login-id';
|
||||||
|
|
||||||
export const cli = new Misskey.api.APIClient({ origin, fetch: (...args) => fetch(...args) });
|
export const cli = new Misskey.api.APIClient({ origin, fetch: (...args): Promise<Response> => fetch(...args) });
|
||||||
|
|
||||||
export async function api<E extends keyof Misskey.Endpoints>(endpoint: E, userId: string, options?: Misskey.Endpoints[E]['req']) {
|
export async function api<E extends keyof Misskey.Endpoints, O extends Misskey.Endpoints[E]['req']>(endpoint: E, userId?: string, options?: O): Promise<void | ReturnType<typeof cli.request<E, O>>> {
|
||||||
const account = await getAccountFromId(userId);
|
let account: { token: string; id: string } | void;
|
||||||
if (!account) return;
|
|
||||||
|
|
||||||
return cli.request(endpoint, options, account.token);
|
if (userId) {
|
||||||
|
account = await getAccountFromId(userId);
|
||||||
|
if (!account) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cli.request(endpoint, options, account?.token);
|
||||||
}
|
}
|
||||||
|
|
||||||
// mark-all-as-read送出を1秒間隔に制限する
|
// mark-all-as-read送出を1秒間隔に制限する
|
||||||
|
@ -24,55 +28,52 @@ export function sendMarkAllAsRead(userId: string): Promise<null | undefined | vo
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
readBlockingStatus.set(userId, false);
|
readBlockingStatus.set(userId, false);
|
||||||
api('notifications/mark-all-as-read', userId)
|
api('notifications/mark-all-as-read', userId).then(resolve, resolve);
|
||||||
.then(resolve, resolve);
|
|
||||||
}, 1000);
|
}, 1000);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// rendered acctからユーザーを開く
|
// rendered acctからユーザーを開く
|
||||||
export function openUser(acct: string, loginId?: string) {
|
export function openUser(acct: string, loginId?: string): ReturnType<typeof openClient> {
|
||||||
return openClient('push', `/@${acct}`, loginId, { acct });
|
return openClient('push', `/@${acct}`, loginId, { acct });
|
||||||
}
|
}
|
||||||
|
|
||||||
// noteIdからノートを開く
|
// noteIdからノートを開く
|
||||||
export function openNote(noteId: string, loginId?: string) {
|
export function openNote(noteId: string, loginId?: string): ReturnType<typeof openClient> {
|
||||||
return openClient('push', `/notes/${noteId}`, loginId, { noteId });
|
return openClient('push', `/notes/${noteId}`, loginId, { noteId });
|
||||||
}
|
}
|
||||||
|
|
||||||
// noteIdからノートを開く
|
// noteIdからノートを開く
|
||||||
export function openAntenna(antennaId: string, loginId: string) {
|
export function openAntenna(antennaId: string, loginId: string): ReturnType<typeof openClient> {
|
||||||
return openClient('push', `/timeline/antenna/${antennaId}`, loginId, { antennaId });
|
return openClient('push', `/timeline/antenna/${antennaId}`, loginId, { antennaId });
|
||||||
}
|
}
|
||||||
|
|
||||||
// post-formのオプションから投稿フォームを開く
|
// post-formのオプションから投稿フォームを開く
|
||||||
export async function openPost(options: any, loginId?: string) {
|
export async function openPost(options: { initialText?: string; reply?: Misskey.entities.Note; renote?: Misskey.entities.Note }, loginId?: string): ReturnType<typeof openClient> {
|
||||||
// クエリを作成しておく
|
// クエリを作成しておく
|
||||||
let url = '/share?';
|
const url = '/share';
|
||||||
if (options.initialText) url += `text=${options.initialText}&`;
|
const query = new URLSearchParams();
|
||||||
if (options.reply) url += `replyId=${options.reply.id}&`;
|
if (options.initialText) query.set('text', options.initialText);
|
||||||
if (options.renote) url += `renoteId=${options.renote.id}&`;
|
if (options.reply) query.set('replyId', options.reply.id);
|
||||||
|
if (options.renote) query.set('renoteId', options.renote.id);
|
||||||
|
|
||||||
return openClient('post', url, loginId, { options });
|
return openClient('post', `${url}?${query}`, loginId, { options });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function openClient(order: SwMessageOrderType, url: string, loginId?: string, query: any = {}) {
|
export async function openClient(order: SwMessageOrderType, url: string, loginId?: string, query: Record<string, SwMessage[string]> = {}): Promise<WindowClient | null> {
|
||||||
const client = await findClient();
|
const client = await findClient();
|
||||||
|
|
||||||
if (client) {
|
if (client) {
|
||||||
client.postMessage({ type: 'order', ...query, order, loginId, url } as SwMessage);
|
client.postMessage({ type: 'order', ...query, order, loginId, url } satisfies SwMessage);
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
return globalThis.clients.openWindow(loginId ? getUrlWithLoginId(url, loginId) : url);
|
return globalThis.clients.openWindow(loginId ? getUrlWithLoginId(url, loginId) : url);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function findClient() {
|
export async function findClient(): Promise<WindowClient | null> {
|
||||||
const clients = await globalThis.clients.matchAll({
|
const clients = await globalThis.clients.matchAll({
|
||||||
type: 'window',
|
type: 'window',
|
||||||
});
|
});
|
||||||
for (const c of clients) {
|
return clients.find(c => !(new URL(c.url)).searchParams.has('zen')) ?? null;
|
||||||
if (!(new URL(c.url)).searchParams.has('zen')) return c;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,8 @@
|
||||||
export const twemojiSvgBase = '/twemoji';
|
|
||||||
|
|
||||||
export function char2fileName(char: string): string {
|
export function char2fileName(char: string): string {
|
||||||
let codes = Array.from(char).map(x => x.codePointAt(0)?.toString(16));
|
let codes = Array.from(char)
|
||||||
|
.map(x => x.codePointAt(0)?.toString(16))
|
||||||
|
.filter(<T>(x: T | undefined): x is T => x !== undefined);
|
||||||
if (!codes.includes('200d')) codes = codes.filter(x => x !== 'fe0f');
|
if (!codes.includes('200d')) codes = codes.filter(x => x !== 'fe0f');
|
||||||
codes = codes.filter(x => x && x.length);
|
codes = codes.filter(x => x.length !== 0);
|
||||||
return codes.join('-');
|
return codes.join('-');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function char2filePath(char: string): string {
|
|
||||||
return `${twemojiSvgBase}/${char2fileName(char)}.svg`;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
/* objを検査して
|
|
||||||
* 1. 配列に何も入っていない時はクエリを付けない
|
|
||||||
* 2. プロパティがundefinedの時はクエリを付けない
|
|
||||||
* (new URLSearchParams(obj)ではそこまで丁寧なことをしてくれない)
|
|
||||||
*/
|
|
||||||
export function query(obj: object): 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>);
|
|
||||||
|
|
||||||
return Object.entries(params)
|
|
||||||
.map((e) => `${e[0]}=${encodeURIComponent(e[1])}`)
|
|
||||||
.join('&');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function appendQuery(url: string, query: string): string {
|
|
||||||
return `${url}${/\?/.test(url) ? url.endsWith('?') ? '' : '&' : '?'}${query}`;
|
|
||||||
}
|
|
|
@ -1,12 +1,12 @@
|
||||||
|
import { get } from 'idb-keyval';
|
||||||
|
import * as Acct from 'misskey-js/built/acct';
|
||||||
|
import type { PushNotificationDataMap } from '@/types';
|
||||||
import { createEmptyNotification, createNotification } from '@/scripts/create-notification';
|
import { createEmptyNotification, createNotification } from '@/scripts/create-notification';
|
||||||
import { swLang } from '@/scripts/lang';
|
import { swLang } from '@/scripts/lang';
|
||||||
import { PushNotificationDataMap } from '@/types';
|
|
||||||
import * as swos from '@/scripts/operations';
|
import * as swos from '@/scripts/operations';
|
||||||
import { acct as getAcct } from '@/filters/user';
|
|
||||||
import { get } from 'idb-keyval';
|
|
||||||
|
|
||||||
globalThis.addEventListener('install', ev => {
|
globalThis.addEventListener('install', () => {
|
||||||
//ev.waitUntil(globalThis.skipWaiting());
|
// ev.waitUntil(globalThis.skipWaiting());
|
||||||
});
|
});
|
||||||
|
|
||||||
globalThis.addEventListener('activate', ev => {
|
globalThis.addEventListener('activate', ev => {
|
||||||
|
@ -43,7 +43,7 @@ globalThis.addEventListener('push', ev => {
|
||||||
ev.waitUntil(globalThis.clients.matchAll({
|
ev.waitUntil(globalThis.clients.matchAll({
|
||||||
includeUncontrolled: true,
|
includeUncontrolled: true,
|
||||||
type: 'window',
|
type: 'window',
|
||||||
}).then(async (clients: readonly WindowClient[]) => {
|
}).then(async () => {
|
||||||
const data: PushNotificationDataMap[keyof PushNotificationDataMap] = ev.data?.json();
|
const data: PushNotificationDataMap[keyof PushNotificationDataMap] = ev.data?.json();
|
||||||
|
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
|
@ -66,7 +66,7 @@ globalThis.addEventListener('push', ev => {
|
||||||
});
|
});
|
||||||
|
|
||||||
globalThis.addEventListener('notificationclick', (ev: ServiceWorkerGlobalScopeEventMap['notificationclick']) => {
|
globalThis.addEventListener('notificationclick', (ev: ServiceWorkerGlobalScopeEventMap['notificationclick']) => {
|
||||||
ev.waitUntil((async () => {
|
ev.waitUntil((async (): Promise<void> => {
|
||||||
if (_DEV_) {
|
if (_DEV_) {
|
||||||
console.log('notificationclick', ev.action, ev.notification.data);
|
console.log('notificationclick', ev.action, ev.notification.data);
|
||||||
}
|
}
|
||||||
|
@ -83,7 +83,7 @@ globalThis.addEventListener('notificationclick', (ev: ServiceWorkerGlobalScopeEv
|
||||||
if ('userId' in data.body) await swos.api('following/create', loginId, { userId: data.body.userId });
|
if ('userId' in data.body) await swos.api('following/create', loginId, { userId: data.body.userId });
|
||||||
break;
|
break;
|
||||||
case 'showUser':
|
case 'showUser':
|
||||||
if ('user' in data.body) client = await swos.openUser(getAcct(data.body.user), loginId);
|
if ('user' in data.body) client = await swos.openUser(Acct.toString(data.body.user), loginId);
|
||||||
break;
|
break;
|
||||||
case 'reply':
|
case 'reply':
|
||||||
if ('note' in data.body) client = await swos.openPost({ reply: data.body.note }, loginId);
|
if ('note' in data.body) client = await swos.openPost({ reply: data.body.note }, loginId);
|
||||||
|
@ -120,7 +120,7 @@ globalThis.addEventListener('notificationclick', (ev: ServiceWorkerGlobalScopeEv
|
||||||
if ('note' in data.body) {
|
if ('note' in data.body) {
|
||||||
client = await swos.openNote(data.body.note.id, loginId);
|
client = await swos.openNote(data.body.note.id, loginId);
|
||||||
} else if ('user' in data.body) {
|
} else if ('user' in data.body) {
|
||||||
client = await swos.openUser(getAcct(data.body.user), loginId);
|
client = await swos.openUser(Acct.toString(data.body.user), loginId);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -160,7 +160,7 @@ globalThis.addEventListener('notificationclick', (ev: ServiceWorkerGlobalScopeEv
|
||||||
globalThis.addEventListener('notificationclose', (ev: ServiceWorkerGlobalScopeEventMap['notificationclose']) => {
|
globalThis.addEventListener('notificationclose', (ev: ServiceWorkerGlobalScopeEventMap['notificationclose']) => {
|
||||||
const data: PushNotificationDataMap[keyof PushNotificationDataMap] = ev.notification.data;
|
const data: PushNotificationDataMap[keyof PushNotificationDataMap] = ev.notification.data;
|
||||||
|
|
||||||
ev.waitUntil((async () => {
|
ev.waitUntil((async (): Promise<void> => {
|
||||||
if (data.type === 'notification') {
|
if (data.type === 'notification') {
|
||||||
await swos.sendMarkAllAsRead(data.userId);
|
await swos.sendMarkAllAsRead(data.userId);
|
||||||
}
|
}
|
||||||
|
@ -169,7 +169,7 @@ globalThis.addEventListener('notificationclose', (ev: ServiceWorkerGlobalScopeEv
|
||||||
});
|
});
|
||||||
|
|
||||||
globalThis.addEventListener('message', (ev: ServiceWorkerGlobalScopeEventMap['message']) => {
|
globalThis.addEventListener('message', (ev: ServiceWorkerGlobalScopeEventMap['message']) => {
|
||||||
ev.waitUntil((async () => {
|
ev.waitUntil((async (): Promise<void> => {
|
||||||
switch (ev.data) {
|
switch (ev.data) {
|
||||||
case 'clear':
|
case 'clear':
|
||||||
// Cache Storage全削除
|
// Cache Storage全削除
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
import * as Misskey from 'misskey-js';
|
import type * as Misskey from 'misskey-js';
|
||||||
|
|
||||||
export type SwMessageOrderType = 'post' | 'push';
|
export type SwMessageOrderType = 'post' | 'push';
|
||||||
|
|
||||||
export type SwMessage = {
|
export type SwMessage = {
|
||||||
type: 'order';
|
type: 'order';
|
||||||
order: SwMessageOrderType;
|
order: SwMessageOrderType;
|
||||||
loginId: string;
|
loginId?: string;
|
||||||
url: string;
|
url: string;
|
||||||
[x: string]: any;
|
[x: string]: unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Defined also @/core/PushNotificationService.ts#L12
|
// Defined also @/core/PushNotificationService.ts#L12
|
||||||
type PushNotificationDataSourceMap = {
|
type PushNotificationDataSourceMap = {
|
||||||
notification: Misskey.entities.Notification;
|
notification: Misskey.entities.Notification;
|
||||||
unreadAntennaNote: {
|
unreadAntennaNote: {
|
||||||
antenna: { id: string, name: string };
|
antenna: { id: string; name: string };
|
||||||
note: Misskey.entities.Note;
|
note: Misskey.entities.Note;
|
||||||
};
|
};
|
||||||
readAllNotifications: undefined;
|
readAllNotifications: undefined;
|
||||||
|
@ -31,8 +31,8 @@ export type PushNotificationDataMap = {
|
||||||
[K in keyof PushNotificationDataSourceMap]: PushNotificationData<K>;
|
[K in keyof PushNotificationDataSourceMap]: PushNotificationData<K>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BadgeNames =
|
export type BadgeNames =
|
||||||
'null'
|
| 'null'
|
||||||
| 'antenna'
|
| 'antenna'
|
||||||
| 'arrow-back-up'
|
| 'arrow-back-up'
|
||||||
| 'at'
|
| 'at'
|
||||||
|
@ -44,5 +44,4 @@ export type BadgeNames =
|
||||||
| 'quote'
|
| 'quote'
|
||||||
| 'repeat'
|
| 'repeat'
|
||||||
| 'user-plus'
|
| 'user-plus'
|
||||||
| 'users'
|
| 'users';
|
||||||
;
|
|
||||||
|
|
Loading…
Reference in a new issue