update backend to the new templates
This commit is contained in:
parent
4e30986cda
commit
05be2596ea
44 changed files with 156 additions and 111 deletions
|
|
@ -767,7 +767,7 @@ export class DriveService {
|
|||
|
||||
@bindThis
|
||||
public async deleteFileSync(file: MiDriveFile, isExpired = false, deleter?: { id: string }) {
|
||||
const promises = [];
|
||||
const promises: Promise<void>[] = [];
|
||||
|
||||
if (file.storedInternal) {
|
||||
promises.push(this.deleteLocalFile(file.accessKey!));
|
||||
|
|
|
|||
|
|
@ -299,7 +299,7 @@ export class MfmService {
|
|||
(note that the `rp` are to be ignored, they only exist
|
||||
for browsers who don't understand ruby)
|
||||
*/
|
||||
let nonRtNodes = [];
|
||||
let nonRtNodes: ChildNode[] = [];
|
||||
// scan children, ignore `rp`, split on `rt`
|
||||
for (const child of node.childNodes) {
|
||||
if (isText(child)) {
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ export class ReactionsBufferingService implements OnApplicationShutdown {
|
|||
// TODO: scanは重い可能性があるので、別途 bufferedNoteIds を直接Redis上に持っておいてもいいかもしれない
|
||||
@bindThis
|
||||
public async bake(): Promise<void> {
|
||||
const bufferedNoteIds = [];
|
||||
const bufferedNoteIds: string[] = [];
|
||||
let cursor = '0';
|
||||
do {
|
||||
// https://github.com/redis/ioredis#transparent-key-prefixing
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ export class FlashEntityService {
|
|||
// { schema: 'UserDetailed' } すると無限ループするので注意
|
||||
const user = hint?.packedUser ?? await this.userEntityService.pack(flash.user ?? flash.userId, me);
|
||||
|
||||
let isLiked = undefined;
|
||||
let isLiked: boolean | undefined = undefined;
|
||||
if (meId) {
|
||||
isLiked = hint?.likedFlashIds
|
||||
? hint.likedFlashIds.includes(flash.id)
|
||||
|
|
|
|||
|
|
@ -51,8 +51,8 @@ export class MetaEntityService {
|
|||
.getMany();
|
||||
|
||||
// クライアントの手間を減らすためあらかじめJSONに変換しておく
|
||||
let defaultLightTheme = null;
|
||||
let defaultDarkTheme = null;
|
||||
let defaultLightTheme: string | null = null;
|
||||
let defaultDarkTheme: string | null = null;
|
||||
if (instance.defaultLightTheme) {
|
||||
try {
|
||||
defaultLightTheme = JSON.stringify(JSON5.parse(instance.defaultLightTheme));
|
||||
|
|
|
|||
|
|
@ -491,7 +491,7 @@ export class NoteEntityService implements OnModuleInit {
|
|||
|
||||
@bindThis
|
||||
public async packAttachedFiles(fileIds: MiNote['fileIds'], packedFiles: Map<MiNote['fileIds'][number], Packed<'DriveFile'> | null>): Promise<Packed<'DriveFile'>[]> {
|
||||
const missingIds = [];
|
||||
const missingIds: string[] = [];
|
||||
for (const id of fileIds) {
|
||||
if (!packedFiles.has(id)) missingIds.push(id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -232,7 +232,7 @@ export class NotificationEntityService implements OnModuleInit {
|
|||
|
||||
validNotifications = validNotifications.filter(x => !('noteId' in x) || packedNotes.has(x.noteId));
|
||||
|
||||
const userIds = [];
|
||||
const userIds: string[] = [];
|
||||
for (const notification of validNotifications) {
|
||||
if ('notifierId' in notification) userIds.push(notification.notifierId);
|
||||
if (notification.type === 'reaction:grouped') userIds.push(...notification.reactions.map(x => x.userId));
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
*/
|
||||
|
||||
function parseBigIntChunked(str: string, base: number, chunkSize: number, powerOfChunkSize: bigint): bigint {
|
||||
const chunks = [];
|
||||
const chunks: string[] = [];
|
||||
while (str.length > 0) {
|
||||
chunks.unshift(str.slice(-chunkSize));
|
||||
str = str.slice(0, -chunkSize);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export function generateInviteCode(now: number): string {
|
|||
chars: CHARS,
|
||||
});
|
||||
|
||||
const uniqueId = [];
|
||||
const uniqueId: string[] = [];
|
||||
let n = Math.floor(now / 1000 / 60);
|
||||
while (true) {
|
||||
uniqueId.push(CHARS[n % CHARS.length]);
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ export function lessThan(xs: number[], ys: number[]): boolean {
|
|||
* Returns the longest prefix of elements that satisfy the predicate
|
||||
*/
|
||||
export function takeWhile<T>(f: Predicate<T>, xs: T[]): T[] {
|
||||
const ys = [];
|
||||
const ys: T[] = [];
|
||||
for (const x of xs) {
|
||||
if (f(x)) {
|
||||
ys.push(x);
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import type { HttpRequestService } from '@/core/HttpRequestService.js';
|
|||
type Field = { name: string, value: string };
|
||||
|
||||
export async function verifyFieldLinks(fields: Field[], profileUrls: string[], httpRequestService: HttpRequestService): Promise<string[]> {
|
||||
const verified_links = [];
|
||||
const verified_links: string[] = [];
|
||||
for (const field_url of fields) {
|
||||
try {
|
||||
// getHtml validates the input URL, so we can safely pass in untrusted values
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ class TypeORMLogger implements Logger {
|
|||
@bindThis
|
||||
private transformParameters(parameters?: unknown[]): Data | undefined {
|
||||
if (this.props.enableQueryParamLogging && parameters && parameters.length > 0) {
|
||||
return parameters.reduce((params: Record<string, string>, p, i) => {
|
||||
return parameters.reduce<Record<string, string>>((params: Record<string, string>, p, i) => {
|
||||
params[`$${i + 1}`] = stringifyParameter(p);
|
||||
return params;
|
||||
}, {} as Record<string, string>);
|
||||
|
|
|
|||
|
|
@ -397,7 +397,7 @@ export class ImportNotesProcessorService {
|
|||
const visibility = followers ? toot.cc.includes('https://www.w3.org/ns/activitystreams#Public') ? 'home' : 'followers' : 'public';
|
||||
|
||||
const date = new Date(toot.object.published);
|
||||
let text = undefined;
|
||||
let text: string | undefined = undefined;
|
||||
const files: MiDriveFile[] = [];
|
||||
let reply: MiNote | null = null;
|
||||
|
||||
|
|
@ -464,7 +464,7 @@ export class ImportNotesProcessorService {
|
|||
}
|
||||
|
||||
const date = new Date(post.object.published);
|
||||
let text = undefined;
|
||||
let text: string | undefined = undefined;
|
||||
const files: MiDriveFile[] = [];
|
||||
let reply: MiNote | null = null;
|
||||
|
||||
|
|
@ -547,7 +547,7 @@ export class ImportNotesProcessorService {
|
|||
const files: MiDriveFile[] = [];
|
||||
|
||||
function decodeIGString(str: string) {
|
||||
const arr = [];
|
||||
const arr: number[] = [];
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
arr.push(str.charCodeAt(i));
|
||||
}
|
||||
|
|
@ -700,7 +700,7 @@ export class ImportNotesProcessorService {
|
|||
const files: MiDriveFile[] = [];
|
||||
|
||||
function decodeFBString(str: string) {
|
||||
const arr = [];
|
||||
const arr: number[] = [];
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
arr.push(str.charCodeAt(i));
|
||||
}
|
||||
|
|
@ -708,7 +708,7 @@ export class ImportNotesProcessorService {
|
|||
}
|
||||
|
||||
if (post.attachments && this.isIterable(post.attachments)) {
|
||||
const media = [];
|
||||
const media: any[] = [];
|
||||
for await (const data of post.attachments[0].data) {
|
||||
if (data.media) {
|
||||
media.push(data.media);
|
||||
|
|
|
|||
|
|
@ -63,7 +63,11 @@ export const meta = {
|
|||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {} as const;
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
required: [],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
|
|
|
|||
|
|
@ -14,8 +14,6 @@ export const meta = {
|
|||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'write:admin:cw-instance',
|
||||
|
||||
res: {},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
|
|
|||
|
|
@ -19,8 +19,6 @@ export const meta = {
|
|||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'write:admin:cw-note',
|
||||
|
||||
res: {},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
|
|
|||
|
|
@ -17,8 +17,6 @@ export const meta = {
|
|||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'write:admin:cw-user',
|
||||
|
||||
res: {},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
|
@ -63,6 +61,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
userUsername: user.username,
|
||||
userHost: user.host,
|
||||
});
|
||||
|
||||
return {};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -167,6 +167,7 @@ export const paramDef = {
|
|||
fileId: { type: 'string', format: 'misskey:id' },
|
||||
url: { type: 'string' },
|
||||
},
|
||||
// TODO it chokes on this
|
||||
anyOf: [
|
||||
{ required: ['fileId'] },
|
||||
{ required: ['url'] },
|
||||
|
|
|
|||
|
|
@ -102,8 +102,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
case 'NO_SUCH_EMOJI': throw new ApiError(meta.errors.noSuchEmoji);
|
||||
case 'SAME_NAME_EMOJI_EXISTS': throw new ApiError(meta.errors.sameNameEmojiExists);
|
||||
}
|
||||
// 網羅性チェック
|
||||
const mustBeNever: never = error;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,11 @@ export const meta = {
|
|||
kind: 'write:admin:meta',
|
||||
} as const;
|
||||
|
||||
export const paramDef = {} as const;
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
required: [],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { RegistrationTicketsRepository } from '@/models/_.js';
|
||||
import type { RegistrationTicketsRepository, MiRegistrationTicket } from '@/models/_.js';
|
||||
import { InviteCodeEntityService } from '@/core/entities/InviteCodeEntityService.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
|
@ -65,7 +65,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
throw new ApiError(meta.errors.invalidDateTime);
|
||||
}
|
||||
|
||||
const ticketsPromises = [];
|
||||
const ticketsPromises: Promise<MiRegistrationTicket>[] = [];
|
||||
|
||||
for (let i = 0; i < ps.count; i++) {
|
||||
ticketsPromises.push(this.registrationTicketsRepository.insertOne({
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { ChannelsRepository, DriveFilesRepository } from '@/models/_.js';
|
||||
import type { ChannelsRepository, DriveFilesRepository, MiDriveFile } from '@/models/_.js';
|
||||
import type { MiChannel } from '@/models/Channel.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js';
|
||||
|
|
@ -68,7 +68,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private channelEntityService: ChannelEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
let banner = null;
|
||||
let banner: MiDriveFile | null = null;
|
||||
if (ps.bannerId != null) {
|
||||
banner = await this.driveFilesRepository.findOneBy({
|
||||
id: ps.bannerId,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { DriveFilesRepository, ChannelsRepository } from '@/models/_.js';
|
||||
import type { DriveFilesRepository, ChannelsRepository, MiDriveFile } from '@/models/_.js';
|
||||
import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
|
|
@ -99,8 +99,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
throw new ApiError(meta.errors.accessDenied);
|
||||
}
|
||||
|
||||
// eslint:disable-next-line:no-unnecessary-initializer
|
||||
let banner = undefined;
|
||||
let banner: MiDriveFile | null = null;
|
||||
if (ps.bannerId != null) {
|
||||
banner = await this.driveFilesRepository.findOneBy({
|
||||
id: ps.bannerId,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { GetterService } from '@/server/api/GetterService.js';
|
|||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import type { DriveFilesRepository, MiUser } from '@/models/_.js';
|
||||
import type { DriveFilesRepository, MiUser, MiDriveFile } from '@/models/_.js';
|
||||
import type { Config } from '@/config.js';
|
||||
|
||||
export const meta = {
|
||||
|
|
@ -96,7 +96,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
throw new ApiError(meta.errors.noSuchRoom);
|
||||
}
|
||||
|
||||
let file = null;
|
||||
let file: MiDriveFile | null = null;
|
||||
if (ps.fileId != null) {
|
||||
file = await this.driveFilesRepository.findOneBy({
|
||||
id: ps.fileId,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { GetterService } from '@/server/api/GetterService.js';
|
|||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import type { DriveFilesRepository, MiUser } from '@/models/_.js';
|
||||
import type { DriveFilesRepository, MiUser, MiDriveFile } from '@/models/_.js';
|
||||
import type { Config } from '@/config.js';
|
||||
|
||||
export const meta = {
|
||||
|
|
@ -103,7 +103,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
throw new ApiError(meta.errors.maxLength);
|
||||
}
|
||||
|
||||
let file = null;
|
||||
let file: MiDriveFile | null = null;
|
||||
if (ps.fileId != null) {
|
||||
file = await this.driveFilesRepository.findOneBy({
|
||||
id: ps.fileId,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { DriveFoldersRepository } from '@/models/_.js';
|
||||
import type { DriveFoldersRepository, MiDriveFolder } from '@/models/_.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { DriveFolderEntityService } from '@/core/entities/DriveFolderEntityService.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
|
|
@ -61,7 +61,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
// If the parent folder is specified
|
||||
let parent = null;
|
||||
let parent: MiDriveFolder | null = null;
|
||||
if (ps.parentId) {
|
||||
// Fetch parent folder
|
||||
parent = await this.driveFoldersRepository.findOneBy({
|
||||
|
|
|
|||
|
|
@ -20,8 +20,6 @@ export const meta = {
|
|||
},
|
||||
},
|
||||
|
||||
res: {},
|
||||
|
||||
// 10 calls per 5 seconds
|
||||
limit: {
|
||||
duration: 1000 * 5,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import ms from 'ms';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { DriveFilesRepository, PagesRepository } from '@/models/_.js';
|
||||
import type { DriveFilesRepository, PagesRepository, MiDriveFile } from '@/models/_.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { MiPage, pageNameSchema } from '@/models/Page.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
|
|
@ -83,7 +83,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private readonly timeService: TimeService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
let eyeCatchingImage = null;
|
||||
let eyeCatchingImage: MiDriveFile | null = null;
|
||||
if (ps.eyeCatchingImageId != null) {
|
||||
eyeCatchingImage = await this.driveFilesRepository.findOneBy({
|
||||
id: ps.eyeCatchingImageId,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { createReadStream } from 'node:fs';
|
||||
import { Readable } from 'node:stream';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { getErrorData, getErrorException, getErrorStatus, MastodonLogger } from '@/server/api/mastodon/MastodonLogger.js';
|
||||
|
|
@ -118,7 +120,10 @@ export class MastodonApiServerService {
|
|||
}
|
||||
|
||||
const client = this.clientService.getClient(_request);
|
||||
const data = await client.uploadMedia(multipartData);
|
||||
const data = await client.uploadMedia({
|
||||
...multipartData,
|
||||
stream: Readable.toWeb(createReadStream(multipartData.filepath)),
|
||||
});
|
||||
const response = convertAttachment(data.data as Entity.Attachment);
|
||||
|
||||
return reply.send(response);
|
||||
|
|
@ -131,7 +136,10 @@ export class MastodonApiServerService {
|
|||
}
|
||||
|
||||
const client = this.clientService.getClient(_request);
|
||||
const data = await client.uploadMedia(multipartData, _request.body);
|
||||
const data = await client.uploadMedia({
|
||||
...multipartData,
|
||||
stream: Readable.toWeb(createReadStream(multipartData.filepath)),
|
||||
}, _request.body);
|
||||
const response = convertAttachment(data.data as Entity.Attachment);
|
||||
|
||||
return reply.send(response);
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Entity, MastodonEntity, MisskeyEntity } from 'megalodon';
|
||||
import * as mfm from 'mfm-js';
|
||||
import { MastodonNotificationType } from 'megalodon/lib/src/mastodon/notification.js';
|
||||
import { NotificationType } from 'megalodon/lib/src/notification.js';
|
||||
import { MastodonNotificationType } from 'megalodon/built/lib/mastodon/notification.js';
|
||||
import { NotificationType } from 'megalodon/built/lib/notification.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { MfmService } from '@/core/MfmService.js';
|
||||
import type { Config } from '@/config.js';
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@
|
|||
window.onload = async () => {
|
||||
const content = document.getElementById('content');
|
||||
|
||||
document.getElementById('ls').addEventListener('click', () => {
|
||||
content.innerHTML = '';
|
||||
document.getElementById('ls')?.addEventListener('click', () => {
|
||||
if (content) content.innerHTML = '';
|
||||
|
||||
const lsEditor = document.createElement('div');
|
||||
lsEditor.id = 'lsEditor';
|
||||
|
|
@ -31,7 +31,7 @@ window.onload = async () => {
|
|||
lsEditor.appendChild(adder);
|
||||
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const k = localStorage.key(i);
|
||||
const k = /** @type {string} */ (localStorage.key(i));
|
||||
const record = document.createElement('div');
|
||||
record.classList.add('record');
|
||||
const header = document.createElement('header');
|
||||
|
|
@ -57,6 +57,6 @@ window.onload = async () => {
|
|||
lsEditor.appendChild(record);
|
||||
}
|
||||
|
||||
content.appendChild(lsEditor);
|
||||
content?.appendChild(lsEditor);
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@
|
|||
//#region Detect language & fetch translations
|
||||
if (!localStorage.getItem('locale')) {
|
||||
const supportedLangs = LANGS;
|
||||
/** @type {string | null | undefined} */
|
||||
let lang = localStorage.getItem('lang');
|
||||
if (lang == null || !supportedLangs.includes(lang)) {
|
||||
if (supportedLangs.includes(navigator.language)) {
|
||||
|
|
@ -92,12 +93,20 @@
|
|||
}
|
||||
//#endregion
|
||||
|
||||
/**
|
||||
* @param {string} styleText
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function addStyle(styleText) {
|
||||
let css = document.createElement('style');
|
||||
css.appendChild(document.createTextNode(styleText));
|
||||
document.head.appendChild(css);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} code
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function renderError(code) {
|
||||
// Cannot set property 'innerHTML' of null を回避
|
||||
if (document.readyState === 'loading') {
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
//#region Detect language & fetch translations
|
||||
if (!localStorage.getItem('locale')) {
|
||||
const supportedLangs = LANGS;
|
||||
/** @type {string | null | undefined} */
|
||||
let lang = localStorage.getItem('lang');
|
||||
if (lang == null || !supportedLangs.includes(lang)) {
|
||||
if (supportedLangs.includes(navigator.language)) {
|
||||
|
|
@ -154,12 +155,21 @@
|
|||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} styleText
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function addStyle(styleText) {
|
||||
let css = document.createElement('style');
|
||||
css.appendChild(document.createTextNode(styleText));
|
||||
document.head.appendChild(css);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} code
|
||||
* @param {any} [details]
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function renderError(code, details) {
|
||||
// Cannot set property 'innerHTML' of null を回避
|
||||
if (document.readyState === 'loading') {
|
||||
|
|
@ -233,7 +243,7 @@
|
|||
<code>ERROR CODE: ${code}</code>
|
||||
</summary>
|
||||
<code>${details.toString()} ${JSON.stringify(details)}</code>`;
|
||||
errorsElement.appendChild(detailsElement);
|
||||
errorsElement?.appendChild(detailsElement);
|
||||
addStyle(`
|
||||
* {
|
||||
font-family: BIZ UDGothic, Roboto, HelveticaNeue, Arial, sans-serif;
|
||||
|
|
|
|||
|
|
@ -6,9 +6,15 @@
|
|||
'use strict';
|
||||
|
||||
window.onload = async () => {
|
||||
const account = JSON.parse(localStorage.getItem('account'));
|
||||
const i = account.token;
|
||||
const accountRaw = localStorage.getItem('account');
|
||||
const account = accountRaw ? JSON.parse(accountRaw) : null;
|
||||
const i = account?.token;
|
||||
|
||||
/**
|
||||
* @param {string} endpoint
|
||||
* @param {Record<string, unknown>} data
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
const api = (endpoint, data = {}) => {
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
// Append a credential
|
||||
|
|
@ -17,19 +23,19 @@ window.onload = async () => {
|
|||
// Send request
|
||||
fetch(endpoint.indexOf('://') > -1 ? endpoint : `/api/${endpoint}`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
credentials: 'omit',
|
||||
cache: 'no-cache'
|
||||
cache: 'no-cache',
|
||||
}).then(async (res) => {
|
||||
const body = res.status === 204 ? null : await res.json();
|
||||
|
||||
if (res.status === 200) {
|
||||
resolve(body);
|
||||
} else if (res.status === 204) {
|
||||
resolve();
|
||||
resolve({});
|
||||
} else {
|
||||
reject(body.error);
|
||||
}
|
||||
|
|
@ -39,7 +45,7 @@ window.onload = async () => {
|
|||
return promise;
|
||||
};
|
||||
|
||||
document.getElementById('submit').addEventListener('click', () => {
|
||||
document.getElementById('submit')?.addEventListener('click', () => {
|
||||
api('notes/create', {
|
||||
text: (/** @type {HTMLInputElement} */(document.getElementById('text'))).value
|
||||
}).then(() => {
|
||||
|
|
@ -49,6 +55,7 @@ window.onload = async () => {
|
|||
|
||||
api('notes/timeline').then(notes => {
|
||||
const tl = document.getElementById('tl');
|
||||
if (!tl) return;
|
||||
for (const note of notes) {
|
||||
const el = document.createElement('div');
|
||||
const name = document.createElement('header');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue